一般のWindowsアプリケーションがWinMainというエントリポイントを持つように、 DLLもDllMainという名前のエントリポイントを持つことができます。 DllMainを実装するかどうかはオプションですが、 プロセスにDLLがマッピングされるときや、 マッピングが解除されるときなどのタイミングを確実に知ることができるので、 DLLの利用者がデータの初期化や破棄を忘れることがなくなりやすくなります。 次に、DllMainの実装例を示します。
BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD dwReason, LPVOID lpReserved) { switch (dwReason) { case DLL_PROCESS_ATTACH: // DLLがプロセスのアドレス空間にマッピングされた。 break; case DLL_THREAD_ATTACH: // スレッドが作成されようとしている。 break; case DLL_THREAD_DETACH: // スレッドが破棄されようとしている。 break; case DLL_PROCESS_DETACH: // DLLのマッピングが解除されようとしている。 break; } return TRUE; }
DllMainという名前でこのようなプロトタイプを持つ関数を実装した場合、 特定のタイミングで通知を受けることができます。 hinstDllは、このDLLがプロセスのアドレス空間でマッピングされたアドレスです。 dwReasonは、DllMainが呼ばれた理由を示す定数が格納されています。 lpReservedは、このDLLが明示的にリンク、または解除される場合はNULLが格納され、 暗黙的にリンク、または解除される場合はNULL以外の値が格納されます。 lpReservedは、dwReasonがDLL_PROCESS_ATTACHかDLL_PROCESS_DETACHのときに調べます。 戻り値は、dwReasonがDLL_PROCESS_ATTACHであるときにのみ考慮されることになっており、 FALSEを返した場合はDLLのマッピングが解除されます。 暗黙的リンクの場合は、これに加えてプロセスの起動に失敗します。
dwReasonとして格納される定義の意味について見ていきます。 まず、プロセスが起動されるとシステムはそのためのアドレス空間を作成し、 EXEファイルとインポートセクションに書き込まれている各種DLLを、 アドレス空間にマッピングします。 このマッピングの部分は、特にローダが担当しています。 次に、システムはプロセスの主スレッドを作成し、 マッピングした各種DLLのDllMainをDLL_PROCESS_ATTACHを引数として呼び出します。 このとき、DLL_PROCESS_ATTACHがFALSEを返した場合、プロセスは終了することになります。 これが、暗黙的リンクにおけるDLL_PROCESS_ATTACHの呼ばれ方になります。 一方、LoadLibraryによる明示的リンクの場合、DLL_PROCESS_ATTACHを引数としたDllMainは、 LoadLibraryを呼び出した任意のスレッドで実行され、DLL_PROCESS_ATTACHでFALSEを返した場合でも、 そのDLLのマッピングが解除されるだけとなっています。 DLL_PROCESS_ATTACHは、DllMainからすればDLLがプロセスのアドレス空間に初めてマッピングされたことを示すもので、 決してプロセスの作成を通知するものではないことに注意してください。 また、LoadLibraryが複数回呼び出されても、既にDLLがマッピングされている場合は、 単純にDLLの参照カウントが上がるだけで、DllMainが呼ばれることはありません。 DLL_PROCESS_DETACHは、プロセスのアドレス空間からDLLのマッピングが解除されるときに通知されます。
DLL_THREAD_ATTACHとDLL_THREAD_DETACHは、スレッドの作成と終了を通知します。 プロセスでスレッドが作成されるとき、そのスレッドはDLL_THREAD_ATTACHを引数としたDllMainを呼び出し、 スレッドが終了するときはDLL_THREAD_DETACHを引数としたDllMainを呼び出します。 暗黙的リンクの場合は、主スレッドの作成をDLL_PROCESS_ATTACHで通知しているという見方もできるでしょうか。 明示的リンクにおいては、そのDLLがマッピングされてから後に作成されたスレッドの通知しか受け取れません。 DisableThreadLibraryCallsという関数にDllMainの第1引数を指定すれば、 DLL_THREAD_ATTACHとDLL_THREAD_DETACHを受け取らないようになります。
多くのDLLの開発者は、そのDLLが必要とするデータの初期化用と破棄用の関数を実装し、 DLLを利用するプロセスにそれらを呼び出すことを期待しますが、 DLL_PROCESS_ATTACHとDLL_PROCESS_DETACHを利用すれば、 それらの関数を呼び出し忘れることはなくなります。 DLLはプロセスのアドレス空間で動作していることから、 DLLがメモリを確保する関数を呼び出すことは、 そのプロセスのアドレス空間を消費することを意味しています。 DLL_PROCESS_DETACHでDLLのマッピングが解除されたとしても、 そのDLLが確保したメモリがプロセスのアドレス空間から自動的に削除されるようなことはありません。 明示的リンクの場合は、そのDLLのマッピングを解除した後でもプロセスは動作し続けるわけですから、 DLL_PROCESS_DETACHでは確実に後始末処理を実行しておかなければなりません。