Version 15.11
DLL の仕組み
「前回はエクスポートとインポートについて説明」
『されてない! 切り替えってのとかはわかったけどよくわかんなかった』
「だね、じゃあもう少し詳しく。まず、ひとつのプロジェクトの中で関数を
呼び出す場合」
<プロジェクトApp>
[From.cpp] [Func.cpp]
呼び出す側 Function()
↓ ↓
↓コンパイル ↓コンパイル
↓ ↓
[From.obj] [Func.obj]
↓ ↓
↓ リンク ↓
→→→→→→→→→→→→→→←←←←←←←←←←←←
↓
[App.exe]
『うん、こんな感じね』
「次にスタティックリンクライブラリの場合」
<プロジェクトSLL>
[Func.cpp]
Function()
↓
↓コンパイル
↓
[Func.obj]
↓
↓リンク
↓
[SLL.lib]
<プロジェクトFrom>
[From.cpp]
呼び出す側
↓
↓コンパイル
↓
[From.obj]
↓
↓ リンク
→→→→→→→→→→→→→→←←←←←←←←←←←←[SLL.lib]
↓
[From.exe]
『ほとんど変わらないね』
「それがスタティックリンクライブラリだからね。ライブラリファイルが
オブジェクトファイルとほとんど同じだから」
『さて、 DLL の場合』
<プロジェクトDLL>
[Func.cpp]
Function()
↓
↓コンパイル
↓
[Func.obj]
↓
↓リンク
↓
[DLL.lib], [DLL.dll]
<プロジェクトFrom>
[From.cpp]
呼び出す側
↓
↓コンパイル
↓
[From.obj]
↓
↓ リンク (A)
→→→→→→→→→→→→→→←←←←←←←←←←←←[DLL.lib]
↓
[From.exe]
<実行するとき>
[From.exe]
↓
↓ 実行時リンク (B)
→→→→→→→→→→→→→→←←←←←←←←←←←←[DLL.dll]
↓
実行
『お、リンクが2回になってる』
「ビルドの時と、実行時の両方のリンクが必要だからね。これまでの
〈リンク〉は〈実行時リンク (B)〉の方に当たります」
『じゃあ〈リンク (A)〉は?』
「そちらは〈仮のリンク〉」
『仮?』
「そう。この例で〈呼び出す側〉は、リンク時に〈Function()〉を〈仮に呼
び出せる状態〉にします。 Function() の本体は DLL.dll に入ってるから
そのままじゃ呼び出せないから」
『だから仮なんだ。で、〈実行時リンク (B)〉の時にちゃんとリンクする、
と』
「その実行時リンクで関数の結び付けをするために必要なのが、前回説明し
たインポートとエクスポート」
『ここで出てくるんだ』
「まず〈リンク (A)〉の時の、ライブラリファイルの意味。 DLL の
ライブラリファイルと、スタティックリンクライブラリのライブラリファイル
は異なるものです」
・スタティックリンクライブラリ : 関数本体がある。
・ダイナミックリンクライブラリ : DLL に結び付ける情報がある。
『 DLL に結び付ける情報……』
「そう、関数本体がない代わりに、〈実行時リンク (B)〉を行う際に、どの
DLL のどの関数をどうやって呼び出せばいいのか、っていう情報が入ってい
ます。〈リンク (A)〉の段階ではその情報だけを取得して」
『〈実行時リンク (B)〉の時に実際に DLL と結び付ける!』
「そういうこと。この時」
__declspec(dllexport) 関数宣言;
「とすることで、この関数の〈DLL に結び付けるための情報〉が
ライブラリファイルに書き込まれます」
『そのためのエクスポートなんだ』
「そう、〈関数に __declspec(dllexport) を付けてライブラリファイルに
情報を出力する〉ことが〈関数をエクスポートする〉ってこと」
『これがなくちゃ始まらないわけね』
「さらに、エクスポートで DLL の方にもその関数を出力します」
『ん? それって当然じゃない?』
「ん、当然って思うのは、 DLL の関数を呼ぶのは普通の
オブジェクトファイルと同じ、って考えてるからかな」
『違うの?』
「そう、 DLL の関数を呼ぶのはそんなに簡単じゃないんです。関数の
〈名前〉や〈呼び出し方〉が、普通に関数を呼ぶときと DLL のを呼ぶとき
とでは違っているんです」
『げげ』
「で、その関数の〈名前〉や〈呼び出し方〉を決めるのがエクスポート。
ライブラリファイルには〈名前〉や〈呼び出し方〉が書かれて、それに合わ
せて DLL の方に関数本体が入れられます」
『ってことは、ライブラリファイルがないと、 DLL の関数が呼び出せな
い……』
「そういうこと。ちょっとこの辺のテストしてみようか」
// DLLTestEasy.h
#ifdef DLLTESTEASY_EXPORTS
#define DLLTESTEASY_API __declspec(dllexport)
#else
#define DLLTESTEASY_API __declspec(dllimport)
#endif
「これを」
// DLLTestEasy.h
#ifdef DLLTESTEASY_EXPORTS
#define DLLTESTEASY_API
#else
#define DLLTESTEASY_API __declspec(dllimport)
#endif
「にしてリビルドしてみて」
『エクスポートしないってことね。ほいリビルド』
「 DLLTestEasy.lib は?」
『……あれ、ない! もしかして、エクスポートする関数がないから?』
「そういうこと。 DLL にとってのライブラリファイルは、エクスポートさ
れた関数の情報を格納するためのファイル」
『エクスポートする関数がひとつもないとライブラリファイルすら作られな
いんだ……あれ? でも DLL はあるんだね』
「 DLL は、結局は〈プログラムをコンパイルした、オブジェクトファイル
代わり〉だから、呼び出されるされない関係なく、関数があればコンパイル
して作られるんです」
『 DLL の方はなくなったりしない、と』
「それに、実はライブラリファイルがなくても DLL の中の関数を呼び出す
方法があるんです」
『……ちょっとマテ。話が違う』
「まぁそれは別の話だから置いといて」
『置いとくの!?』
「ここまでで、エクスポートの意味は分かったと思います」
『……つまり、エクスポートしないとその関数の情報がライブラリファイル
に書き込まれない、だから Exe から呼び出せない、ってわけね』
「そういうこと。で、逆に〈ライブラリファイルに情報がある関数を呼び出
す〉場合にはインポートが必要になります」
『その関数の頭に付けるわけね、こんな感じに』
__declspec(dllimport) 関数宣言;
「こうすることで、呼び出す側はその関数をライブラリファイルの中から探
して〈どのDLLにあってどういうふうに呼び出すのか〉という情報を憶えて
おきます」
『それが〈リンク (A)〉だよね』
「そうそう。で、〈実行時リンク (B)〉の時に、その情報を元に DLL の中
の関数を呼び出すようにするわけです。こんな感じに」
呼び出す側 : 関数をインポートする
呼び出される側 : 関数をエクスポートする
「ことが必要になります。これを、ひとつの関数宣言で切り替えるため」
// DLLTestEasy.h
#ifdef DLLTESTEASY_EXPORTS
#define DLLTESTEASY_API __declspec(dllexport)
#else
#define DLLTESTEASY_API __declspec(dllimport)
#endif
「というマクロを用意して」
DLLTESTEASY_API 関数宣言;
『ってして、 DLL のプロジェクトで DLLTESTEASY_EXPORTS を #define し
ちゃえば自動的に切り替わるってわけね』
「そういうこと。この自動切り替えが複雑だから分かりにくいかもしれない
ので、エクスポートとインポートが必要、その便利な手段として切り替えを
している、って切り分けて考えてね」
/*
Preview Next Story!
*/
『相変わらず混乱するなー』
「ふたつの視点で考えてるからね。というわけで」
『で?』
「次回は API でシンプルに考えてみます」
『え? なんで API ?』
「というわけで次回」
< Version 15.12 API も DLL ! >
『につづく!』
「 API も DLL を使うから」
『それで分かりやすくってことねー』