Tips of VC++ > DLL > エクスポートとインポートとリンク
前へ戻る ★ 次へ進む ★


エクスポートとインポートとリンク

エクスポート

DLLはアイテムを共有するためにあります。 そのアイテムは主に関数ですが、 基本的にほとんど全てのアイテムを共有できます。 関数、変数、クラス、リソースあたりですね。 このうちリソース以外のアイテムは、 プログラムから利用できるようにするためにエクスポートする必要があります。 エクスポートとは何ぞや?さぁ、分かりません。(ぉぃ
困ったときはMSDNに依存しときましょう。

DLL のレイアウトは .EXE とよく似ていますが、重要な相違点が 1 つあります。DLL ファイルには、エクスポート テーブルが含まれています。エクスポート テーブルには、DLL が別の実行形式に対してエクスポートする各関数の名前が含まれています。これらの関数は、DLL のエントリ ポイントです。エクスポート テーブルに記述されたエクスポート関数のみが、別の実行形式にアクセスできます。DLL 内のその他の関数は、その DLL でしか使えません。

エクスポートテーブルに名前を刻む(謎)作業のようですね。
エクスポート方法には二つあります。これもMSDNに依存。(手抜き

上のどちらかの方法を使って関数をエクスポートする場合は、必ず __stdcall 呼び出し規約を使います。

それぞれの方法は別スレでサンプルを交えて書きますね。 では、問題はその選択方法です。 当然、それぞれに良し悪しがあります。かなり長いですが引用。

.DEF ファイルを使う場合の利点と欠点

.DEF ファイルを使って関数をエクスポートすると、エクスポート序数を制御できます。エクスポート関数を DLL に追加する場合、その関数には高い (その他のエクスポート関数よりも高い) 序数値を割り当てることができます。こうすると、暗黙的なリンクを使用するアプリケーションを、新しい関数を含む新しいインポート ライブラリとリンクし直す必要はありません。これが非常に重要になるのは、たとえば、多くのアプリケーションで使うサード パーティの DLL を設計する場合です。既存のアプリケーションは、新しい DLL を引き続き適切に利用しながら、機能を追加することによって、DLL の機能を向上できます。MFC DLL のビルドには、.DEF ファイルを使います。

.DEF ファイルを使うもう 1 つの利点は、NONAME 属性を使って、関数をエクスポートできることです。NONAME 属性は、DLL のエクスポート テーブル内の序数のみを配置します。多数のエクスポート関数を含む DLL の場合、NONAME 属性を使うと、DLL ファイルのサイズを抑えることができます。モジュール定義文の記述については、「モジュール定義文に関する規則」を参照してください。序数値によるエクスポートの詳細については、「名前ではなく序数による DLL 関数のエクスポート」を参照してください。

.DEF ファイルを使う最大の欠点は、C++ ファイル内の関数をエクスポートする場合、コンパイラが関数名を装飾しないように extern "C" を使って標準の C リンケージでエクスポート関数を定義するか、.DEF ファイルに装飾名を記述する必要があることです。

.DEF ファイル内に装飾名を配置する場合、装飾名を取得するには、ツール DUMPBIN か、リンク スイッチ /MAP を使います。コンパイラが作成した装飾名は、コンパイラ独自のものであることに注意してください。Visual C++ コンパイラが作成した装飾名を .DEF ファイルに配置する場合は、DLL とリンクするアプリケーションは、同じバージョンの Visual C++ を使ってビルドする必要があります。これは、呼び出しアプリケーション内の装飾名と、DLL の .DEF ファイル内のエクスポート名を一致させるためです。

__declspec(dllexport) を使う場合の利点と欠点

__declspec(dllexport) を使うと便利なのは、.DEF ファイルのメンテナンスやエクスポート関数の装飾名の取得を気にする必要がないことです。ただし、コンパイラが生成するエクスポート序数を管理することはできません。この方式は、たとえば、プログラマが制御するアプリケーションで使う DLL を作成する場合に適しています。つまり、新しいエクスポートを使って DLL をリビルドする場合は、アプリケーションもリビルドする必要があります。

これを箇条書きにしてみます。

DEFファイルを使うと…

__declspec(dllexport)を使うと…

装飾名とは(論点がずれてきてるぞ…)コンパイラが関数を識別するために 関数の定義またはプロトタイプのコンパイル時にコンパイラが生成する 文字列のことです。 識別するのに関数名をそのまま使えればいいのですが、 C++の場合、引数が違ってれば同じ関数名を使えるじゃないですか。 だから、func(int)とfunc(int,int)を共にfuncという名前で識別できないので、 装飾名が必要なわけです。
装飾の仕方はコンパイラ独自のものです。 したがって、他の人のコンパイラが自分と同じ装飾方法をする保証はありません ということはDEFファイルに装飾名を書くことはあまりお薦めできません。 自分だけが使うなら別ですけど。
そこで、extern "C"の登場です。 これを使えば名前の装飾を取りやめることが出来ます。


インポート

;

今まで勘違いしてたんですが、リンクとインポートは違います。 エクスポート方法、リンク方法とは独立して、インポート方法は一つです。
ではMSDNの箇所を引用。

あるプログラムが DLL によって定義されたパブリック シンボルを使うことを、シンボルをインポートすると言います。DLL を使ってビルドされるアプリケーション用のヘッダー ファイルを作成する場合、パブリック シンボルの宣言には __declspec(dllimport) を使います。キーワード __declspec(dllimport) は、エクスポートに .DEF ファイルと __declspec(dllexport) キーワードのどちらを使うかにかかわわらず、動作します。

コードを読みやすくするために、次のように __declspec(dllimport) 用のマクロを定義して、そのマクロを各インポート シンボルの宣言に使います。

#define DllImport   __declspec( dllimport )

DllImport int  j;
DllImport void func();

関数の宣言としては、__declspec(dllimport) は省略可能ですが、このキーワードを使うと、コンパイラはより効率的なコードを生成します。ただし、インポートされる実行形式が DLL のパブリック データ シンボルとオブジェクトにアクセスするには、__declspec(dllimport) を使う必要があります。DLL を使う場合は、引き続きインポート ライブラリとリンクする必要があることに注意してください。

DLL とクライアント アプリケーションには、同じヘッダー ファイルを使うことができます。こうする場合は、DLL のビルドとクライアント アプリケーションのビルドの区別を示すために、専用のプリプロセッサ シンボルを使います。以下に例を示します。

#ifdef _EXPORTING
   #define CLASS_DECLSPEC    __declspec(dllexport)
#else
   #define CLASS_DECLSPEC    __declspec(dllimport)
#endif

class CLASS_DECLSPEC CExampleA : public CObject
{ ... class definition ... };

エクスポートのときと同じように__declspec( dllimport )を 付けるだけでいいのです。 実際、DLLなんて作業別に分ければたいしたことないですね。


リンク

リンクについては、今更、解説は要りませんね。 DLLじゃなくてもリンクは行ってます。 objファイルどうしのリンクや、libファイルのリンクです。 DLLの場合もほぼ同じですが、ちょっと違うのは方法が二つあることです。 この二つは、エクスポート方法に関係なく、 プログラマが自由に選択できます。
MSDNには以下のように書いてあります。

実行可能ファイルを DLL とリンクするには、次の 2 つの方法があります。

メ モ   明示的リンクは、静的ロード、またはロード時ダイナミック リンクと見なされることがあります。暗黙的リンクは、動的ロード、またはランタイム ダイナミック リンクと見なされることがあります。

暗黙的リンクの場合、DLL を使う実行形式は、DLL の作成元から提供されるインポート ライブラリ (.LIB ファイル) とリンクします。オペレーティング システムは、DLL を使う実行形式のロード時にその DLL をロードします。クライアントの実行形式は、その実行形式内に含まれている関数を呼び出す場合と同じように、DLL のエクスポート関数を呼び出します。

明示的リンクの場合、DLL を使う実行形式は、関数呼び出しを行って、DLL を明示的にロード/アンロードしたり、DLL のエクスポート関数にアクセスします。クライアントの実行形式は、関数ポインタを通じてエクスポート関数を呼び出します。

実行形式は、リンク方式に関係なく同じ DLL を使うことができます。さらに、これらの機構は相互に排他的ではありません。つまり、ある実行形式は、DLL と暗黙的にリンクでき、別の実行形式は、DLL と明示的にアタッチできます。

ではエクスポート方法のときと同じように、 メリット・デメリットを見てみます。とりあえず長いです。(^^

リンク方式の使い分け

暗黙的リンクは最も使いやすい方法なので、多くのアプリケーションは、暗黙的リンクを使います。しかし、明示的リンクが必要な場合もあります。ここでは、明示的リンクを使う一般的な理由について説明します。

明示的リンクの欠点は、次の 2 つです。

最初から、「おっ」って感じですね。分かりましたか? MSDNには基本的に暗黙的リンクを使えと書いてあります。 私もそう思います。可能なら暗黙的リンクを使ったほうがいいですね。 暗黙的リンクだと、関数の呼び出し方法は変わらないのです。 明示的リンクだとちょっと面倒になってきます。
では、明示的リンクを使う場合についてをまとめてみます。

明示的リンクを使うモチベーション(謎)

MSDNではその後に欠点が二つ書いてありますが、よく分かりません。 誰か教えてください。 マルチスレッドはまだ分からないんですよぉ〜。(笑

Jun. 2, 2002