💉

共有ライブラリ(DLL)を複数回ロードするとどうなるか

2023/07/01に公開

概要

同じ共有ライブラリ(DLL)を複数回ロードしても DLL のインスタンスは 1 つしか作られません。実際には最初の1回目しかロードされず、2回目以降は参照カウンタが増えるだけ。

そのあたりの挙動でハマったのでメモ。主に Win32API/MSVC の話です。

Windows

  • ロード/アンロードは LoadLibrary / FreeLibrary を使う。2回目以降のロード/アンロードのペアは GetModuleHandleEx / CloseHandle を使うこともできる。
  • 2回目以降で GetModuleHandle を使うことで参照カウンタを増やさずにロード済み DLL のハンドルを得ることもできる。

詳細

DLL がロードされると、プロセス単位で参照カウンタが管理される。

  • ある DLL がプロセスに対して最初にロード (LoadLibrary) されたタイミングで DllMainPROCESS_ATTACH が走り、参照カウンタは 1 になる。
  • 同じプロセスに対して同じ DLL がロードされても、実際にはロードは行われることは無く、参照カウンタが +1 されるだけ。
  • DLL がアンロード (FreeLibrary) されると参照カウンタが -1 される。参照カウントが 0 にならなければ実際にはアンロードされることはない。
  • 参照カウンタが 0 になったタイミングで DllMainPROCESS_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 では、_beginthreadexCreateThread のラッパー関数。スレッド処理で CRT 関数 を使えるような初期化処理が加えられている模様。

DLL の参照カウンタを確認するには

  • WinDBG を使用すると確認できる。(プロセスにアタッチした状態で !dlls コマンドを打つ。)

Linux

  • dlopen で同じライブラリを複数回ロードした場合、実際のロードは1回目だけとなり、2回目以降はロード済みのハンドルが返ってくる。
  • 内部で参照カウンタは増加しているため、同じ回数だけ dlclose の呼び出しが必要。
  • これは同一プロセス内の話だが、同一プロセスでロードした2種類の共有ライブラリそれぞれから、更に別の共有ライブラリをロードした場合にも適用される?(未確認)

Discussion