#navi_header|C言語系| DLL開発時に"__declspec(dllexport)"を使ってシンボルをエクスポートする例は [[587]] 参照。 本記事では".DEF"ファイルを使ってシンボルをエクスポートする機能を試してみる。 参考MSDN(Express Edition): - 「Visual C++」→「C/C++ プログラムのビルド」→「C/C++ ビルドのリファレンス 」→「リンク」→「モジュール定義 (.def) ファイル 」 対象: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. #more|| #outline|| ---- * DLL側 DLL側は実行コード本体である "dll_lib.c" と、モジュール定義ファイル "dll_lib.def" の二本立てになる。 ** dll_lib.c "func1" - "func7"がモジュール定義ファイルによりエクスポートされる関数になる。 比較用に foo(), bar() を"__declspec(dllexport)"で自動的にエクスポートされるようにしておく。 dll_lib.c: #code|c|> #include int __declspec(dllexport) foo(int a, int b) { OutputDebugString("foo() start"); return a + b; } int __declspec(dllexport) bar(int a, int b) { OutputDebugString("bar() start"); return a * b; } #define MAKE_FUNCX(N) int func##N(int a, int b) { return a + b + N; } MAKE_FUNCX(1) MAKE_FUNCX(2) MAKE_FUNCX(3) MAKE_FUNCX(4) MAKE_FUNCX(5) MAKE_FUNCX(6) MAKE_FUNCX(7) BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpvReserved) { switch (dwReason) { case DLL_PROCESS_ATTACH: OutputDebugString("DllMain() : DLL_PROCESS_ATTACH"); break; case DLL_THREAD_ATTACH: OutputDebugString("DllMain() : DLL_THREAD_ATTACH"); break; case DLL_THREAD_DETACH: OutputDebugString("DllMain() : DLL_THREAD_DETACH"); break; case DLL_PROCESS_DETACH: OutputDebugString("DllMain() : DLL_PROCESS_DETACH"); break; default: break; } return TRUE; } ||< ** dll_lib.def func1 - func7 までを、何パターン化にわけてエクスポートする。 dll_lib.def: #pre||> LIBRARY dll_lib EXPORTS func1 func2 @5 func3 @6 NONAME func4 @7 PRIVATE func5 @8 NONAME PRIVATE funcX=func6 funcY=func7 PRIVATE ||< func1: 特にオプションを指定していないので、序数はリンカ側で決定され、エクスポート名もfunc1のまま。 func2: 序数を5に手動指定。最初の序数が5なので、func1などリンカ側で決定される序数は5以降の空いている序数が使われる。エクスポート名はfunc2のまま。 func3: "NONAME"オプションを指定されているので、エクスポート名は無い。序数でのみエクスポートされる。 func4: "PRIVATE"オプションが指定されているので、インポートライブラリ(.lib)には含まれない。DLLファイルでは通常通り序数7, エクスポート名はそのままでエクスポートされる。 func5: "NONAME" + "PRIVATE"オプションが指定されているので、インポートライブラリ(.lib)には含まれず、さらにDLLファイルでも序数でのみエクスポートされる。 func6: "funcX"としてエクスポートされる。 func7: "funcY"としてエクスポートされるが、"PRIVATE"オプションが指定されるので、インポートライブラリ(.lib)には含まれない。 ** コンパイル&リンク, dumpbinで確認 > cl /c dll_lib.c > link /dll /def:dll_lib.def dll_lib.obj #pre||> > dumpbin /exports dll_lib.dll Microsoft (R) COFF/PE Dumper Version 9.00.30729.01 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file dll_lib.dll File Type: DLL Section contains the following exports for dll_lib.dll 00000000 characteristics 4C05F0F2 time date stamp Wed Jun 02 14:49:38 2010 0.00 version 5 ordinal base 9 number of functions 7 number of names ordinal hint RVA name 9 0 00001020 bar 10 1 00001000 foo 11 2 00001040 func1 5 3 00001050 func2 7 4 00001070 func4 12 5 00001090 funcX 13 6 000010A0 funcY 6 00001060 [NONAME] 8 00001080 [NONAME] Summary 2000 .data 2000 .rdata 1000 .reloc 7000 .text ||< dumpbinの出力より、以下を確認出来る。 - 序数が5から始まっている。 - func6, func7 がそれぞれ funcX, funcY でエクスポートされている。 - DEFファイルで"NONAME"を指定した func3, func5 が実際に名前無しとなり、dumpbinの出力上は"[NONAME]"となっている。 またインポートライブラリ "dll_lib.lib" をdumpbinで確認してみると、DEFファイルで"PRIVATE"オプションを指定したfunc4, func5, funcY(func7) がインポートライブラリに含まれていないことを確認出来る。 > dumpbin /headers dll_lib.lib (出力は省略) * EXE側 dll_main.c: #code|c|> #include #include typedef int (CALLBACK* DLLFUNC)(int, int); __declspec(dllimport) int foo(int a, int b); __declspec(dllimport) int bar(int a, int b); __declspec(dllimport) int func1(int a, int b); __declspec(dllimport) int func2(int a, int b); __declspec(dllimport) int func3(int a, int b); /* __declspec(dllimport) int func4(int a, int b); */ /* __declspec(dllimport) int func5(int a, int b); */ __declspec(dllimport) int funcX(int a, int b); /* func6 */ /* __declspec(dllimport) int funcY(int a, int b); */ /* func7 */ int main() { HINSTANCE hDll; DLLFUNC func4 = NULL; DLLFUNC func5 = NULL; DLLFUNC funcY = NULL; hDll = LoadLibrary("dll_lib"); if (NULL == hDll) { printf("dll load error.\n"); return 1; } printf("dll module handle = %x\n", hDll); /* func4 */ func4 = (DLLFUNC)GetProcAddress(hDll, "func4"); if (!func4) { printf("GetProcAddress error(func4).\n"); FreeLibrary(hDll); return 2; } /* func5 */ func5 = (DLLFUNC)GetProcAddress(hDll, MAKEINTRESOURCEA(8)); if (!func5) { printf("GetProcAddress error(func5).\n"); FreeLibrary(hDll); return 3; } /* funcY */ funcY = (DLLFUNC)GetProcAddress(hDll, "funcY"); if (!funcY) { printf("GetProcAddress error(funcY).\n"); FreeLibrary(hDll); return 4; } printf("foo(2, 3) = %d\n", foo(2, 3)); printf("bar(2, 3) = %d\n", bar(2, 3)); printf("func1(2, 3) = %d\n", func1(2, 3)); printf("func2(2, 3) = %d\n", func2(2, 3)); printf("func3(2, 3) = %d\n", func3(2, 3)); printf("func4(2, 3) = %d\n", func4(2, 3)); printf("func5(2, 3) = %d\n", func5(2, 3)); printf("funcX(2, 3) = %d\n", funcX(2, 3)); printf("funcY(2, 3) = %d\n", funcY(2, 3)); FreeLibrary(hDll); return 0; } ||< コンパイル&実行: > cl dll_main.c dll_lib.lib > dll_main.exe dll module handle = 10000000 foo(2, 3) = 5 bar(2, 3) = 6 func1(2, 3) = 6 func2(2, 3) = 7 func3(2, 3) = 8 func4(2, 3) = 9 func5(2, 3) = 10 funcX(2, 3) = 11 funcY(2, 3) = 12 ** 解説 func4, func5, funcY 以外はインポートライブラリに含まれているので "__declspec(dllimport)" を使って宣言し、リンカにリンクしてもらう。 #code|c|> // (...) __declspec(dllimport) int foo(int a, int b); __declspec(dllimport) int bar(int a, int b); __declspec(dllimport) int func1(int a, int b); __declspec(dllimport) int func2(int a, int b); __declspec(dllimport) int func3(int a, int b); /* __declspec(dllimport) int func4(int a, int b); */ /* __declspec(dllimport) int func5(int a, int b); */ __declspec(dllimport) int funcX(int a, int b); /* func6 */ /* __declspec(dllimport) int funcY(int a, int b); */ /* func7 */ // (...) ||< func4, funcY(func7)はエクスポート名を使ってGetProcAddress()でDLLから直接関数ポインタを取り出している: func4 = (DLLFUNC)GetProcAddress(hDll, "func4"); (...) funcY = (DLLFUNC)GetProcAddress(hDll, "funcY"); func5については序数でしかエクスポートされていない。この場合はMAKEINTRESOURCEA()マクロを使う。 func5 = (DLLFUNC)GetProcAddress(hDll, MAKEINTRESOURCEA(8)); GetProcAddress()の第二引数は本来はLPCSTRだが、上位WORDを0埋め+下位WORDに序数値を指定したDWORDでも良い。 MAKEINTRESOURCEA()マクロを使うとそのような値を作ってくれるので、これで序数によりエクスポートシンボルのアドレスを取得出来る。 エクスポート名が有る場合でも、序数値からエクスポートシンボルを取得することは可能。例えばfuncY(func7)については次のようにしても問題ない。 funcY = (DLLFUNC)GetProcAddress(hDll, MAKEINTRESOURCEA(13)); #navi_footer|C言語系|