Visual Studio を使用してダイナミック リンク ライブラリ (DLL) をビルドする場合、既定では、リンカーには Visual C++ ランタイム ライブラリ (VCRuntime) が含まれます。 VCRuntime には、C/C++ 実行可能ファイルを初期化および終了するために必要なコードが含まれています。 DLL にリンクされる場合、VCRuntime コードは _DllMainCRTStartup と呼ばれる内部 DLL エントリポイント関数を提供します。この関数は、Windows OS メッセージを DLL に渡してプロセスまたはスレッドにアタッチまたはデタッチします。
_DllMainCRTStartup関数は、スタック バッファー セキュリティのセットアップ、C ランタイム ライブラリ (CRT) の初期化と終了、静的オブジェクトとグローバル オブジェクトのコンストラクターとデストラクターの呼び出しなどの重要なタスクを実行します。
_DllMainCRTStartup は、WinRT、MFC、ATL などの他のライブラリのフック関数も呼び出して、独自の初期化と終了を実行します。 この初期化がないと、CRT とその他のライブラリ、および静的変数は初期化されていない状態になります。 DLL が静的にリンクされた CRT を使用するか、動的にリンクされた CRT DLL を使用するかにかかわらず、同じ VCRuntime 内部初期化および終了ルーチンが呼び出されます。
既定の DLL エントリ ポイント _DllMainCRTStartup
Windows では、すべての DLL にオプションのエントリ ポイント関数 (通常、DllMain) が含まれます。エントリ ポイント関数は、初期化と終了の両方で呼び出されます。 これにより、必要に応じて他のリソースを割り当てたり、解放したりできます。 Windows には、エントリ ポイント関数が呼び出される 4 つの状況 (プロセスのアタッチ、プロセス デタッチ、スレッド アタッチ、スレッド デタッチ) があります。 DLL を使用するアプリケーションが読み込まれるとき、または実行時にアプリケーションが DLL を要求したときに、DLL がプロセス アドレス空間に読み込まれると、オペレーティング システムによって DLL データの別のコピーが作成されます。 これはプロセスのアタッチと呼ばれます。
スレッドのアタッチは、DLL が読み込まれるプロセスによって新しいスレッドが作成されるときに発生します。
スレッドのデタッチはスレッドが終了するときに発生し、プロセスのデタッチは DLL が不要になり、アプリケーションによって解放されるときに発生します。 オペレーティング システムは、これらの各イベントの DLL エントリ ポイントを個別に呼び出し、各イベントの種類に対して reason 引数を渡します。 たとえば、OS は DLL_PROCESS_ATTACH を reason 引数として送信し、プロセスのアタッチを通知します。
VCRuntime ライブラリには、既定の初期化および終了操作を処理するために _DllMainCRTStartup というエントリポイント関数が用意されています。 プロセスアタッチ時に、 _DllMainCRTStartup 関数はバッファー セキュリティ チェックを設定し、CRT とその他のライブラリを初期化し、ランタイム型情報を初期化し、静的および非ローカル データのコンストラクターを初期化して呼び出し、スレッド ローカル ストレージを初期化し、アタッチごとに内部静的カウンターをインクリメントしてから、ユーザーまたはライブラリから提供される DllMainを呼び出します。 プロセスのデタッチでは、関数はこれらのステップを逆順で実行します。
DllMain を呼び出し、内部カウンターをデクリメントし、デストラクターを呼び出し、CRT 終了関数と登録済み atexit 関数を呼び出し、他のすべてのライブラリが終了したことを通知します。 アタッチカウンターが 0 になると、関数は FALSE を返して、DLL をアンロードできることを Windows に示します。
_DllMainCRTStartup 関数は、スレッドのアタッチとスレッドのデタッチ中にも呼び出されます。 このような場合、VCRuntime コードはそれ自体で他の初期化や終了を行わず、 DllMain を呼び出してメッセージを渡すだけです。
DllMain がプロセスのアタッチから FALSE を返した場合、通知は失敗し、_DllMainCRTStartup は DllMain を再度呼び出して DLL_PROCESS_DETACH を reason 引数として渡してから、終了処理の残りを実行します。
Visual Studio で DLL をビルドする場合、VCRuntime によって提供される既定のエントリ ポイント _DllMainCRTStartup が自動的にリンクされます。
/ENTRY (エントリ ポイント シンボル) リンカー オプションを使用して、DLL のエントリ ポイント関数を指定する必要はありません。
注
/ENTRY: リンカー オプションを使用して DLL に別のエントリ ポイント関数を指定することは可能ですが、エントリ ポイント関数は同じ順序で_DllMainCRTStartup行うすべてを複製する必要があるため、推奨されません。 VCRuntime には、その動作を複製できるようにする関数が用意されています。 たとえば、プロセスのアタッチですぐに __security_init_cookie を呼び出して、/GS (バッファー セキュリティ チェック) バッファー チェック オプションをサポートすることができます。
_CRT_INIT 関数を呼び出して、エントリ ポイント関数と同じパラメーターを渡すことにより、DLL 初期化関数または終了関数の残りを実行できます。
DLL の初期化
DLL の読み込み時に実行する必要がある初期化コードが DLL に含まれている可能性があります。 独自の DLL 初期化関数と終了関数を実行するために、_DllMainCRTStartup は、指定できる DllMain と呼ばれる関数を呼び出します。
DllMain には、DLL エントリ ポイントに要求されるシグネチャが必要です。 既定のエントリ ポイント関数 _DllMainCRTStartup は、Windows によって渡される同じパラメーターを使用して DllMain を呼び出します。 既定では、 DllMain 関数を指定しない場合は、Visual Studio によって関数が提供され、 _DllMainCRTStartup が常に呼び出し対象になるようにリンクされます。 つまり、DLL を初期化する必要がない場合は、DLL をビルドするときに特別な操作を行う必要はありません。
これは DllMain に使用されるシグネチャです。
#include <windows.h>
extern "C" BOOL WINAPI DllMain (
HINSTANCE const instance, // handle to DLL module
DWORD const reason, // reason for calling function
LPVOID const reserved); // reserved
一部のライブラリは、DllMain 関数をラップしてくれます。 たとえば、標準の MFC DLL では、CWinApp オブジェクトの InitInstance および ExitInstance メンバー関数を実装して、DLL で必要な初期化と終了を実行します。 詳細については、「 標準 MFC DLL の初期化 」セクションを参照してください。
警告
DLL のエントリ ポイントで安全に実行できる処理には大きな制限があります。
DllMain での呼び出しが安全ではない特定の Windows API について詳しくは、一般的なベスト プラクティスに関するページを参照してください。 最も単純な初期化以外のものが必要な場合は、DLL の初期化関数で行います。
DllMain が実行された後、DLL 内の他の関数を呼び出す前に、アプリケーションが初期化関数を呼び出すように要求することができます。
通常 (非 MFC) DLL の初期化
VCRuntime によって提供される _DllMainCRTStartup エントリ ポイントを使用する通常の (非 MFC) DLL で独自の初期化を実行するには、DLL ソース コードに DllMain という名前の関数が含まれている必要があります。 次のコードは、DllMain の定義がどのようなものかを示す基本的なスケルトンを示しています。
#include <windows.h>
extern "C" BOOL WINAPI DllMain (
HINSTANCE const instance, // handle to DLL module
DWORD const reason, // reason for calling function
LPVOID const reserved) // reserved
{
// Perform actions based on the reason for calling.
switch (reason)
{
case DLL_PROCESS_ATTACH:
// Initialize once for each new process.
// Return FALSE to fail DLL load.
break;
case DLL_THREAD_ATTACH:
// Do thread-specific initialization.
break;
case DLL_THREAD_DETACH:
// Do thread-specific cleanup.
break;
case DLL_PROCESS_DETACH:
// Perform any necessary cleanup.
break;
}
return TRUE; // Successful DLL_PROCESS_ATTACH.
}
注
以前の Windows SDK ドキュメントでは、dll エントリ ポイント関数の実際の名前は、 /ENTRY オプションを使用してリンカー コマンド ラインで指定する必要があります。 Visual Studio では、エントリ ポイント関数の名前が/ENTRYされている場合は、DllMain オプションを使用する必要はありません。 実際、 /ENTRY オプションを使用し、エントリ ポイント関数に DllMain 以外の名前を付ける場合、エントリ ポイント関数が _DllMainCRTStartup と同じ初期化呼び出しを行わない限り、CRT は正しく初期化されません。
_CRT_INIT と共にCRTを手動で初期化
C ランタイム ライブラリのいずれかを使用する DLL をビルドする場合は、CRT が正しく初期化されていることを確認するために、次のいずれかを実行します。
- 初期化関数には
DllMain()という名前を付ける必要があり、エントリ ポイントはリンカー オプションを使用して指定する必要があります-entry:_DllMainCRTStartup@12これは、DLL をビルドするときの既定の動作です (@12は、stdcallを使用するため x86 のみ)。 - DLL のエントリ ポイントは、プロセスのアタッチとプロセスのデタッチ時に
_CRT_INIT()を明示的に呼び出す必要があります。 これは、/NOENTRYを使用している場合、またはカスタム エントリ ポイントがある場合にのみ関連します。 可能であれば、/NOENTRYまたはカスタム エントリ ポイントを使用することはお勧めしません。これは、_DllMainCRTStartupが提供するすべての初期化コードと終了コードを同じ順序で複製する必要があるためです。
これにより、プロセスまたはスレッドが DLL にアタッチされているときに C ランタイム データを適切に割り当てて初期化したり、プロセスが DLL からデタッチされるときに C ランタイム データを適切にクリーンアップしたり、DLL 内のグローバル C++ オブジェクトを適切に構築および破棄したりすることができます。
Win32 SDK のサンプルはすべて、最初のメソッドを使用します。
DllEntryPoint()の Win32 プログラマ リファレンスと、DllMain()の Visual C++ ドキュメントを参照してください。
DllMainCRTStartup() は _CRT_INIT() を呼び出し、 _CRT_INIT() アプリケーションの DllMain()が存在する場合は呼び出します。
2 番目のメソッドを使用して CRT 初期化コードを自分で呼び出す場合は、 DllMainCRTStartup() と DllMain()を使用する代わりに、次の 2 つの手法があります。
独自の DLL エントリ ポイントがある場合は、エントリ ポイントで次の操作を行います。
a.
process.hには、次のプロトタイプ (_DECL_DLLMAINが定義されている場合は_CRT_INIT()で定義) を使用します。BOOL WINAPI _CRT_INIT(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);戻り値
_CRT_INIT()詳細については、DllEntryPointのドキュメントを参照してください。同じ値が返されます。b。
DLL_PROCESS_ATTACHおよびDLL_THREAD_ATTACHでは、C ランタイム関数が呼び出されるか、浮動小数点演算が実行される前に、最初に_CRT_INIT()を呼び出します。c. 独自のプロセス/スレッド初期化/終了コードを呼び出します。
d.
DLL_PROCESS_DETACHおよびDLL_THREAD_DETACHでは、すべての C ランタイム関数が呼び出され、すべての浮動小数点演算が完了した後、最後に_CRT_INIT()呼び出します。
エントリ ポイントのすべてのパラメーター _CRT_INIT() 渡すようにしてください。 _CRT_INIT() はこれらのパラメーターを想定しているため、省略すると確実に動作しない場合があります (特に、プロセスの初期化または終了が必要かどうかを判断するには、 fdwReason が必要です)。
DLL エントリ ポイントでこれらの呼び出しを _CRT_INIT() するタイミングと方法を示すスケルトン サンプル エントリ ポイント関数を次に示します。
BOOL WINAPI DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
if (fdwReason == DLL_PROCESS_ATTACH || fdwReason == DLL_THREAD_ATTACH)
if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved))
return(FALSE);
if (fdwReason == DLL_PROCESS_DETACH || fdwReason == DLL_THREAD_DETACH)
if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved))
return(FALSE);
return(TRUE);
}
注
これは、 DllMain() と -entry:_DllMainCRTStartup@12を使用している場合は必要ありません。
標準 MFC DLL の初期化
標準 MFC Dll には CWinApp オブジェクトがあるので、MFC アプリケーションと同じ場所、つまり DLL の InitInstance 派生クラスの ExitInstance および CWinApp メンバー関数で初期化タスクと終了タスクを実行する必要があります。 MFC には、DllMainと_DllMainCRTStartupのDLL_PROCESS_ATTACHによって呼び出されるDLL_PROCESS_DETACH関数が用意されているため、独自のDllMain関数を記述しないでください。 MFC に用意されている DllMain 関数は、DLL が読み込まれるときに InitInstance を呼び出し、DLL がアンロードされる前に ExitInstance を呼び出します。
標準 MFC DLL では、その 関数で TlsAlloc および InitInstance を呼び出すことで、複数のスレッドを追跡できます。 これらの関数により、DLL がスレッド固有のデータを追跡できます。
通常の MFC DLL (MFC に動的にリンクするもの) で、MFC OLE、MFC データベース (または DAO)、あるいは MFC ソケットのサポートを使用している場合、それぞれのデバッグ用 MFC 拡張 DLL、すなわち MFCOバージョンD.dll、MFCDバージョンD.dll、MFCNバージョンD.dll (ここで バージョン はバージョン番号を表します) が自動的にリンクされます。 通常の MFC DLL の CWinApp::InitInstanceで使用している DLL ごとに、次のいずれかの定義済みの初期化関数を呼び出す必要があります。
| MFC サポートの種類 | 呼び出すべき初期化関数 |
|---|---|
| MFC OLE (MFCOバージョンD.dll) | AfxOleInitModule |
| MFC データベース (MFCDversionD.dll) | AfxDbInitModule |
| MFC ソケット (MFCNversionD.dll) | AfxNetInitModule |
MFC 拡張 DLL の初期化
MFC 拡張 DLL には (通常の MFC DLL と同様に) CWinApp派生オブジェクトがないため、MFC DLL ウィザードで生成される DllMain 関数に初期化コードと終了コードを追加する必要があります。
ウィザードには、MFC 拡張 DLL 用の次のコードが用意されています。 コードで、PROJNAME はプロジェクト名のプレースホルダーです。
#include "pch.h" // For Visual Studio 2017 and earlier, use "stdafx.h"
#include <afxdllx.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
static AFX_EXTENSION_MODULE PROJNAMEDLL;
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0("PROJNAME.DLL Initializing!\n");
// MFC extension DLL one-time initialization
AfxInitExtensionModule(PROJNAMEDLL,
hInstance);
// Insert this DLL into the resource chain
new CDynLinkLibrary(Dll3DLL);
}
else if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0("PROJNAME.DLL Terminating!\n");
}
return 1; // ok
}
初期化中に CDynLinkLibrary オブジェクトを作成すると、MFC 拡張 DLL がクライアント アプリケーションに CRuntimeClass オブジェクトまたはリソースをエクスポートできます。
1 つ以上の標準 MFC DLL から MFC 拡張 DLL を使用する場合は、 CDynLinkLibrary オブジェクトを作成する初期化関数をエクスポートする必要があります。 その関数は、MFC 拡張 DLL を使用するそれぞれの標準 MFC DLL から呼び出す必要があります。 この初期化関数を呼び出す適切な場所は、標準 MFC DLL の InitInstance 派生オブジェクトの CWinApp メンバー関数内で、MFC 拡張 DLL のエクスポートされたクラスまたは関数のいずれかを使用する前です。
MFC DLL ウィザードによって生成されるDllMainでは、AfxInitExtensionModuleの呼び出しによって、CRuntimeClass オブジェクトの作成時に使用するモジュールのランタイム クラス (COleObjectFactory 構造体) とそのオブジェクト ファクトリ (CDynLinkLibrary オブジェクト) がキャプチャされます。
AfxInitExtensionModule の戻り値を確認する必要があります。AfxInitExtensionModule から 0 の値が返された場合は、DllMain 関数から 0 を返します。
MFC 拡張 DLL が実行可能ファイルに明示的にリンクされている場合 (つまり、実行可能ファイルが DLL にリンクするためにAfxLoadLibraryを呼び出す場合)、AfxTermExtensionModuleにDLL_PROCESS_DETACHへの呼び出しを追加する必要があります。 この関数を使用すると、各プロセスが MFC 拡張 DLL からデタッチされたときに MFC 拡張 DLL をクリーンアップできます (デタッチは、プロセスが終了したとき、または DLL が AfxFreeLibrary 呼び出しの結果としてアンロードされたときに発生します)。 MFC 拡張 DLL が暗黙的にアプリケーションにリンクされている場合、 AfxTermExtensionModule の呼び出しは必要ありません。
MFC 拡張 DLL に明示的にリンクするアプリケーションは、DLL を解放するときに AfxTermExtensionModule を呼び出す必要があります。 また、アプリケーションで複数のスレッドが使用されている場合は、(Win32 関数 AfxLoadLibrary および AfxFreeLibrary ではなく) LoadLibrary および FreeLibrary も使用する必要があります。
AfxLoadLibraryとAfxFreeLibraryを使用すると、MFC 拡張 DLL の読み込みとアンロード時に実行されるスタートアップ コードとシャットダウン コードによってグローバル MFC 状態が破損することはありません。
MFCx0.dll は DllMain が呼び出された時点で完全に初期化されるので、DllMain 内でメモリを割り当て、MFC 関数を呼び出すことができます (16 ビット バージョンの MFC とは異なります)。
拡張 DLL では、DLL_THREAD_ATTACH 関数内の DLL_THREAD_DETACH および DllMain ケースを処理することによって、マルチスレッド処理を行うことができます。 これらのケースは、スレッドが DLL に対してアタッチおよびデタッチするときに DllMain に渡されます。 DLL がアタッチされているときに TlsAlloc を呼び出すと、DLL は DLL にアタッチされているすべてのスレッドのスレッド ローカル ストレージ (TLS) インデックスを維持できます。
ヘッダー ファイル Afxdllx.h には、MFC 拡張 DLL で使用される構造体の特別な定義 ( AFX_EXTENSION_MODULE や CDynLinkLibraryの定義など) が含まれています。 このヘッダー ファイルは、MFC 拡張 DLL に含める必要があります。
注
_AFX_NO_XXX (Visual Studio 2017 以前のpch.h) では、stdafx.hマクロを定義または未定義にしないようにすることが重要です。 これらのマクロは、特定のターゲット プラットフォームがその機能をサポートしているかどうかを確認するためにのみ存在します。 これらのマクロをチェックするようにプログラムを記述できます (たとえば、#ifndef _AFX_NO_OLE_SUPPORT)。ただし、プログラムでこれらのマクロを定義または未定義にすることはできません。
マルチスレッド処理を行うサンプルの初期化関数は、Windows SDK のダイナミックリンク ライブラリでスレッド ローカル ストレージを使用する方法に関するページに含まれています。 サンプルには LibMain というエントリ ポイント関数が含まれていますが、MFC および C ランタイム ライブラリで動作するように、この関数に DllMain 名前を付ける必要があることに注意してください。
関連項目
Visual Studio での C/C++ Dll の作成
DllMain エントリ ポイント
ダイナミックリンク ライブラリのベスト プラクティス