현재까지 알려진 바에 의하면, C++빌더와 VC++은 표준 DLL 내의 export된 함수를 통해 기능을 공유해야 합니다.
이는 델파이에서 VC++의 기능을 쓸 때 또는 그 반대의 경우, 그 외 다른 언어간의 기능 제공과 사용은
표준 DLL의 export된 함수를 통하는 방법이 유일하다고 알려져 있습니다.
(물론 COM을 쓰는 방법이 있지만 이는 COM이라는 공식화된 다른 방법이므로 여기서는 논외로 합니다)
하지만 객체지향적인 프로그래밍에 익숙한 C++프로그래머에겐
오직 export 된 함수에 의지해야 한다는 사실이 매우 불만스럽기만 합니다.
델파이와 C++빌더 사이에는 같은 회사가 제작한 덕에 이러한 장벽을 넘어 클래스도 공유 될 수 있습니다.
이전에는 델파이에서 제작한 클래스를 빌더 에서 일방적으로 가져다 쓰는 것만 가능했는데,
이전 저의 강좌를 통해 C++ 클래스도 추상클래스를 통해 델파이가 가져다 쓸수 있음이 알려 졌습니다.
그러면 C++빌더로 제작된 DLL 내의 클래스를 VC++에서 사용할 수는 없을까요?
또는 반대로 VC++로 제작된 DLL 내의 클래스를 C++빌더 또는 델파이에서 사용할 수는 없을까요?
혹시 이러한 사용이 가능하지 않을까 해서 저는 몇 해전에 시도를 해 보았습니다.
자료가 없어 혼자 실험 하다가 안되니 포기를 한 것입니다.
하지만 현실적으로 C++빌더에서 만든 클래스를 VC++에서 사용 가능하다면 꼭 함수를 export 해야만
하는 수고를 덜 수 있을 뿐 아니라, 그야말로 OOP적인 기능과 데이타의 교환을 가능케 할 것입니다.
이는 C++빌더와 VC++에서 그치는 것이 아니라, 델파이에서 VC++의 클래스를 쓰는 것이 가능해지는 것이라
아직도 수 많은 델피언들이 VC++ DLL을 export 된 함수에만 의지해 사용하는 현실을 생각해 볼 때
그 가치는 매우 크다 하겠습니다.
그래서 며칠 다시 시도 해봤습니다.
결론은 조건은 있지만 가능하다 입니다.
간단히 요약하자면
방법은 추상클래스를 만들어 공유하되 멤버함수에 대한 호출 규약을 지켜주면 됩니다.
일단 공유할 클래스를 추상클래스로 뽑아 내야 합니다. 또는 처음부터 추상클래스를 작성해도 됩니다.
추상 클래스내에는 멤버 변수와 멤버 함수가 있으면 됩니다. 당연한 것을... ㅡㅡ;
이때 멤버 함수는 추상클래스가 될 수 있게 전부 virtual 함수원형 = 0; 식으로
순수 가상 함수로 작성해야 합니다. 그래야 클래스의 프로토타입만 컴파일러에게 인식 시킬 수 있으닌까요.
이때 순수 추상클래스이므로 생성자 소멸자는 없어야 합니다. 문법상 만들 수도 없지만.
생성자 소멸자는 실제 구현 클래스에 만듭니다.
그리고 함수 호출규약을 맞춰야 하는데 모든 함수는 __stdcall 로 작성합니다.
왜냐하면 함수 호출규약을 쓰지 않거나 __fastcall 을 쓰는 경우 이에 대한 처리가
빌더와 VC++이 서로 호환되지 않습니다. 즉 서로 레지스터와 스택 포인트 처리에 약간 차이를 보입니다.
아주 자세하게는 저도 조사를 하지 않았지만, 아무튼 두 컴파일러 간에는 호환이 되지 않습니다.
단지 __stdcall 호출 규약에 대해서는 동일하게 처리 됩니다.
C++빌더와 델파이는 함수호출규약이 정확하게 일치하므로 같은 규약을 쓰기만 하면 됩니다.
델파이는 클래스 조상을 명시하지 않아도 TObject를 상속 받게 되는데, VC++에는 TObject 클래스가 없으므로,
이런 제약에 대해서는 인식을 하고 있어야 합니다.
물론 첨부한 델파이 예제와 같이 당장은 TObject를 신경쓰지 않고 없는 것처럼 처리해도 됩니다만,
나중에 혹 TObject를 고려해야할 상황이 생길 지도 모르닌까요.
맞춰 줄 것에 대한 설명은 이게 다 입니다.
이전 저의 강좌에서 설명한 바와 같이 델파이 유닛에서 C++빌더 클래스 호출시는 클래스 추상화만 하면 됐지만,
VC++에서 C++빌더 클래스 호출시는 클래스 추상화와 멤버함수 호출규약을 __stdcall 로 맞춰 주어야 합니다.
또 하나 신경 써야 할 것은 생성과 소멸 문제인데 이것은 아래에서 설명하겠습니다.
이전에 저는 빌더와 VC++이 클래스를 공유하지 못하는 문제에 가상함수테이블(VTable)의 처리의 상이성도
작용하지 않나 하고 생각했었는데, 오늘 자세하게 메모리 번지를 일일이 비교해서 살펴보니
VTable 은 동일하게 처리하더군요. 그래서 빌더와 VC++간에 클래스가 공유 될수 있겠다는 생각을 한 것입니다.
하긴 당연히 처리가 같아야 하는데... 이전에 왜 그렇게 생각했는지 개구리가 올챙이적 기억을 못하는 것 마냥 갸우뚱 거리게 되더군요.
추상 클래스를 상속받아 구현클래스를 만들게 되면 VTable 포인트 4바이트가 클래스 처음 위치에 나타나게 됩니다.
이건 클래스의 일반 속성이고 빌더와 VC++ 그리고 델파이가 동일합니다.
VTable 포인트가 가르키는 번지를 보면 실제 함수를 가르키는 4바이트 포인트가 가상함수 갯수 만큼 있게 됩니다.
이건 COM을 할때도 반드시 알아야 할 사항이고, 고급 프로그래밍을 할 수록
계속 부딪히는 문제이므로 명확하게 이해를 하는 것이 좋습니다.
자.. 그러면 실제 사용을 위해,
VC++ 응용프로그램에서 클래스의 생성과 사용 그리고 소멸은 어떻게 하는가 입니다.
VC++에서 알수 있는 것은 추상클래스 뿐입니다. 추상 클래스는 아시다시피 인스턴스를 만들 수 없습니다.
이미 빌더로 만든 DLL내에 구현 클래스가 있는데 VC++ 에서 또 구현클래스를 만드는 황당한 일도 할 수는 없는 것이죠.
그래서 할수 없이 클래스 생성은 DLL내에 export 된 클래스 생성 함수를 통해 객체의 인스턴스를 만들게 됩니다.
이는 구현 클래스가 DLL내 에 있으니 할수 없는 일입니다.
DLL내에서 인스턴스를 생성했으면 소멸도 DLL내에서 시켜줘야 합니다.
이는 DLL내의 빌더 메모리 관리자와 VC++의 메모리 관리자의 상이성 때문입니다.
그래서 생성과 소멸을 책임질 함수를 DLL에서는 제공해줘야 합니다.
빌더 DLL을 빌더가 사용하는 경우는 메모리 관리자가 같으므로 생성만 하고 소멸은 직접해줘도 됩니다.
클래스의 사용은 그냥 일상적으로 클래스 멤버변수 멤버 함수 사용 하듯이 사용하면 됩니다.
추가적으로 한가지 더 생각해야할 것은
빌더로 만든 DLL에 export 된 함수의 호출 규약을 쓰지 않거나 __cdecl을 쓰는 경우, 함수명 앞에 _ 가 추가로 붙습니다.
이는 예전 도스용 TurboC 1.0 시절부터 이어져온 볼랜드의 관행 인데,
DLL을 로딩한후 함수를 찾을 때는 실제 함수 명 앞에 _ 를 추가해야 합니다.
이러한 문제를 피하려면
export 되는 함수는 모든 __stdcall 함수 호출 규약을 적용하면 됩니다.
혹 델파이에서 DLL을 만들어 export 시킬때도 stdcall 규약을 맞추는 것이 여러모로 편리합니다.
기사를 적다가 보닌까 이건... COM 하고 어찌 분위기가 비슷해지는 군요. 하지만 COM하고는 차이가 큽니다.
그러면 이제 방법적인 문제에 대한 설명은 다 된 것이나 다름 없습니다.
첨부한 예제는 빌더로 만든 DLL과 VC++ 응용프로그램에서 빌더 클래스를 사용하는 예제가 있습니다.
그리고 델파이로 DLL을 만드는 예제가 있는데 이는 C++빌더에서 만든 것과 동일한 것을 델파이로만 만들고,
이를 동일하게 VC++에서 클래스를 사용할 수 있음을 보여줍니다.
빌더 응용프로그램에서 사용하는 예제도 있는데 이건 있으나 마나한 것이고요.
내부에 설명은 그다지 없어도 전술한 기술적인 내용만 숙지하면 코드 이해에 어려움이 없으리라고 생각됩니다.
예제와 반대로
VC++ DLL을 빌더 응용 프로그램에서 사용하는 것 역시 같은 방법을 사용하면 되리라 봅니다.
이건 역으로도 성립할 수 밖에 없는 사항이닌까요.
또한 VC++이나 C++빌더에서 제작한 DLL을 델파이 응용 프로그램에서 추상 클래스를 통해 제어하는 것 역시 가능합니다.
이 부분에 대해서는 당연히 될 것이므로 예제는 따로 만들지 않았습니다.
VC++ 예제가 몇 해 전 만든 예제인데 클래스 사용 부분만 끼워 넣은 것이라 좀 이상하긴 해도 테스트에는 지장이 없을 것입니다.
아마도 저의 이 기사가 C++빌더 VC++ 델파이 간에 클래스를 통한 공유 방법에 대해,
처음으로 밝힌 기사가 아닌가 하는 생각이 듭니다.
이제 DLL에 함수만 나열하는 번거로움은 사라지고, OOP 를 유지하는 공유가 가능할 것입니다.
사실 객체지향적인 프로그래밍에 익숙해지면 클래스를 쓰는 것과 C 스타일로 함수만 쓰는 것은
프로그래밍 시 매우 큰 차이가 있습니다. C++를 하다가 C 할때의 답답함이란 매우 큰 것이죠.
클래스 공유시, C++빌더 또는 델파이와 VC++간에는 메모리 관리자가 틀리다는 사실은 꼭 유의해야 합니다.
stdcall 에 대한 컴파일러 수준에서의 처리는 오랫동안 변함이 없었고 앞으로도 변하지 않으리라 생각됩니다.
이는 필자가 말한 공유 방법은 컴파일러의 정규적인 기능으로 처리되는 것이며, 앞으로도 그 규약의 변화 가능성이 없다는 뜻 입니다.
이렇게 생각할 수 밖에 없는 이유는 COM interface 때문입니다.
COM interface는 C++의 경우는 순수 가상 클래스를 통해 구현합니다. 다만 멤버변수를 사용하지 않고,
모든 멤버함수는 stdcall 함수 호출 규약을 따릅니다. 실지로 C++에서는 interface라는 별도의 키워드가 없고
struct 를 재정의하여 interface라는 식별자로 사용함으로써 interface를 구현하고 있습니다.
다시 말해 이는 위에서 설명한 클래스 공유를 위한 순수 가상 클래스에서 멤버변수만 없는 상태와 거의 같은 것입니다.
델파이의 경우는 interface라는 키워드를 지원하며 interface에서는 멤버변수를 쓸수 없습니다.
하지만 예제와 같이 순수 가상 클래스를 정의해 클래스를 공유해서 사용하는 것이 가능한 이유는,
원래 델파이 클래스의 기본 구조를 C++과 거의 같게 디자인 했고, COM interface를 지원하기 위해
stdcall 호출에 따른 레지스터 값이나 스택의 값이 혼란이 일어나지 않도록 처리되어 있기 때문입니다.
interface의 경우는 무조건 IUnknown 의 자손인데, 공유를 위한 순수 가상 클래스의 경우는
누구의 자손이 될 필요도 없으며, 필요하면 상속을 받는 형태로 만들어도 상관 없습니다.
COM interface는 클래스의 서브셋이며, 클래스는 interface의 슈퍼셋입니다.
COM interface가 성립하기 위한 조건을 클래스 공유를 위한 방법에 쓰는 것이나 다름 없기 때문에,
지금까지 한번도 C++빌더,델파이,VC++의 클래스 공유가 공식화 된 적이 없지만,
자연히 호환을 가질 수 밖에 없으며 앞으로도 변치 않는 사항이 되는 것입니다.
멤버 변수의 처리 역시 기재된 순서대로 처리하는 것이 세 컴파일러 공통된 사항이므로 역시 호환을 가질 수 밖에 없습니다.
널리 알려져 있다 시피 COM 은 규약이 변할 수가 없는 구조입니다.
그러므로 공유 클래스는 안심하고 사용할 수 있다고 생각됩니다.
추가적으로 좀 더 간편하게 쓰기 위한 팁으로,
클래스 생성 함수를 호출할 때 클래스 생성에 필요한 인자를 건재주는 식이 되는 것이 좋고,
클래스 소멸 함수는 예제처럼 따로 제공하지 말고, 그냥 클래스에
Free 와 같은 클래스 소멸 함수를 따로 제공하는 것이 더 간편합니다.
이렇게 되면 DLL에서 export 하는 함수는, 클래스 생성 함수 하나만 제공하면 된다는 것이죠.
테스트한 컴파일러는 C++빌더6와 VS 2005, 델파이7 입니다.
많은 분들이 프로그래밍하는데 도움이 되기를 바랍니다.
그럼.
이 기사는 Free 입니다. 단 ,상업적 용도로는 사용할 수 없습니다.
작성자 : 김태성 jsdkts@korea.com
|
불편한 거지요. 형변환도 많이 해야되고, 참고할 자료가 거의 없지요.
이런 글 하나하나가 많은 도움이 됩니다.
올려 주신 글에 감사드립니다.