#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のトップページをご覧ください。