Jump to content
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr ×
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr

C++ DLL을 델파이 프로그램에서 사용하는 방법


Recommended Posts

Thushara Wedagedara"How To Use A C++ DLL In Any Delphi Program" 을 번역했습니다. (원문 작성: 2021년 9월, 최종 번역: 2022년 12월)

인터넷에는 유용한 C++ 라이브러리가 엄청나게 풍부하다. 이 블로그에서도 C++를 훌륭하게 사용하기와 관련하여 많이 소개하고 있다. 일반적으로 C++는 매우 높은 성능을 제공한다. 우리는 C++ 라이브러리의 소스 코드를 가지고 있는 경우, 패키지를 만들어서 델파이 프로그램에서 그 C++를 사용하면 된다. 하지만, C++ 라이브러리의 소스 코드가 없는 경우가 종종 있다. 일반적으로 상용 C++ 라이브러리는 C++ 헤더들과 정적 라이브러리 파일(.lib)만 제공하고, .cpp 소스 파일은 제공하지 않는다. 이런 경우에 델파이 애플리케이션에서 해당 C++ 라이브러리를 사용하고 싶다면, 프록시(Proxy) DLL을 사용하면 가능하다. 

이미 있는 C++ 코드를 델파이에 통합하려는 경우라면 이 방식이 매우 유용하다. 하지만, 만약 C++ 코드를 새로 작성하고 싶은 경우라면, RAD 스튜디오의 일부인 C++빌더를 사용할 수 있다. C++빌더는 C++ 개발자를 위해 잘 정비된 통합 환경을 제공한다.

목차


1 C++ DLL과 델파이를 연결해 줄 프록시 DLL을 만들려면?

DLL을 델파이와 연결하려면, 그 DLL은 C++가 아니라 윈도우 API 스타일로 된 간단한 API를 노출해야 한다. 무슨 C++ 컴파일러든 가지고 있다면, 우리는 프록시 DLL을 컴파일할 수 있다.

C++빌더는 RAD 스튜디오에서 C++를 담당하는데, 델파이에서 직접 사용할 수 있는 C++ 패키지 파일(BPL)을 빌드할 수 있다. 이처럼, C++빌더는 델파이 자체와 매우 밀접하게 통합된다는 장점이 있다. 즉, C++빌더 안에서 오브젝트 파스칼 파일을 사용할 수 있고, 델파이 안에서 C++ BPL 파일을 사용할 수 있다. 더 많은 정보 보기.

이제 간단한 예를 보자. 아래의 C++을 사용하고 싶은데, DLL 파일 하나와 그리고 아래 선언이 담긴 헤더 파일 하나만 있고, 구현이 담긴 .cpp 파일은 없는 상황이라고 가정해보자.

#pragma once
class CppClass
{
  int data;
public:
  CppClass();
  ~CppClass();
  void setData(int);
  int getSquare();
};

예제로 사용된 소스 코드는아래 링크에 있다:
https://github.com/PacktPublishing/Delphi-High-Performance/tree/master/Chapter%208/StaticLib1

2 델파이 프로그램에서 프록시 dll을 통해 C++ 라이브러리를 사용하는 방법을 알아보자.

우리는 프록시 DLL을 만들어야 한다.

즐겨 사용하는 IDE를 사용하여 새 C++ DLL 프로젝트를 하나 생성한다.

spacer.png

그러면 자동으로 “dllmain.cpp” 파일이 추가된다. 하지만 이 파일 외에도 우리는 정적 라이브러리를 덮는(Wrap) 유닛이 필요하다. 새 유닛을 하나 추가하고 이름을 “StaticLibWrapper.cpp”으로 지정한다. 이제 이 유닛을 사용하여 C++ DLL을 웹핑(Wrapping)하자.

3 이 웹퍼(Wrapper) 유닛에서 우리는 프로젝트로 가져오려는 정적 라이브러리의 헤더 파일을 include 해야 한다.

#include "stdafx.h"
#include "CppClass.h"

이제, 이 프로젝트 폴더 안에 해당 정적 라이브러리의 헤더 파일들을 복사하여 넣는다. 다음으로, 이 프로젝트 안에 이 정적 라이브러리를 포함할 차례이다. 그러기 위해 프로젝트 옵션의 library directories에 그 정적 라이브러리 폴더를 추가한다.

spacer.png

또는 비주얼 스튜디오에서라면 “Configuration Properties | Linker | General | Additional Library Directories settings”를 사용한다.

4 C++ DLL 안에 있는 함수를 "내보내기(export) 되도록" 표시하는 방법

헤더 파일을 include 한 다음에는 DLL에 있는 함수들이 내보내기(export) 되도록 표기하는 매크로를 정의하는 코드를 작성한다.

#define EXPORT comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)

다음으로, C++ 오브젝트들을 캐시하는 IndexAllocator 클래스를 구현한다. 이 클래스에는 포인터 배열이 들어 간다. 이 클래스는 "Allocate", "Release" 및 "Get"라는 세 가지 함수가 있어서 포인터를 캐시에 저장하고, 캐시를 해제하고, 인덱스에 맞게 포인터를 가져온다.

bool Allocate(int& deviceIndex, void* obj)
bool Release(int deviceIndex)
void* Get(int deviceIndex)

그런 다음, IndexAllocator 오브젝트를 할당 및 할당 해제하는 InitializeFinalize 함수가 필요하다.

extern "C" int WINAPI Initialize()
extern "C" int WINAPI Finalize()

