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) が基本」
『げげげ』