#pragma twice

KAB-studio > プログラミング > #pragma twice > 315 Version 15.15 さまざまなエクスポート

#pragma twice 315 Version 15.15 さまざまなエクスポート

前のページへ 表紙・目次へ 次のページへ

 Version 15.15
さまざまなエクスポート

「前回は自分で作った DLL の関数を動的に呼び出してみました」
『っていうかあれあり得ない!! なんであんな変な文字列使うの!?』

    // DLLTestEasy.dll の fnDLLTestEasy() 関数の関数ポインタを取得。
    type_pfnDLLTestEasy pfnDLLTestEasy 
        = (type_pfnDLLTestEasy)GetProcAddress
            ( hInstanceDll, "?fnDLLTestEasy@@YAHXZ" );

『 Version 15.13 ( No.313 ) の時の MessageBox() 呼んだときは』

    // user32.dll の MessageBoxA() 関数の関数ポインタを取得します。
    type_pfnMessageBox pfnMessageBox
        = (type_pfnMessageBox)GetProcAddress
            ( hInstanceDll, "MessageBoxA" );

『って、普通の関数名だったのに』
「実は、これは関数のエクスポートの仕方が異なるからなんです」
『エクスポートっていくつもあるの?』
「大まかに分けてふたつの方法があるんです」

・__declspec(dllexport)を使用する
・.defファイルを使用する

『上のが Version 15.10 ( No.310 ) とかでやった方法だよね。でも下のは
教わってない……ってことは』
「そう、下の方法でエクスポートすれば、普通の関数名になるんです」
『それやってみよ、それ!』
「というわけでしてみましょう。使うのは DLLTestEasy プロジェクト。ま
ずはソースファイルの修正から」

// DLLTestEasy.cpp : DLL アプリケーション用のエントリ ポイントを定義
// します。
//

// 略

// これはエクスポートされた関数の例です。
int fnDLLTestEasy(void)
{
    OutputDebugString( "fnDLLTestEasy()\n" );
    return 42;
}

// 略

『どこが違うの?』
「前はこうしてました」

DLLTESTEASY_API int fnDLLTestEasy(void)

『あ、 DLLTESTEASY_API を取ったんだ』
「このマクロが __declspec(dllexport) に置き換わってエクスポートして
いたからね。同様にヘッダーファイルも修正します」

// DLLTestEasy.h

// 略

int fnDLLTestEasy(void);

『ヘッダーファイルの方も取っちゃうわけね』
「これで __declspec(dllexport) によるエクスポートがされなくなりまし
た。代わりに .def ファイルでエクスポートします。まずメニューの
【ファイル】-【新規作成】で【ファイル】のページを開いて」
『ほい』
「【テキスト ファイル】を選択して、ファイル名を〈DLLTestEasy.def〉に
してOK押して」
『ほい。ファイル作られて、開いたよ』
「そこに、次のように書いてください」

; DLLTestEasy.def

LIBRARY      "DLLTestEasy"

EXPORTS
    fnDLLTestEasy @1

『……なんか、今まで見てきた感じと全然違う……』
「ファイルの形式が全然違うからね。この拡張子が def のファイルは
【モジュール定義ファイル】といって、エクスポートする関数を指定するた
めのファイルです」
『こんなものがあったんだ……』
「実際、 Visual C++ に限らず、プログラミングには様々なツールがあっ
て、そのツールごとにファイル形式が違うから」
『全部憶えろと?』
「っていうのは無理だから、ある程度のものだけ憶えて、それをなんとなく
使い回せる、っていうのがいいかな」
『それはそれで難しそうね……で、このファイルの説明』
「まず、【;】があったら、その行の;以降はコメントになります」
『 // と同じってことだよね』
「そういうこと。次に【LIBRARY】。これは DLL ファイルの指定。ただ、拡
張子はつけません」
『 DLL のファイル名が DLLTestEasy.dll だから "DLLTestEasy" ね』
「最後に【EXPORTS】。このあとの行で、エクスポートする関数の指定をしま
す。構文はこう」

    関数名 @序数

『じょすう?』
「ようするに関数に付ける番号。エクスポートする関数に番号をつけるんで
す。複数の関数をエクスポートする時には当然それぞれ別の数字を付けま
す」
『質問!』
「はい火美ちゃん」
『関数増えたら大変じゃない?』
「大変です。エクスポートする関数全部こうやって指定しなきゃいけないか
ら、関数が多い場合や、プログラムを作ってる途中で関数の増減が激しい場
合には向いていません」
『ならなんでこんな方法必要なの!?』
「だって動的にリンクするときには関数名が」
『あ……』
「そう、実際の所、普通に呼び出すのであれば __declspec(dllexport) が
最適な方法なんです。 __declspec(dllexport) なら関数の前に付けるだけ
だから」
『確かに……』
「ただ、 __declspec(dllexport) の方法だとエクスポートされる関数名、
正式には【装飾名】って言うんだけど、その装飾名が複雑な文字列になりま
す」
『ってことは、このモジュール定義ファイル使うと普通の文字列になるん
だ』
「うん、これでOKだからリビルドしてみて」
『ほい』
「完成した DLLTestEasy.dll を BuildTest.exe と同じフォルダにコピー」
『前のが残ってるから上書きコピーね』
「で、呼び出す側、 BuildTest.cpp の修正」

