C++ で DLL を作成し、重要なデストラクタを使用してクラスのグローバル オブジェクトを宣言するとします。DLL がアンロードされるときにデストラクタが呼び出されますか?
6 に答える
Windows C++ DLL では、すべてのグローバル オブジェクト (クラスの静的メンバーを含む) は、DLL_PROCESS_ATTACH で DllMain を呼び出す直前に構築され、DLL_PROCESS_DETACH で DllMain を呼び出した直後に破棄されます。
ここで、次の 3 つの問題を考慮する必要があります。
0 - もちろん、グローバルな non-const オブジェクトは悪です (ただし、既にご存知のとおり、マルチスレッド、ロック、god-object などについては触れません)。
1 - オブジェクトまたは異なるコンパイル単位 (つまり、CPP ファイル) の構築順序は保証されていないため、2 つのオブジェクトが 2 つの異なる CPP でインスタンス化されている場合、オブジェクト A が B の前に構築されることは期待できません。これは、B が A に依存している場合に重要です。解決策は、すべてのグローバル オブジェクトを同じ CPP ファイルに移動することです。同じコンパイル ユニット内では、オブジェクトのインスタンス化の順序は構築の順序になります (および順序の逆になります)。破壊の)
2 - DllMain で禁止されていることがあります。これらのことは、おそらくコンストラクターでも禁止されています。したがって、何かをロックすることは避けてください。この件に関する Raymond Chen の優れたブログを参照してください。
- DllMain で恐ろしいことをしてはいけないいくつかの理由
- DllMain で恐ろしいことをしてはいけないもう 1 つの理由: 不注意によるデッドロック
- DllMain で恐ろしいことをしてはいけない理由、パート 3
この場合、遅延初期化が興味深い可能性があります。クラスは、メソッドのいずれかを呼び出すまで、「初期化されていない」状態 (内部ポインターは NULL、ブール値は false など) のままです。これらのオブジェクトをメイン (またはメインの子孫関数の 1 つ) 内で使用する場合は、DllMain の実行後に呼び出されるため問題ありません。
3 - もちろん、DLL A の一部のグローバル オブジェクトが DLL B のグローバル オブジェクトに依存している場合は、DLL の読み込み順序、つまり依存関係について非常に注意する必要があります。この場合、直接的または間接的な循環依存関係を持つ DLL は、非常に頭の痛い問題を引き起こします。最善の解決策は、循環依存関係を断ち切ることです。
PS: C++ では、コンストラクターがスローされる可能性があり、DLL の読み込み中に例外が発生することは望ましくないことに注意してください。そのため、非常に正当な理由がない限り、グローバル オブジェクトが例外を使用しないようにしてください。正しく記述されたデストラクタはスローする権限がないため、この場合、DLL のアンロードは問題ありません。
Microsoft のこのページでは、DLL の初期化とグローバルの破棄の詳細について説明しています:
http://msdn.microsoft.com/en-us/library/988ye33t.aspx
.dll をリンクするときに実行される実際のコードを確認するには、.dll を参照してください%ProgramFiles%\Visual Studio 8\vc\crt\src\dllcrt0.c
。
検査から、デストラクタは_cexit()
、dll CRT によって維持される内部参照カウントがゼロに達すると呼び出されます。
アプリケーションが終了するか、DLL がアンロードされたときのいずれか早い方で呼び出す必要があります。これは、コンパイルする実際のランタイムに多少依存することに注意してください。
また、タイミングと順序付けの問題があるため、重要なデストラクタには注意してください。デストラクタが依存する DLL の後に DLLがアンロードされる可能性があり、明らかに問題が発生します。
Windows では、拡張子が *.exe、*.dll のバイナリ イメージ ファイルはPE 形式です 。このようなファイルにはエントリ ポイントがあります。次のような dumpbin ツールで表示できます
dumpbin /headers dllname.dll
Microsoft の C ランタイムを使用する場合、エントリ ポイントは *CRTStartup または *DllMainCRTStartup のようになります。
このような関数は、c および c++ ランタイムの初期化を実行し、それぞれ (main、WinMain) または DllMain に実行を委譲します。
Microsoft の VC コンパイラを使用している場合は、VC ディレクトリにあるこの関数のソース コードを確認できます。
- crt0.c
- dllcrt0.c
DllMainCRTStartup プロセスは、dll のアンロード中に通知 DLL_PROCESS_DETACH を取得するときに、通常のシナリオで .data セクションからグローバル変数を初期化/初期化解除する必要があります。例えば:
- プログラムの起動スレッドの main または WinMain が制御フローを返す
- 明示的に FreeLibrary を呼び出し、use-dll-counter がゼロである
fdwReason = DLL_PROCESS_DETACH パラメータを指定して DllMain が呼び出されると、アプリケーションによって DLL がアンロードされます。これは、グローバル/静的オブジェクトのデストラクタが呼び出されるまでの時間です。