그런 다음, CppClass 클래스의 인스턴스를 만들고 캐시에 저장하도록 아래 함수를 작성한다.

extern "C" int WINAPI CreateCppClass (int& index)

위 구문에서, "C"를 사용하여 동일한 이름을 내보내 지도록 하고, WINAPI를 사용하여 호출 규약을 변경한다. DestroyCppClass도 이와 비슷하다. 이어서 그 뒤에 있는 코드 즉 내보내지는 주 함수인 "CppClass_setValue" 및 "CppClass_getSquare"를 살펴보자. 이 함수를 사용자가 호출하면 캐시에서 오브젝트를 가져오고 해당 함수를 호출하고 값을 받을 수 있다.

extern "C" int WINAPI CppClass_setValue(int index, int value)
{
#pragma EXPORT
	CppClass* instance = (CppClass*)GAllocator->Get(index);
	if (instance == NULL)
		return -1;
	else {
		instance->setData(value);
		return 0;
	}
}
extern "C" int WINAPI CppClass_getSquare(int index, int& value)
{
#pragma EXPORT
	CppClass* instance = (CppClass*)GAllocator->Get(index);
	if (instance == NULL)
		return -1;
	else {
		value = instance->getSquare();
		return 0;
	}
}

첫 번째 함수는 오브젝트의 인덱스와 값을 받아서 해당 오브젝트의 변수 값을 설정한다. 두 번째 함수는 인덱스를 받아서 그 인덱스에 해당하는 오브젝트의 "getSquare" 함수를 호출하고 결과 값을 value 변수에 담는다.

5 델파이 애플리케이션에서 이 C++ 프록시 DLL을 사용하는 방법은?

DLL은 정적(static) 또는 동적(dynamic)으로 연결할 수 있다. 정적 로딩을 사용하면 응용 프로그램이 시작될 때 DLL이 로드된다. 동적 로딩을 사용하면 "LoadLibrary"를 호출할 때까지 DLL이 로드되지 않는다. 이 예제에서는 정적 로딩을 사용하겠다. DLL에서 내보내진 함수들을 선언(declare)하자.

const
CPP_CLASS_LIB = 'DllLib1.dll';
function Initialize: integer;
  stdcall; external CPP_CLASS_LIB name 'Initialize' delayed;
 
function Finalize: integer;
  stdcall; external CPP_CLASS_LIB name 'Finalize' delayed;
 
function CreateCppClass(var index: integer): integer;
  stdcall; external CPP_CLASS_LIB name 'CreateCppClass' delayed;
 
function DestroyCppClass(index: integer): integer;
  stdcall; external CPP_CLASS_LIB name 'DestroyCppClass' delayed;
 
function CppClass_setValue(index: integer; value: integer): integer;
  stdcall; external CPP_CLASS_LIB name 'CppClass_setValue' delayed;
 
function CppClass_getSquare(index: integer; var value: integer): integer;
  stdcall; external CPP_CLASS_LIB name 'CppClass_getSquare' delayed;

우리가 만든 프록시 DLL을 사용하기 위해 필요한 함수들이다. 델파이 애플리케이션이 생성될 때 “Initialize” 함수를 호출하고 애플리케이션이 파괴될 때 “Finalize” 함수를 호출해야 한다. 프록시 DLL을 델파이 코드에서 사용할 때는, 먼저 "CreateCppClass"를 호출하여 객체를 생성해야 한다. 그러면 해당 클래스 ID가 설정되어 사용할 수 있게 된다. 그리고 나면, 우리는 이 DLL의 모든 함수를 호출할 수 있다. 마지막에는 반드시 "DestroyCppClass" 함수를 호출하여 클래스 인스턴스를 파괴해야 한다. 예를 들면 델파이에서 다음과 같이 사용할 수 있다.

procedure TfrmCppClassDemo.btnImportLibClick(Sender: TObject);
var
 idxClass: Integer;
 value: Integer;
begin
 if CreateCppClass(idxClass) <> 0 then
  ListBox1.Items.Add('CreateCppClass 실패함')
 else if CppClass_setValue(idxClass, SpinEdit1.Value) <> 0 then
  ListBox1.Items.Add('CppClass_setValue 실패함')
 else if CppClass_getSquare(idxClass, value) <> 0 then
  ListBox1.Items.Add('CppClass_getSquare 실패함')
 else begin
  ListBox1.Items.Add(Format('square(%d) = %d', [SpinEdit1.Value, value]));
  if DestroyCppClass(idxClass) <> 0 then
   ListBox1.Items.Add('DestroyCppClass 실패함')
 end;
end;

위 과정을 마치면, 우리의 델파이 애플리케이션은 C++ DLL을 사용하여 작동한다. 아래 그림과 같다.

spacer.png

이 댓글 링크
다른 사이트에 공유하기

이 토의에 참여하세요

지금 바로 의견을 남길 수 있습니다. 그리고 나서 가입해도 됩니다. 이미 회원이라면, 지금 로그인하고 본인 계정으로 의견을 남기세요.

Guest
이 토픽(기고/질문)에 답하기

×   서식있는 텍스트로 붙여넣기.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   이전에 작성한 콘텐츠가 복원되었습니다..   편집창 비우기

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

중요한 정보

이용약관 개인정보보호정책 이용규칙 이 사이트가 더 잘 작동하기 위해 방문자의 컴퓨터에 쿠키가 배치됩니다. 쿠키 설정 변경에서 원하는 설정을 할 수 있습니다. 변경하지 않으면 쿠키를 허용하는 것으로 이해합니다.