Version 15.10
エクスポートとインポート
「前回はとりあえず DLL を作って使ってみました」
『あれだけ見ると結構簡単だったんだけど……』
「あれは全部下準備ができているからね。というわけで、今回はその必要な
下準備とかについて見ていきます」
『お〜』
「ではひとつずつ見ていきます。まず DllMain() 関数から」
// DLLTestEasy.cpp
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
『うん、こういう関数あるね』
「これは DLL が読み込まれたときに呼び出される関数。 WinMain() みたい
な感じかな」
『え、これが WinMain() の代わり!?』
「一応ね」
『一応?』
「だって、 DLL が読み込まれたときに呼び出される、って言っても、その
時に必要な処理ってほとんどないから。普通は Exe から関数が呼び出され
るだけだもの」
『そっか、 Exe とは違うんだものね……』
「だから、これは無視しちゃってください」
『無視ですか』
「次に関数について」
// DLLTestEasy.cpp
// これはエクスポートされた関数の例です。
DLLTESTEASY_API int fnDLLTestEasy(void)
{
OutputDebugString( "fnDLLTestEasy()" );
return 42;
}
『これを Exe から呼んでたんだよね』
「そうです。基本的にこの関数は普通の関数と同じ。ただひとつ違うのは」
『 DLLTESTEASY_API ってゆーのが付いてる』
「そう。これはヘッダーファイルの方で定義されています」
// DLLTestEasy.h
#ifdef DLLTESTEASY_EXPORTS
#define DLLTESTEASY_API __declspec(dllexport)
#else
#define DLLTESTEASY_API __declspec(dllimport)
#endif
『????』
「 #ifdef って憶えてる?」
『マクロだよね、 #define でこれ定義してたら、ってゆーの。
Version 6.06 ( No.106 ) でやったし』
「そう、だからこれを分かりやすく書くと」
#ifdef DLLTESTEASY_EXPORTS
// もし DLLTESTEASY_EXPORTS が #define されていたらこっち。
#define DLLTESTEASY_API __declspec(dllexport)
#else
// それ以外はこっち。
#define DLLTESTEASY_API __declspec(dllimport)
#endif
「ということになります」
『ってことは……』
// DLLTESTEASY_EXPORTS が #define されている場合
__declspec(dllexport) int fnDLLTestEasy(void)
{
OutputDebugString( "fnDLLTestEasy()" );
return 42;
}
『か、』
// DLLTESTEASY_EXPORTS が #define されていない場合
__declspec(dllimport) int fnDLLTestEasy(void)
{
OutputDebugString( "fnDLLTestEasy()" );
return 42;
}
『ってことだよね。違うのは dllexport か dllimport か、ね』
「そうなります。それともうひとつ、ヘッダーファイルにも」
// DLLTestEasy.h
DLLTESTEASY_API int fnDLLTestEasy(void);
『あ、関数宣言の方だね。これも』
// DLLTESTEASY_EXPORTS が #define されている場合
__declspec(dllexport) int fnDLLTestEasy(void);
『か』
// DLLTESTEASY_EXPORTS が #define されていない場合
__declspec(dllimport) int fnDLLTestEasy(void);
『だね』
「実は、この違いは、関数宣言の方で大きな意味を持ちます」
『関数宣言の方?』
「そう。さて問題。 DLLTESTEASY_EXPORTS はどこで #define されているで
しょう」
『え……? ……あれ、ソースファイルやヘッダーファイル見てみたけどな
いよそんなの』
「そう、実は設定にあるんです」
『設定??』
「【プロジェクト】-【設定】ダイアログの【C/C++】-【一般】ページの
【プリプロセッサの定義】に」
『あ! DLLTESTEASY_EXPORTS がこんなとこに! しかも #define じゃな
いし!』
「この設定に書いてある文字列は、 #define と同じ意味を持つんです。普
通にプログラムに #define で書くよりは簡単だから」
『でも探すの大変……』
「さて、ここで DLLTESTEASY_EXPORTS が定義されているって事は?」
『んー、ってことは関数もその宣言も、』
// DLLTESTEASY_EXPORTS が #define されている場合
__declspec(dllexport) int fnDLLTestEasy(void)
{
OutputDebugString( "fnDLLTestEasy()" );
return 42;
}
// DLLTESTEASY_EXPORTS が #define されている場合
__declspec(dllexport) int fnDLLTestEasy(void);
『の方、ってそれじゃわざわざマクロ使う必要ないじゃん!』
「このプロジェクトではね」
『このプロジェクトでは……?』
「ヘッダーファイルの方は?」
『ヘッダーファイルは……あ! BuildTest からも見てる!』
// Main.cpp
#include <Windows.h>
#include "DLLTestEasy.h"
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
fnDLLTestEasy();
return 0;
}
『こっちで DLLTestEasy.h をインクルードするときには、さっきの
プロジェクトの設定の #define ってないわけだから』
// DLLTESTEASY_EXPORTS が #define されていない場合
__declspec(dllimport) int fnDLLTestEasy(void);
『になってる』
「つまり、 __declspec() の中が」
・DLLTestEasy : dllexport
・BuildTest : dllimport
「と、切り替えられるってことです」
『どのプロジェクトから見るかで切り替わる……』
「この切り替わってるのが fnDLLTestEasy() の宣言ってことが重要。この
関数、プロジェクトによって立場が違うでしょ」
・DLLTestEasy : fnDLLTestEasy() そのものがある。
・BuildTest : fnDLLTestEasy() を呼び出す側。
「それぞれのために、関数宣言の前に __declspec(dllexport) と
__declspec(dllimport) を付けるんです」
・DLLTestEasy : __declspec(dllexport) : Exe から呼び出せるようにする
・BuildTest : __declspec(dllimport) : DLL のを呼ぶようにする
『呼び出せるように、と呼ぶように……』
「そう、専門用語で」
・DLLTestEasy : __declspec(dllexport) : エクスポート
・BuildTest : __declspec(dllimport) : インポート
『あ、こっちの方が分かりやすいかも。 DLL では関数を〈外出し〉して、
Exe では〈取り込む〉ってことなんだ』
「そういうこと。というわけで次回に続く!」
/*
Preview Next Story!
*/
『プロジェクトによって変わるなんてわけわかんねー!』
「ここは難しいからもう一度説明します」
『っつーか全部説明されてもらってねー!』
「というかちょっと落ち着いて」
『いやーっ、私逃げるー!』
「というわけで次回」
< Version 15.11 DLL の仕組み >
『につづく!』
「次回は図を使って説明します」
『メルマガで図なんてありえねー!!』
「おちつけー!!」