C/C++ - DLL에서 template 함수를 export하는 방법
아래의 Q&A에서,
템플릿 대체할수있는방법이 있을까요?
; https://www.sysnet.pe.kr/3/0/5939
template 함수를 DLL에 export할 수 없다는 의견이 나오는데, 절반은 맞고 절반은 틀립니다. ^^ 이게 뭔 소리냐면, template 함수도 구현 코드를 cpp로 분리하는 것은 가능하지만,
// Dll1.h
#define DLL1_API __declspec(dllexport)
template<typename ObjectType> DLL1_API ObjectType* CreateObject();
// Dll1.cpp
template<typename ObjectType>
DLL1_API ObjectType* CreateObject()
{
ObjectType* NewObj = new ObjectType();
return NewObj;
}
저렇게 만들어진 DLL은 CreateObject 함수를 "__declspec(dllexport)"이 지정되었음에도 export하지는 못합니다. 그럴 수밖에 없는 것이, template에 전달되는 타입 정보는 컴파일 시점에 정해져, 같은 시점에 소스코드가 생성돼 함께 컴파일되는 것이므로 실행 시 DLL Export의 함수를 바인딩한다면 이것을 연결할 만한 mangling 이름 규칙이 없기 때문입니다.
만약 template을 위한 mangling 이름 규칙을 정의한다면, DLL은 그 스스로 함수 바인딩 시에 전달된 타입 정보를 기반으로 template 함수를 런타임 시에 생성해 내는 방법을 제공해야 합니다.
C#과 같은 VM 계열의 언어라면 런타임 측에서 그런 것을 할 수도 있지만 C/C++과 같은 native 언어는 방법이 없습니다.
이에 대한 보완으로, Visual C++의 경우 template 함수에 대해서는 타입 정보를 미리 명시적으로 지정하여 export 할 수 있게 해주는데요, 다소 원시적일 수 있지만 어쩔 수 없습니다. ^^
// Dll1.h
template<typename ObjectType> DLL1_API ObjectType* CreateObject();
class CMyClass2
{
public:
int n = 5;
const char* ptr = "Hello";
};
template DLL1_API CMyClass2* CreateObject<CMyClass2>();
template DLL1_API int* CreateObject<int>();
저렇게라도 하면 Visual C++은 명시된 타입 정보에 해당하는 template을 구체화시켜 코드를 만들어내 미리 빌드해 놓습니다. 따라서, 이제 DLL의 export 함수 테이블에는 이에 해당하는 함수가 등록되고,
c:\temp> dumpbin Dll1.dll /EXPORTS
...[생략]...
1 0 0001123A ??$CreateObject@H@@YAPEAHXZ = @ILT+565(??$CreateObject@H@@YAPEAHXZ)
2 1 00011064 ??$CreateObject@VCMyClass2@@@@YAPEAVCMyClass2@@XZ = @ILT+95(??$CreateObject@VCMyClass2@@@@YAPEAVCMyClass2@@XZ)
이렇게 export한 함수의 이름을 각각 풀어보면,
c:\temp> undname ??$CreateObject@H@@YAPEAHXZ
...[생략]...
Undecoration of :- "??$CreateObject@H@@YAPEAHXZ"
is :- "int * __ptr64 __cdecl CreateObject(void)"
c:\temp> undname ??$CreateObject@VCMyClass2@@@@YAPEAVCMyClass2@@XZ
...[생략]...
Undecoration of :- "??$CreateObject@VCMyClass2@@@@YAPEAVCMyClass2@@XZ"
is :- "class CMyClass2 * __ptr64 __cdecl CreateObject(void)"
타입별로 템플릿 함수가 export된 것을 확인할 수 있습니다. 결국, 사용 측에서는 다음과 같이 해당 함수로 바인딩할 수 있고,
#include <iostream>
#include "../Dll1/Dll1.h"
#pragma comment(lib, "../x64/Debug/Dll1.lib")
int main()
{
CMyClass2* cmc = CreateObject<CMyClass2>();
std::cout << cmc->n << " " << cmc->ptr; // 출력 결과: 5 Hello
}
만약 다른 타입을 사용하려고 하면,
unsigned int* n1 = CreateObject<unsigned int>();
DLL 측에는 "unsigned int"로 CreateObject 함수를 미리 만들어 둔 것이 없기 때문에 "unsigned int * __cdecl CreateObject<unsigned int>(void)" mangling 이름을, 즉 "(__imp_??$CreateObject@I@@YAPEAIXZ)" 함수를 찾지 못해 에러가 발생합니다.
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]