トップ 履歴 一覧 カテゴリ ソース 検索 ヘルプ RSS ログイン

Win32/WinDll-About

INDEX

MSDN Visual C++ 「インポートとエクスポート」「DLL と実行形式のリンク」などより

概要

ダイナミックリンクライブラリ(Dynamic Link Library : DLL)は、関数の共有ライブラリとして機能する実行可能ファイルです。動的リンクを利用することによって、プロセスの実行コードに含まれない関数を呼び出すことができます。関数の実行コードは、実際にその関数を使用するプロセスとは別に、複数の関数をコンパイル、リンクして格納したDLLに収められています。DLLは、データやリソースの共有にも有効です。メモリ内のDLLの内容には、複数のアプリケーションが同時にアクセスできます。

静的リンクの代わりに動的リンクを使うと、いくつかの利点があります。DLLの利点としては、メモリの節約、スワップの減少、ディスク領域の節約、アップグレードの簡便化、製品化後のサポートの提供、多言語プログラムのサポート、各国対応バージョン作成の容易化が挙げられます。

エクスポート

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

DLLから関数をエクスポートする方法には、次の2つがあります。

  • モジュール定義ファイル(.DEF)を作成して、DLLのビルド時に .DEFファイルを使う
  • 関数の定義に __declspec(dllexport) キーワードを使う

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

 .DEFファイルを使ったDLLからのエクスポート

モジュール定義ファイル(.DEF)は、テキスト ファイルです。DLLのさまざまな属性を記述する 1つ以上のモジュール文が含まれています。DLLの関数をエクスポートする __declspec(dllexport) キーワードを使わない場合、DLLには.DEFファイルが必要です。

.DEFファイルには、最低限でも以下のモジュール定義文を記述する必要があります。

LIBRARY
ファイルの先頭には、必ずLIBRARY文を記述します。LIBRARY文は、その.DEFファイルが所属するDLLを特定します。LIBRARY文の引数には、DLLの名前を指定します。リンカは、この名前をDLLのインポート ライブラリに配置します。
EXPORTS
EXPORTS文には、DLLのエクスポート関数の名前と、オプションで序数値を指定します。序数値を関数に割り当てるには、アット マーク(@)と数字の後に関数名を記述します。序数値の場合は、1からNの範囲で指定する必要があります。NはDLLのエクスポート関数の数字です。
DESCRIPTION
必須ではありませんが、通常 .DEFファイルには、DLLの目的を示すDESCRIPTION文を記述します。

C++ファイル内の関数をエクスポートする場合は、装飾名を.DEFファイルに配置するか、標準のCリンケージで extern "C" を使ってエクスポート関数を定義する必要があります。

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

 __declspec(dllexport) を使ったDLLからのエクスポート

__declspec(dllexport) キーワードを使うと、データ、関数、クラス、クラスのメンバ関数を DLL からエクスポートできます。__declspec(dllexport) を使う場合、エクスポート用の.DEFファイルは不要です。

 C/C++関数のエクスポート

C++で記述されたDLL内の関数にC言語のモジュールからアクセスするには、C++リンケージではなくCリンケージを使って関数を宣言する必要があります。特に指定しない限り、C++コンパイラはC++のタイプ セーフな名前付け規約(名前の装飾)とC++の呼び出し規約を使います。C++の規約を使うと、Cからの呼び出しが難しくなります。また、C++言語モジュールから使う場合は、これらの関数をCリンケージで宣言します。

Cリンケージを指定するには、関数の宣言に extern "C" を指定します。

インポート

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

関数の宣言としては、__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 ... };

C/C++の汎用的にするならこんな感じ?開放するAPIの宣言に DLLEXPORTS を付けて、DLLを作成するプロジェクトで (プロジェクト名)_EXPORTS を定義する。

/* DLL export/import macro */
#ifdef EXAMPLE_EXPORTS
#  ifdef __cplusplus
#    define DLLEXPORTS extern "C" __declspec(dllexport)
#  else
#    define DLLEXPORTS __declspec(dllexport)
#  endif
#else
#  ifdef __cplusplus
#    define DLLEXPORTS extern "C" __declspec(dllimport)
#  else
#    define DLLEXPORTS __declspec(dllimport)
#  endif
#endif

/*  */
DLLEXPORTS int WINAPI exampleFunction(int val1, int val2);
/*  */
class DLLEXPORTS CExampleClass : public CObject
{ ... class definition ... };

 リンク

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

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

明示的リンクの場合、DLLを使う実行形式は、関数呼び出しを行って、DLLを明示的にロード/アンロードしたり、DLLのエクスポート関数にアクセスします。クライアントの実行形式は、関数ポインタを通じてエクスポート関数を呼び出します。明示的リンクは、静的ロード、またはロード時ダイナミック リンクと見なされることがあります。

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

 暗黙的なリンク

DLLと暗黙的にリンクするには、実行形式は、DLLの提供元から以下のファイルを取得する必要があります。

  • エクスポート関数の宣言や C++クラスの宣言を含むヘッダー ファイル(.H ファイル)
  • リンク先のインポート ライブラリ (.LIB ファイル)。リンカは、DLLのビルド時にインポート ライブラリを作成します。
  • 実際のDLL (.DLL ファイル)

