윈도우즈계의 동적 라이브러리
from DLLs the Dynamic Way
struct PDLL { HMODULE hDLL; PDLL(LPCTSTR filename) { memset(this, 0, sizeof(PDLL)); hDLL = LoadLibrary(filename); } ~PDLL() { if (hDLL != NULL) FreeLibrary(hDLL); } }; #define DECLARE_DLL_CLASS(CLASSNAME, DLLNAME) \ public: \ CLASSNAME() : PDLL(DLLNAME) {} \ ~CLASSNAME() {} #define DECLARE_FUNCTION0(RETVAL, FNAME) \ typedef RETVAL (CALLBACK* PFN_##FNAME)(VOID); \ RETVAL FNAME(VOID) \ { \ if (hDLL == NULL) return (RETVAL)0; \ static PFN_##FNAME s_pfn##FNAME = NULL; \ if (s_pfn##FNAME == NULL) \ s_pfn##FNAME = (PFN_##FNAME)GetProcAddress(hDLL, #FNAME); \ return (s_pfn##FNAME != NULL) ?\ (s_pfn##FNAME)() : (RETVAL)0; \ } #define DECLARE_FUNCTION1(RETVAL, FNAME, P1) \ typedef RETVAL (CALLBACK* PFN_##FNAME)(P1); \ RETVAL FNAME(P1 _1) \ { \ if (hDLL == NULL) return (RETVAL)0; \ static PFN_##FNAME s_pfn##FNAME = NULL; \ if (s_pfn##FNAME == NULL) \ s_pfn##FNAME = (PFN_##FNAME)GetProcAddress(hDLL, #FNAME); \ return (s_pfn##FNAME != NULL) ?\ s_pfn##FNAME(_1) : (RETVAL)0; \ } #define DECLARE_FUNCTION2(RETVAL, FNAME, P1, P2) \ typedef RETVAL (CALLBACK* PFN_##FNAME)(P1, P2); \ RETVAL FNAME(P1 _1, P2 _2) \ { \ if (hDLL == NULL) return (RETVAL)0; \ static PFN_##FNAME s_pfn##FNAME = NULL; \ if (s_pfn##FNAME == NULL) \ s_pfn##FNAME = (PFN_##FNAME)GetProcAddress(hDLL, #FNAME); \ return (s_pfn##FNAME != NULL) ?\ s_pfn##FNAME(_1, _2) : (RETVAL)0; \ } #define DECLARE_FUNCTION3(RETVAL, FNAME, P1, P2, P3) \ typedef RETVAL (CALLBACK* PFN_##FNAME)(P1, P2, P3); \ RETVAL FNAME(P1 _1, P2 _2, P3 _3) \ { \ if (hDLL == NULL) return (RETVAL)0; \ static PFN_##FNAME s_pfn##FNAME = NULL; \ if (s_pfn##FNAME == NULL) \ s_pfn##FNAME = (PFN_##FNAME)GetProcAddress(hDLL, #FNAME); \ return (s_pfn##FNAME != NULL) ?\ s_pfn##FNAME(_1, _2, _3) : (RETVAL)0; \ } ...
#!cpp #include "PDLL.h" class DbgHelp : public PDLL { DECLARE_DLL_CLASS(DbgHelp, "dbghelp.dll") DECLARE_FUNCTION0(DWORD, SymGetOptions); DECLARE_FUNCTION3(BOOL, SymInitialize, HANDLE, PSTR, BOOL); }; class Toolhelp32 : public PDLL { DECLARE_DLL_CLASS(Toolhelp32, "tlhelp32.dll"); DECLARE_FUNCTION2(BOOL, CreateToolhelp32Snapshot, DWORD, DWORD); }; void DllTest() { DbgHelp dbghelp; Toolhelp32 toolhelp; DWORD o = dbghelp.SymGetOptions(); BOOL r1 = dbghelp.SymInitialize(NULL, NULL, FALSE); BOOL r2 = toolhelp.CreateToolhelp32Snapshot(0, 0); }
별로 편하지도 않은가…?
DLL Rebasing이란 DLL이 최초에 로드되는 기본 주소를 오버라이드해주는 일을 말한다.
이런 일이 필요한 이유는 대부분의 DLL이 같은 기본 주소(0x10000000)를 가지는 데서 기인한다. DLL을 로드했을 때, 그 DLL이 들어가야할 주소를 다른 DLL이 이미 차지하고 있는 경우, 커널이 다른 주소를 알아서 찾아주게 되는데, 이 작업이 꽤나 오버헤드를 차지한다는 것이다.
그렇다면 어떤 기준으로 DLL이 들어갈 주소를 정해줄 것인가? 업계 표준(-_-)적인 아이디어는 DLL 파일의 알파벳 명칭을 이용하는 것이다.
DLL 이름 알파벳 첫글자 | 시작 주소 |
---|---|
A-C | 0x60000000 |
D-F | 0x61000000 |
G-I | 0x62000000 |
J-L | 0x63000000 |
M-O | 0x64000000 |
P-R | 0x65000000 |
S-U | 0x66000000 |
V-X | 0x67000000 |
Y-Z | 0x68000000 |
위와 같은 식으로 기본 주소를 정하고, 해당하는 DLL들을 하나하나 매치시켜나가는 것이다. 예를 들어 A1.DLL D1.DLL, D2.DLL이 있다면…
DLL 이름 | 주소 |
---|---|
A1.DLL | 0x60000000 |
D1.DLL | 0x61000000 |
D2.DLL | 0x61100000 |
이런 식으로 주소를 정해준다. 주소는 프로젝트 세팅의 링커탭에 가면 간단히 지정해줄 수 있다. 자신이 만든 DLL이 아니라면, 비쥬얼 스튜디오나 플랫폼 SDK에 따라오는 Rebase.exe 유틸리티를 이용하기 바란다.
2003 버전의 경우는 <code>…\Microsoft Visual Studio .NET 2003\Common7\Tools\Bin</code> 디렉토리에 있다.
이 유틸리티를 사용하는 기본적인 방법은 다음과 같다.
rebase /b 0x60000000 A1.DLL rebase /b 0x61000000 D1.DLL rebase /b 0x61000000 D1.DLL D2.DLL
마지막 라인 같은 경우, 여러 개의 DLL에다 한꺼번에 주소를 주기 위한 기능인데, 주소가 너무 가까워질 우려가 있으므로 귀찮더라도 수동으로 하나씩 하기 바란다. 좀 더 추가적인 기능들은 자체 매뉴얼을 참고하기 바란다.
프로그램을 실행할 때, 윈도우즈는 실행 파일의 임포트 영역에 있는 DLL들을 전부 로드하게 된다. 문제는 이 DLL 파일이 존재하지 않는 경우, 무슨무슨 DLL이 없다고 프로그램이 실행되지도 않는다는 점이다. 해당하는 DLL이 없어도 일단 프로그램을 실행하고 난 다음, 어떤 처리를 하고 싶을 때 쓸 수 있는 것이 DllDelayLoading이라는 개념이다.