DLL開発時に"__declspec(dllexport)"を使ってシンボルをエクスポートする例は C言語系/memos/VC++/04, Win32のEXE,LIB,DLL開発入門(C言語) 参照。
本記事では".DEF"ファイルを使ってシンボルをエクスポートする機能を試してみる。
参考MSDN(Express Edition):
対象: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.
DLL側は実行コード本体である "dll_lib.c" と、モジュール定義ファイル "dll_lib.def" の二本立てになる。
"func1" - "func7"がモジュール定義ファイルによりエクスポートされる関数になる。
比較用に foo(), bar() を"__declspec(dllexport)"で自動的にエクスポートされるようにしておく。
dll_lib.c:
#include <windows.h> 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; }
func1 - func7 までを、何パターン化にわけてエクスポートする。
dll_lib.def:
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)には含まれない。
> cl /c dll_lib.c > link /dll /def:dll_lib.def dll_lib.obj
> 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の出力より、以下を確認出来る。
またインポートライブラリ "dll_lib.lib" をdumpbinで確認してみると、DEFファイルで"PRIVATE"オプションを指定したfunc4, func5, funcY(func7) がインポートライブラリに含まれていないことを確認出来る。
> dumpbin /headers dll_lib.lib (出力は省略)
dll_main.c:
#include <windows.h> #include <stdio.h> 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)" を使って宣言し、リンカにリンクしてもらう。
// (...) __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));