DLLを使う実行形式は、各ソース ファイル内のエクスポート関数(または C++クラス)を含むヘッダー ファイルをインクルードする必要があります。各ソース ファイルには、エクスポート関数の呼び出しが含まれています。コーディングの面から見ると、エクスポート関数の関数呼び出しは、その他の関数呼び出しとほとんど同じです。

実行可能ファイルの呼び出しをビルドするには、インポート ライブラリとリンクする必要があります。外部メイクファイルを使う場合は、リンクするその他のオブジェクト ファイル (.OBJ) やライブラリを列記したインポート ライブラリのファイル名を指定します。Visual C++ の開発環境で作業する場合は、[プロジェクトの設定] ダイアログ ボックスの [リンク] タブの [オブジェクト/ライブラリ モジュール] ボックスに、インポート ライブラリ名を指定します。

オペレーティング システムは、呼び出し実行形式のロード時に、.DLL ファイルを配置可能でなければなりません。

 明示的なリンク

明示的リンクの場合、アプリケーションは実行時にDLLを明示的にロードするために、関数呼び出しをする必要があります。DLLと明示的にリンクするには、アプリケーションは、以下の手順を実行します。

  1. LoadLibrary (または、同様の関数)を呼び出して、DLLをロードし、モジュール ハンドルを取得します。
  2. GetProcAddress を呼び出して、アプリケーションが呼び出す各エクスポート関数への関数ポインタを取得します。アプリケーションは、ポインタを通じてDLLの関数を呼び出すので、コンパイラは外部参照を生成しません。このため、インポート ライブラリとリンクする必要はありません。
  3. DLLの終了時に FreeLibrary を呼び出します。

拡張DLL(MFC)を使用する場合は、AfxLoadLibrary , AfxFreeLibrary を使う必要があります。

 リンク方式の使い分け

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

  • アプリケーションは、読み込む必要のある DLL の名前を実行時に初めて認識します。たとえば、アプリケーションは、DLL の名前とエクスポート関数を設定ファイルから取得しなければならない場合があります。
  • DLL がプロセスの起動時に見つからない場合、暗黙的なリンクを使うプロセスは、オペレーティングシステムによって停止されます。同じ状況でも、明示的リンクを使うプロセスは停止されずに、エラーからの回復を試行します。たとえば、プロセスがユーザーにエラーを通知して、ユーザーに DLL への別のパスを指定させることができます。
  • プロセスにリンクする DLL の DllMain 関数が失敗する場合にも、暗黙的リンクを使うプロセスは停止されます。同じ状況でも、明示的リンクを使うプロセスは停止されません。
  • Windows は、アプリケーションの読み込み時にすべての DLL を読み込むため、暗黙的に多くの DLL とリンクするアプリケーションは、起動に時間がかかることがあります。起動時のパフォーマンスを向上するには、アプリケーションは読み込み直後に必要な DLL を暗黙的にリンクし、その他の DLL については必要なときに明示的にリンクするように待機させることができます。
  • 明示的リンクの場合、アプリケーションとインポート ライブラリをリンクする必要はありません。DLL 内の変更によってエクスポート序数が変更される場合、明示的リンクを使うアプリケーションをリンクし直す必要はありません (序数値ではなく関数の名前を使って GetProcAddress を呼び出すと仮定した場合)。他方、暗黙的リンクを使うアプリケーションは、新しいインポート ライブラリとリンクし直す必要があります。

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

  • DLL が DllMain エントリ ポイント関数を持つ場合、オペレーティング システムは LoadLibrary を呼び出したスレッドのコンテキスト内で、DllMain 関数を呼び出します。LoadLibrary の前回の呼び出しに対応する FreeLibrary 関数が呼び出されていないために、DLL が既にプロセスにアタッチされている場合は、エントリ ポイント関数は呼び出されません。また、DLL が DllMain 関数を使って、プロセスの各スレッドの初期化を実行している場合、明示的リンクによって問題が発生することがあります。LoadLibrary (または AfxLoadLibrary) が呼び出される時点で、既存のスレッドが初期化されていないからです。
  • DLL が静的範囲のデータを __declspec(thread) として宣言する場合、明示的リンクでは保護違反が発生することがあります。DLL が LoadLibrary によって読み込まれる場合、コードがこのデータを参照すると、保護違反の原因になることがあります。静的範囲のデータには、グローバル スタティックアイテムとローカル スタティック アイテムの両方が含まれます。このため、DLL の作成時には、スレッド ローカルストレージの利用を避けるか、エラー発生の可能性について DLL のユーザーに通知する (動的読み込みを試行する場合) 必要があります。

簡単にまとめると。

  • コンパイル時にDLLの名前とエクスポート関数がわからない
    • 外部の設定ファイルから取得する
    • プラグインの利用
  • DLLが存在しないときの強制終了の排除
    • 暗黙的リンクの場合、存在しないとOSによって強制終了される
    • DllMain 関数が失敗する場合
  • パフォーマンスの向上
    • ロードするDLLが多い場合
  • インポートライブラリ(LIBファイル)が必要ない・存在しない
    • 提供しない・されていない場合

最終更新時間:2011年04月15日 13時05分26秒 指摘や意見などあればSandBoxのBBSへ。