実行可能ファイルと DLL のリンク
実行可能ファイルを DLL とリンクするには、次の 2 つの方法があります。
"暗黙的なリンク"。オペレーティング システムによって、DLL を使用する実行可能ファイルと同時に DLL が読み込まれます。 クライアント実行可能ファイルによって、DLL のエクスポートされた関数が、その関数が静的にリンクされ実行可能ファイルに含まれていた場合と同じように呼び出されます。 暗黙的なリンクは、"静的読み込み" または "読み込み時の動的リンク" と呼ばれることもあります。
"明示的なリンク"。オペレーティング システムにより、実行時に必要に応じて DLL が読み込まれます。 明示的なリンクによって DLL が使用される実行可能ファイルでは、DLL の読み込みとアンロードを明示的に行う必要があります。 また、使用する DLL の各関数にアクセスするための関数ポインターも設定する必要があります。 静的にリンクされたライブラリまたは暗黙的にリンクされた DLL の関数の呼び出しとは異なり、クライアント実行可能ファイルでは、明示的にリンクされた DLL 内のエクスポートされた関数を関数ポインターを介して呼び出す必要があります。 明示的なリンクは、"動的読み込み" または "実行時の動的リンク" と呼ばれることもあります。
実行可能ファイルでは、同じ DLL にリンクするためにどちらかのリンク方法を使用することができます。 さらに、これらの方法は相互に排他的ではありません。つまり、ある実行可能ファイルが暗黙的に DLL にリンクし、別の実行可能ファイルが明示的にそれにアタッチすることが可能です。
リンク方式の使い分け
暗黙的なリンクと明示的なリンクのどちらを使用するかは、ご自分のアプリケーションに対して決定する必要があるアーキテクチャ上の判断です。 どちらの方法にも利点と欠点があります。
暗黙的リンク
アプリケーションのコードでエクスポートされた DLL の関数が呼び出されると、暗黙的なリンクが発生します。 DLL 関数を呼び出す実行形式のソース コードをコンパイルまたはアセンブルすると、オブジェクト コード内に外部関数への参照が生成されます。 この外部参照を解決するには、アプリケーションをインポート ライブラリ (.lib) ファイルとリンクする必要があります。インポート ライブラリは、DLL の作成元が提供します。
インポート ライブラリには、DLL を読み込んで、DLL 内の関数呼び出しを実装するコードが含まれるだけです。 リンカーは、インポート ライブラリ内に外部関数を見つけると、その関数のコードは DLL 内にあるものと認識します。 リンカーは、単に DLL コードの場所を実行可能ファイルに記入することによって、外部参照を解決します。システムはプロセスの起動時にこの情報を利用します。
動的にリンクされた参照を含むプログラムが起動されると、プログラムの実行可能ファイル内の情報に従って、必要な DLL を探します。 DLL が見つからない場合、システムによって処理が停止され、エラーを報告するダイアログ ボックスが表示されます。 それ以外の場合は、システムによってその DLL モジュールがプロセスのアドレス空間に割り当てられます。
DLL のいずれかに、DllMain
などの初期化および終了コード用のエントリ ポイント関数が含まれていた場合、オペレーティング システムによってその関数が呼び出されます。 エントリ ポイント関数に渡されるパラメーターの 1 つは、DLL がプロセスにアタッチされようとしていることを示すコードになります。 エントリ ポイント関数から TRUE が返されなかった場合、システムによって処理が停止され、エラーが報告されます。
最後に、システムはプロセスの実行可能コードを変更し、その DLL 関数の開始アドレスをセットします。
プログラム コードの残りの部分と同じように、プロセスの開始時に、ローダーによって DLL コードがプロセスのアドレス空間にマップされます。 オペレーティング システムでは、必要な場合にのみそれがメモリに読み込まれます。 結果として、以前のバージョンの Windows で読み込みを制御するために .def ファイルで使用されていた PRELOAD
および LOADONCALL
コード属性は、意味がなくなりました。
明示的リンク
暗黙的なリンクは、最も使用しやすいリンク方法であるため、多くのアプリケーションで使用されています。 しかし、明示的なリンクが必要な場合もあります。 ここでは、明示的リンクを使う一般的な理由について説明します。
アプリケーションでは、読み込む DLL の名前を、実行時まで認識しません。 たとえば、アプリケーションでは、DLL の名前とエクスポートされた関数を、構成ファイルから起動時に取得する場合があります。
プロセスの起動時に DLL が見つからない場合、暗黙的なリンクを使うプロセスはオペレーティング システムによって終了されます。 同じ状況でも、明示的なリンクを使うプロセスは終了されず、エラーからの回復を試みることができます。 たとえば、プロセスがユーザーにエラーを通知して、ユーザーに DLL への別のパスを指定させることができます。
暗黙的なリンクを使うプロセスは、リンクされている DLL のいずれかに失敗する
DllMain
関数が含まれている場合にも終了されます。 同じ状況でも、明示的なリンクを使うプロセスは終了されません。Windows は、アプリケーションの読み込み時にすべての DLL を読み込むため、暗黙的に多くの DLL とリンクするアプリケーションは、起動に時間がかかることがあります。 起動時のパフォーマンスを向上させるために、アプリケーションでは、読み込み直後に必要となる DLL に対する暗黙的なリンクのみが使用される場合があります。 その他の DLL を必要となったときにのみ読み込むために、明示的なリンクが使用される場合があります。
明示的なリンクを使用すると、インポート ライブラリを使用してアプリケーションをリンクする必要がなくなります。 DLL 内の変更によってエクスポート序数が変更された場合、序数値ではなく関数名を使用して
GetProcAddress
を呼び出せば、アプリケーションで再リンクを行う必要がなくなります。 暗黙的なリンクを使用するアプリケーションでは、変更されたインポート ライブラリに引き続き再リンクする必要があります。
明示的リンクの欠点は、次の 2 つです。
DLL に
DllMain
エントリ ポイント関数が含まれる場合、オペレーティング システムにより、LoadLibrary
を呼び出したスレッドのコンテキストでその関数が呼び出されます。 DLL が既にプロセスにアタッチされている場合は、エントリ ポイント関数は呼び出されません。FreeLibrary
関数への対応する呼び出しがなかったLoadLibrary
を以前に呼び出したためです。 プロセスの各スレッドを初期化するために DLL でDllMain
関数が使用される場合、明示的なリンクによって問題が発生する可能性があります。LoadLibrary
(またはAfxLoadLibrary
) が呼び出されたときに既に存在しているスレッドが初期化されないためです。DLL で静的範囲のデータが
__declspec(thread)
として宣言されると、明示的にリンクされている場合は保護違反が発生する可能性があります。LoadLibrary
への呼び出しによって DLL が読み込まれると、コードでこのデータが参照されるたびに保護違反が発生します。 (静的エクステント データには、グローバル静的項目とローカル静的項目の両方が含まれます)。そのため、DLL を作成するときは、スレッド ローカル ストレージの使用を避ける必要があります。 それができない場合は、DLL の動的読み込みによって発生する可能性のある問題点について、DLL のユーザーに通知してください。 詳細については、ダイナミックリンク ライブラリでスレッド ローカル ストレージを使用する方法 (Windows SDK) に関する記事をご覧ください。
暗黙的なリンクを使用する方法
暗黙的なリンクによって DLL を使用する場合、クライアント実行可能ファイルでは、DLL のプロバイダーからこれらのファイルを取得する必要があります。
DLL 内のエクスポートされたデータ、関数、および C++ クラスの宣言を含む、1 つまたは複数のヘッダー ファイル (.h ファイル)。 DLL によってエクスポートされたクラス、関数、およびデータはすべて、ヘッダー ファイルで
__declspec(dllimport)
としてマークされている必要があります。 詳細については、「dllexport、dllimport」をご覧ください。実行可能ファイルにリンクするインポート ライブラリ。 リンカーにより、DLL のビルド時にインポート ライブラリが作成されます。 詳細については、リンカー入力としての LIB ファイルに関する記事をご覧ください。
実際の DLL ファイル。
暗黙的なリンクによって DLL 内のデータ、関数、およびクラスを使用するには、クライアント ソース ファイルにそれらを宣言するヘッダー ファイルを含める必要があります。 コーディングの観点から見ると、エクスポートされた関数の呼び出しは、その他の関数の呼び出しとまったく同じです。
クライアント実行可能ファイルをビルドするには、DLL のインポート ライブラリとリンクする必要があります。 外部のメイクファイルやビルド システムを使用する場合は、リンクする他のオブジェクト ファイルやライブラリと共に、インポート ライブラリを指定します。
オペレーティング システムは、呼び出し実行形式の読み込み時に、DLL ファイルを配置できる必要があります。 つまり、アプリケーションをインストールするときに、DLL を配置するか、その存在を確認する必要があります。
DLL に明示的にリンクする方法
明示的なリンクによって DLL を使用する場合、アプリケーションでは、実行時に DLL を明示的に読み込むために、関数呼び出しを行う必要があります。 DLL と明示的にリンクするには、アプリケーションは、以下の手順を実行します。
LoadLibraryEx または同様の関数を呼び出して、DLL を読み込み、モジュール ハンドルを取得します。
GetProcAddress を呼び出して、アプリケーションが呼び出すエクスポートされた各関数への関数ポインターを取得します。 アプリケーションではポインターを通じて DLL の関数が呼び出されるため、コンパイラでは外部参照が生成されません。このため、インポート ライブラリとリンクする必要はありません。 ただし、呼び出すエクスポートされた関数の呼び出しシグネチャを定義する、
typedef
またはusing
ステートメントが必要です。DLL の終了時に FreeLibrary を呼び出します。
たとえば、次のサンプル関数では、LoadLibrary
を呼び出して "MyDLL" という名前の DLL を読み込み、GetProcAddress
を呼び出して "DLLFunc1" という名前の関数へのポインターを取得し、その関数を呼び出して結果を保存した後、FreeLibrary
を呼び出して DLL をアンロードします。
#include "windows.h"
typedef HRESULT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT*);
HRESULT LoadAndCallSomeFunction(DWORD dwParam1, UINT * puParam2)
{
HINSTANCE hDLL; // Handle to DLL
LPFNDLLFUNC1 lpfnDllFunc1; // Function pointer
HRESULT hrReturnVal;
hDLL = LoadLibrary("MyDLL");
if (NULL != hDLL)
{
lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL, "DLLFunc1");
if (NULL != lpfnDllFunc1)
{
// call the function
hrReturnVal = lpfnDllFunc1(dwParam1, puParam2);
}
else
{
// report the error
hrReturnVal = ERROR_DELAY_LOAD_FAILED;
}
FreeLibrary(hDLL);
}
else
{
hrReturnVal = ERROR_DELAY_LOAD_FAILED;
}
return hrReturnVal;
}
この例とは異なり、ほとんどのケースでは、特定の DLL に対してアプリケーション内で 1 回だけ LoadLibrary
と FreeLibrary
を呼び出す必要があります。 DLL 内の複数の関数を呼び出したり、DLL の関数を繰り返し呼び出したりする場合は特にそうです。