💉
共有ライブラリ(DLL)を複数回ロードするとどうなるか
概要
同じ共有ライブラリ(DLL)を複数回ロードしても DLL のインスタンスは 1 つしか作られません。実際には最初の1回目しかロードされず、2回目以降は参照カウンタが増えるだけ。
そのあたりの挙動でハマったのでメモ。主に Win32API/MSVC の話です。
Windows
- ロード/アンロードは
LoadLibrary
/FreeLibrary
を使う。2回目以降のロード/アンロードのペアはGetModuleHandleEx
/CloseHandle
を使うこともできる。 - 2回目以降で
GetModuleHandle
を使うことで参照カウンタを増やさずにロード済み DLL のハンドルを得ることもできる。
詳細
DLL がロードされると、プロセス単位で参照カウンタが管理される。
- ある DLL がプロセスに対して最初にロード (
LoadLibrary
) されたタイミングでDllMain
のPROCESS_ATTACH
が走り、参照カウンタは 1 になる。 - 同じプロセスに対して同じ DLL がロードされても、実際にはロードは行われることは無く、参照カウンタが +1 されるだけ。
- DLL がアンロード (
FreeLibrary
) されると参照カウンタが -1 される。参照カウントが 0 にならなければ実際にはアンロードされることはない。 - 参照カウンタが 0 になったタイミングで
DllMain
のPROCESS_DETACH
が走り、実際にアンロードされる。
注意点
- DLL のメモリ空間はプロセス中に1つだけとなる。DLL の中でグローバル変数を使用していると、プロセス単位で値が共有される点に注意。
-
DllMain
の中で行える処理には制限が掛かる。(特にサブスレッド操作は行ってはいけない) - DLlの参照カウンタは、
GetModuleHandleEx
などの一部の Win32API を呼び出すことでも +1 されることに注意。- CRT の
_beginthreadex
を呼び出すと参照カウントが +1 される。- VS2013 以前に付属の CRT では +1 されないが、VS2015 以降に付属の CRT (Windows Kits の一部になった?) では、内部で
GetModuleHandleEx
を呼び出すことで +1 させている。- これは、DLL 内で生成したスレッドより先に DLL がアンロードされてしまわないようにガードする目的の模様。
- 公式リファレンスに記述が無い が、CRT のソースを見ると分かる。
- Win32API の
CreateThread
を直接使用した場合は +1 されない。- Windows では、
_beginthreadex
はCreateThread
のラッパー関数。スレッド処理で CRT 関数 を使えるような初期化処理が加えられている模様。
- Windows では、
- VS2013 以前に付属の CRT では +1 されないが、VS2015 以降に付属の CRT (Windows Kits の一部になった?) では、内部で
- CRT の
DLL の参照カウンタを確認するには
- WinDBG を使用すると確認できる。(プロセスにアタッチした状態で
!dlls
コマンドを打つ。)
Linux
-
dlopen
で同じライブラリを複数回ロードした場合、実際のロードは1回目だけとなり、2回目以降はロード済みのハンドルが返ってくる。 - 内部で参照カウンタは増加しているため、同じ回数だけ
dlclose
の呼び出しが必要。 - これは同一プロセス内の話だが、同一プロセスでロードした2種類の共有ライブラリそれぞれから、更に別の共有ライブラリをロードした場合にも適用される?(未確認)
Discussion