// Main.cpp
#include <Windows.h>

// 略
    
    // DLLTestEasy.dll の fnDLLTestEasy() 関数の関数ポインタを取得。
    type_pfnDLLTestEasy pfnDLLTestEasy 
        = (type_pfnDLLTestEasy)GetProcAddress
            ( hInstanceDll, "fnDLLTestEasy" );    // ←この関数名。

// 略

「一箇所、 GetProcAddress() で指定する関数名」

    // DLLTestEasy.dll の fnDLLTestEasy() 関数の関数ポインタを取得。
    type_pfnDLLTestEasy pfnDLLTestEasy 
        = (type_pfnDLLTestEasy)GetProcAddress
            ( hInstanceDll, "fnDLLTestEasy" );    // ←この関数名。

『お、関数名がへんてこじゃなくなった。ビルドして実行! うん、
〈fnDLLTestEasy()〉って出た』
「このように、モジュール定義ファイルを使えば、エクスポート時の装飾名
がきれいになります」
『質問! じゃあなんで __declspec(dllexport) はへんてこなの?』
「装飾名を見比べてみようか」

・__declspec(dllexport)  : ?fnDLLTestEasy@@YAHXZ
・モジュール定義ファイル : fnDLLTestEasy

『一番大きいのは @@YAHXZ って謎なのだよね』
「そう、そこが一番重要な点。そもそもの問題点として、関数は、関数名だ
け分かっていればいい、というわけじゃないというところ」
『???』
「つまり、 "fnDLLTestEasy" っていう関数名だけじゃ、情報量が足りない
んです。ほら、同じ関数名だけど引数が違う関数を作ることができるで
しょ」
『あ、オーバーロード!!』
「そう、 Version 11.19 ( No.219 ) でやったでしょ。引数さえ違えば、同
名の関数をたくさん作ることができるんです」
「そっか……関数のオーバーロードをして、同じ名前の関数がいっぱいあっ
たら、 "fnDLLTestEasy" って名前だけじゃどの関数かわからない……」
「実際にそれを試してみます。 DLLTestEasy.cpp に次の関数を追加して」

int fnDLLTestEasy(int i)
{
    OutputDebugString( "fnDLLTestEasy()\n" );
    return 42;
}

『あ! 関数名同じ、でも引数が違う』
「これをビルドすると、こんなリンクエラーが発生します」

\DLLTestEasy.def : 
 warning LNK4022: シンボル "fnDLLTestEasy" の unique match が
 見つかりません
.\DLLTestEasy.def : 
 warning LNK4002: "int __cdecl fnDLLTestEasy(int)" 
 (?fnDLLTestEasy@@YAHH@Z) は .\Debug\DLLTestEasy.obj 
 で定義されています
.\DLLTestEasy.def : 
 warning LNK4002: "int __cdecl fnDLLTestEasy(void)" 
 (?fnDLLTestEasy@@YAHXZ) は .\Debug\DLLTestEasy.obj 
 で定義されています
LINK : 
 fatal error LNK1152: 
 1 つ以上の装飾されていないシンボルを解決できません。
LINK : 
 fatal error LNK1141: 
 エクスポート ファイルのビルド中に障害が起こりました。

『うわ出まくり。あ、なんか重複してるっぽいね』
「このように、モジュール定義ファイルでは関数のオーバーロードに対応で
きません」
『でも __declspec(dllexport) ならできる、と』
「実際、 "?fnDLLTestEasy@@YAHXZ" の最後の "YAHXZ" の箇所は、引数の型
や数を示してるんです」
『これが!?』
「だから __declspec(dllexport) の方が一般的なんです」

/*
    Preview Next Story!
*/
『私は定義ファイルの方が好きだなー』
「装飾名がきれいだから?」
『そ。つかあの変な文字列嫌い!』
「でも __declspec(dllexport) の方が一般的かな?」
『そなの?』
「というわけで次回」
< Version 15.16 クラスをエクスポートする! >
『につづく!』
「実はクラスのエクスポートは __declspec(dllexport) が基本」
『げげげ』
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。