home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

C言語系/memos/VC++/05, モジュール定義ファイル(".DEF")とDLLのエクスポート

C言語系/memos/VC++/05, モジュール定義ファイル(".DEF")とDLLのエクスポート

C言語系 / memos / VC++ / 05, モジュール定義ファイル(".DEF")とDLLのエクスポート
id: 668 所有者: msakamoto-sf    作成日: 2010-06-02 14:26:32
カテゴリ: C言語 Windows 

DLL開発時に"__declspec(dllexport)"を使ってシンボルをエクスポートする例は C言語系/memos/VC++/04, Win32のEXE,LIB,DLL開発入門(C言語) 参照。
本記事では".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.

DLL側

DLL側は実行コード本体である "dll_lib.c" と、モジュール定義ファイル "dll_lib.def" の二本立てになる。

dll_lib.c

"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;
}

dll_lib.def

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)には含まれない。

コンパイル&リンク, dumpbinで確認

> 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の出力より、以下を確認出来る。

  • 序数が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:

#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));


プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2010-06-02 15:17:53
md5:5c76218a6fbe51ab6cd7f56eb07b5023
sha1:06b3112b6a3db290f6b07d6ce45bb88d42b0055a
コメント
コメントを投稿するにはログインして下さい。