#navi_header|C言語系| DLLにはエクスポートするシンボルの実体を別のDLLに転送(forward)する機能がある。例えばWindowsXP(SP3)上のKERNEL32.DLLのエクスポート情報を表示してみると、NTDLL.DLLへ転送されているシンボルがいくつか見つかる。 > dumpbin /exports c:\WINDOWS\System32\kernel32.dll ... 708 2C3 RtlCaptureContext (forwarded to NTDLL.RtlCaptureContext) 709 2C4 RtlCaptureStackBackTrace (forwarded to NTDLL.RtlCaptureStackBackTrace) 710 2C5 RtlFillMemory (forwarded to NTDLL.RtlFillMemory) 711 2C6 RtlMoveMemory (forwarded to NTDLL.RtlMoveMemory) 712 2C7 RtlUnwind (forwarded to NTDLL.RtlUnwind) 713 2C8 RtlZeroMemory (forwarded to NTDLL.RtlZeroMemory) ... forwardingによるDLLの階層化やインポートライブラリの集約は、ライブラリやソースの構成・依存関係の整理に役立つだろう。 今回はモジュール定義ファイル(.def)を使ったDLLのforwardingを試してみる。 参考: - "アレ用の何か"より: -- DLL のエクスポート転送で遊んでみる --- http://hp.vector.co.jp/authors/VA050396/tech_09.html - "The Old New Thing"より: -- Index to the series on DLL imports and exports --- http://blogs.msdn.com/b/oldnewthing/archive/2006/07/27/680250.aspx -- Exported functions that are really forwarders --- http://blogs.msdn.com/b/oldnewthing/archive/2006/07/19/671238.aspx -- DLL forwarding is not the same as delay-loading --- http://blogs.msdn.com/b/oldnewthing/archive/2008/02/04/7439592.aspx 対象:Visual C++ 2008 Express Edition > cl Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 15.00.30729.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. > link Microsoft (R) Incremental Linker Version 9.00.30729.01 Copyright (C) Microsoft Corporation. All rights reserved. なお動作確認は Windows XP SP3 上で行っている。 #more|| #outline|| ---- * DLL側のサンプルコード dll01.c と dll02.c, dll01.c のモジュール定義ファイル dll01.def の3本となる。 ** dll01.c, dll01.def dll01.cは"funcA1()"の実体を提供する。 dll01.c: #code|c|> #include int funcA1(int a, int b) { OutputDebugString("funcA1() start"); return a + b; } BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpvReserved) { switch (dwReason) { case DLL_PROCESS_ATTACH: OutputDebugString("dll01, DllMain() : DLL_PROCESS_ATTACH"); break; case DLL_THREAD_ATTACH: OutputDebugString("dll01, DllMain() : DLL_THREAD_ATTACH"); break; case DLL_THREAD_DETACH: OutputDebugString("dll01, DllMain() : DLL_THREAD_DETACH"); break; case DLL_PROCESS_DETACH: OutputDebugString("dll01, DllMain() : DLL_PROCESS_DETACH"); break; default: break; } return TRUE; } ||< dll01.defで"funcA1()"をエクスポートする。また、"funcB1()"という名前でdll02.cの"funcB2()"をエクスポートする。これにより、"funcB1()"を呼ぶ場合はdll02.cの"funcB2()"に転送される。 dll01.def: #pre||> LIBRARY dll01 EXPORTS funcA1 funcB1 = dll02.funcB2 ||< ** dll02.c dll02.c側では"funcB2()"の実体を実装する。 dll02.c: #code|c|> #include int __declspec(dllexport) funcB2(int a, int b) { OutputDebugString("funcB2() start"); return a * b; } BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpvReserved) { switch (dwReason) { case DLL_PROCESS_ATTACH: OutputDebugString("dll02, DllMain() : DLL_PROCESS_ATTACH"); break; case DLL_THREAD_ATTACH: OutputDebugString("dll02, DllMain() : DLL_THREAD_ATTACH"); break; case DLL_THREAD_DETACH: OutputDebugString("dll02, DllMain() : DLL_THREAD_DETACH"); break; case DLL_PROCESS_DETACH: OutputDebugString("dll02, DllMain() : DLL_PROCESS_DETACH"); break; default: break; } return TRUE; } ||< ** dll01.dll, dll02.dll のビルド 最初にdll02.dllをビルドする: > cl /c dll02.c > link /dll dll02.obj 次にdll01.dllをビルドする: >cl /c dll01.c >link /dll /def:dll01.def dll01.obj ... dll01.def : error LNK2001: 外部シンボル "funcB2" は未解決です。 dll01.lib : fatal error LNK1120: 外部参照 1 が未解決です。 dll02側のシンボルを解決出来なかったので、dll02.lib(インポートライブラリ)を追加する: > link /dll /def:dll01.def dll01.obj dll02.lib これでdll01.dll, dll02.dllがビルド出来た。 実際にdumpbinでエクスポート情報を確認してみる。 まずdll02.dllのエクスポート情報を見てみる: #pre||> > dumpbin /exports dll02.dll ... 00000000 characteristics 4C0715B5 time date stamp Thu Jun 03 11:38:45 2010 0.00 version 1 ordinal base 1 number of functions 1 number of names ordinal hint RVA name 1 0 00001000 funcB2 ... ||< "funcB2"がエクスポートされていることを確認出来る。 続いてdll01.dllのエクスポート情報を見てみる: #pre||> > dumpbin /exports dll01.dll ... 00000000 characteristics 4C0715D2 time date stamp Thu Jun 03 11:39:14 2010 0.00 version 1 ordinal base 2 number of functions 2 number of names ordinal hint RVA name 1 0 00001000 funcA1 2 1 funcB1 (forwarded to dll02.funcB2) ... ||< "funcA1()"がエクスポートされていると同時に、"funcB1()"がdll02(.dll)の"funcB2()"に転送されるようになっているのが確認出来る。 * EXE側のサンプルコード 暗黙的リンク(implicit-link)と明示的リンク(explicit-link)で2種類のexeを作る。 後ほどデバッガでdll02.dllのロードタイミングを比べてみる。 ** 暗黙的リンク(implicit-link)する main_implicit.c とビルド main_implicit.c: #code|c|> #include __declspec(dllimport) int funcA1(int a, int b); __declspec(dllimport) int funcB1(int a, int b); int main() { printf("funcA1(2, 3) = %d\n", funcA1(2, 3)); printf("funcB1(2, 3) = %d\n", funcB1(2, 3)); return 0; } ||< コンパイル時は dll01.lib を指定するだけでよい。dll02.libは不要。 > cl main_implicit.c dll01.lib インポート情報を見てみると"dll01.dll"として"funcA1", "funcB1"がインポートされることが確認出来る: #pre||> > dumpbin /imports main_implicit.exe ... dll01.dll 40A110 Import Address Table 40B8E0 Import Name Table 0 time date stamp 0 Index of first forwarder reference 0 funcA1 1 funcB1 ... ||< 実行: > main_implicit.exe funcA1(2, 3) = 5 funcB1(2, 3) = 6 ** 明示的リンク(explicit-link)する main_explicit.c とビルド main_explicit.c: #code|c|> #include #include typedef int (CALLBACK* DLLFUNC)(int, int); int main() { HINSTANCE hDll; DLLFUNC funcA1 = NULL; DLLFUNC funcB1 = NULL; hDll = LoadLibrary("dll01"); if (NULL == hDll) { printf("dll load error.\n"); return 1; } funcA1 = (DLLFUNC)GetProcAddress(hDll, "funcA1"); if (!funcA1) { printf("GetProcAddress error(funcA1).\n"); FreeLibrary(hDll); return 2; } funcB1 = (DLLFUNC)GetProcAddress(hDll, "funcB1"); if (!funcB1) { printf("GetProcAddress error(funcB1).\n"); FreeLibrary(hDll); return 3; } printf("funcA1(2, 3) = %d\n", funcA1(2, 3)); printf("funcB1(2, 3) = %d\n", funcB1(2, 3)); FreeLibrary(hDll); return 0; } ||< ビルドではインポートライブラリ不要: > cl main_explicit.c インポート情報を見てみるとKERNEL32だけが登録されていることが確認出来る: #pre||> > dumpbin /imports main_explicit.exe ... Dump of file main_explicit.exe File Type: EXECUTABLE IMAGE Section contains the following imports: KERNEL32.dll 40A000 Import Address Table 40B7AC Import Name Table 0 time date stamp 0 Index of first forwarder reference 14C FreeLibrary ... ||< 実行: > main_explicit.exe funcA1(2, 3) = 5 funcB1(2, 3) = 6 * 転送先のDLLのロードタイミング dll01.dll, dll02.dllとも、DllMain()にOutputDebugString()を組み込んでいる。デバッガ上でデバッグログなどを確認すれば、どのタイミングでDllMain()が呼ばれた、つまりロードされたかを確認出来る。 暗黙的リンク、明示的リンクでどのように転送先のDLL、今回ならdll02.dllがロードされるのかを比べてみる。 デバッガにはOllyDbgを使用している。 : 暗黙的リンクの場合 : 暗黙的リンクを使ったmain_implicit.exeの場合、EXEがロードされて実行準備が整った段階で、dll01.dll, dll02.dllともにロードされた。 : 明示的リンクの場合 : #block||> main_explicit.exeの場合、dll01.dllは hDll = LoadLibrary("dll01"); の段階でロードされ、DllMain()が実行される。 dll02.dllは funcB1 = (DLLFUNC)GetProcAddress(hDll, "funcB1"); の段階でロードされ、DllMain()が実行される。恐らく"funcB1"のエクスポートシンボルを解決する時にdll02.dllへの転送情報を見つけ、自動的にdll02.dllをロードしているものと思われる。 ||< なお、当然であるが funcA1 = (DLLFUNC)GetProcAddress(hDll, "funcA1"); は"dll01.dll"のメモリ空間上のアドレスを返し、 funcB1 = (DLLFUNC)GetProcAddress(hDll, "funcB1"); は"dll02.dll"のメモリ空間上のアドレスを返す。 "dll01.dll"上にdll02.dll, funcB2を呼ぶようなラッパコードが生成され、そのアドレスを返す訳ではない。 * おまけ dll01.cのバリエーションとして、以下のようなdll03.cを用意してみた: #code|c|> #include int funcA1(int a, int b) { OutputDebugString("funcA1() start"); return a + b; } int funcB1(int a, int b) { OutputDebugString("funcB1() start"); return a * b; } BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpvReserved) { switch (dwReason) { case DLL_PROCESS_ATTACH: OutputDebugString("dll01, DllMain() : DLL_PROCESS_ATTACH"); break; case DLL_THREAD_ATTACH: OutputDebugString("dll01, DllMain() : DLL_THREAD_ATTACH"); break; case DLL_THREAD_DETACH: OutputDebugString("dll01, DllMain() : DLL_THREAD_DETACH"); break; case DLL_PROCESS_DETACH: OutputDebugString("dll01, DllMain() : DLL_PROCESS_DETACH"); break; default: break; } return TRUE; } ||< モジュール定義(dll03.def)ファイルは次の通り: #pre||> LIBRARY dll03 EXPORTS funcA1 funcB1 = dll02.funcB2 ||< defファイルでは"funcB1"がdll02.funcB2に転送設定されるが、dll03.cの方でもfuncB1の実体がある。 どうなるか? > cl /c dll03.c > link /dll /def:dll03.def dll03.obj dll03.def : error LNK2001: 外部シンボル "funcB2" は未解決です。 dll03.lib : fatal error LNK1120: 外部参照 1 が未解決です。 ・・・どうやらモジュール定義ファイルが優先され、dll02が必要になるらしい。 #pre||> > link /dll /def:dll03.def dll03.obj dll02.lib >dumpbin /exports dll03.dll ... 00000000 characteristics 4C084189 time date stamp Fri Jun 04 08:58:01 2010 0.00 version 1 ordinal base 2 number of functions 2 number of names ordinal hint RVA name 1 0 00001000 funcA1 2 1 funcB1 (forwarded to dll02.funcB2) ||< ちなみに int funcB1(int a, int b) を"dllexport"付で int __declspec(dllexport) funcB1(int a, int b) としてみても、リンクの段階でdll02側に転送される。モジュール定義ファイルが優先される(らしい)。 以上、おまけでした。 #navi_footer|C言語系|