2012-12-09 まとめ:
- 通常の混合モード アプリケーションでは、グローバル ネイティブ C++ デストラクタがファイナライザとして実行されます。その動作または関連するタイムアウトを変更することはできません。
- 混合モードのアセンブリ DLL は、DLL のロード/アンロード中に、ネイティブ DLL とまったく同じように、C++ コンストラクター/デストラクタを実行します。
- COM インターフェイスを使用してネイティブ実行可能ファイルで CLR をホストすると、両方のデコンストラクターがネイティブ DLL のように動作し (私が望む動作)、ファイナライザーのタイムアウトを設定することができます (追加ボーナス)。
- 私が知る限り、上記は少なくとも Visual Studio 2008、2010、および 2012 に適用されます。 (.NET 4 でのみテスト済み)
私が使用する予定の実際の CLR ホスティング実行可能ファイルは、いくつかの小さな変更を除いて、この質問で概説されているものと非常によく似ています。
OPR_FinalizerRun
Hans Passant の提案に従って、ある値 (現在は 60 秒ですが、変更される可能性があります) に設定します。- ATL COM スマート ポインター クラスを使用します (これらは Visual Studio のエクスプレス エディションでは使用できないため、この投稿では省略しました)。
CLRCreateInstance
動的に読み込みmscoree.dll
ます (互換性のある CLR がインストールされていない場合に、より適切なエラー メッセージを表示できるようにするため)。- コマンド ラインをホストから
Main
アセンブリ DLL 内の指定された関数に渡します。
質問やコメントを読んでくれたすべての人に感謝します。
2012-12-02 記事の下部を更新。
Visual Studio 2012 と .NET 4 を使用して混合モードの C++/CLI アプリケーションに取り組んでいますが、ネイティブ グローバル オブジェクトの一部のデストラクタが呼び出されていないことに驚きました。この問題を調査すると、この投稿で説明されているように、管理対象オブジェクトのように動作することが判明しました。
私はこの動作に非常に驚きました (マネージド オブジェクトの場合は理解しています) 。C++/CLI 標準にも、デストラクタとファイナライザの説明にも、どこにも文書化されていませんでした。
Hans Passantによるコメントの提案に従って、私はプログラムをアセンブリ DLL としてコンパイルし、それを小さなネイティブ実行可能ファイルでホストしました。構築)!
私の質問:
- スタンドアロンの実行可能ファイルで同じ動作をすることはできますか?
- (1) が実行可能でない場合、実行可能ファイルのプロセス タイムアウト ポリシー (つまり、基本的に呼び出し
ICLRPolicyManager->SetTimeout(OPR_ProcessExit, INFINITE)
) を構成することは可能ですか? これは許容できる回避策です。 - これはどこに文書化されていますか / このトピックについてもっと自分自身を教育するにはどうすればよいですか? 変化しやすい行動に頼りたくありません。
再現するには、以下のファイルを次のようにコンパイルします。
cl /EHa /MDd CLRHost.cpp
cl /EHa /MDd /c Native.cpp
cl /EHa /MDd /c /clr CLR.cpp
link /out:CLR.exe Native.obj CLR.obj
link /out:CLR.dll /DLL Native.obj CLR.obj
望ましくない動作:
C:\Temp\clrhost>clr.exe
[1210] Global::Global()
[d10] Global::~Global()
C:\Temp\clrhost>
ホストされた実行中:
C:\Temp\clrhost>CLRHost.exe clr.dll
[1298] Global::Global()
2a returned.
[1298] Global::~Global()
[1298] Global::~Global() - Done!
C:\Temp\clrhost>
使用ファイル:
// CLR.cpp
public ref class T {
static int M(System::String^ arg) { return 42; }
};
int main() {}
// Native.cpp
#include <windows.h>
#include <iostream>
#include <iomanip>
using namespace std;
struct Global {
Global() {
wcout << L"[" << hex << GetCurrentThreadId() << L"] Global::Global()" << endl;
}
~Global() {
wcout << L"[" << hex << GetCurrentThreadId() << L"] Global::~Global()" << endl;
Sleep(3000);
wcout << L"[" << hex << GetCurrentThreadId() << L"] Global::~Global() - Done!" << endl;
}
} g;
// CLRHost.cpp
#include <windows.h>
#include <metahost.h>
#pragma comment(lib, "mscoree.lib")
#include <iostream>
#include <iomanip>
using namespace std;
int wmain(int argc, const wchar_t* argv[])
{
HRESULT hr = S_OK;
ICLRMetaHost* pMetaHost = 0;
ICLRRuntimeInfo* pRuntimeInfo = 0;
ICLRRuntimeHost* pRuntimeHost = 0;
wchar_t version[MAX_PATH];
DWORD versionSize = _countof(version);
if (argc < 2) {
wcout << L"Usage: " << argv[0] << L" <assembly.dll>" << endl;
return 0;
}
if (FAILED(hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost)))) {
goto out;
}
if (FAILED(hr = pMetaHost->GetVersionFromFile(argv[1], version, &versionSize))) {
goto out;
}
if (FAILED(hr = pMetaHost->GetRuntime(version, IID_PPV_ARGS(&pRuntimeInfo)))) {
goto out;
}
if (FAILED(hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pRuntimeHost)))) {
goto out;
}
if (FAILED(hr = pRuntimeHost->Start())) {
goto out;
}
DWORD dwRetVal = E_NOTIMPL;
if (FAILED(hr = pRuntimeHost->ExecuteInDefaultAppDomain(argv[1], L"T", L"M", L"", &dwRetVal))) {
wcerr << hex << hr << endl;
goto out;
}
wcout << dwRetVal << " returned." << endl;
if (FAILED(hr = pRuntimeHost->Stop())) {
goto out;
}
out:
if (pRuntimeHost) pRuntimeHost->Release();
if (pRuntimeInfo) pRuntimeInfo->Release();
if (pMetaHost) pMetaHost->Release();
return hr;
}
2012-12-02 :
私が知る限り、動作は次のようです。
- 混合モードの EXE ファイルでは、グローバル デストラクタは、ネイティブ コードまたは CLR コードのどちらに配置されているかに関係なく、 DomainUnload 中にファイナライザとして実行されます。これは、Visual Studio 2008、2010、および 2012 の場合です。
- ネイティブ アプリケーションによってホストされる混合モード DLL では、マネージ メソッドが実行され、他のすべてのクリーンアップが発生した後、グローバル ネイティブ オブジェクトのデストラクタが DLL_PROCESS_DETACH 中に実行されます。それらはコンストラクターと同じスレッドで実行され、それらに関連付けられたタイムアウトはありません (望ましい動作)。予想どおり、グローバルマネージド オブジェクト( でコンパイルされたファイルに配置された非参照クラス
/clr
) の時間デストラクタは、 を使用して制御できますICLRPolicyManager->SetTimeout(OPR_ProcessExit, <timeout>)
。
推測を危険にさらすと、DLL シナリオでグローバル ネイティブ コンストラクター/デストラクタが "正常に" (期待どおりに動作するように定義されている) 機能する理由は、ネイティブ関数の使用LoadLibrary
と使用を許可するためだと思いGetProcAddress
ます。したがって、予見可能な将来に変更されないことに依存することは比較的安全であると予想しますが、いずれにしても、公式の情報源/ドキュメントから何らかの確認/拒否があることを感謝します.
更新 2 :
Visual Studio 2012 (エクスプレス バージョンとプレミアム バージョンでテスト済みですが、残念ながら、このマシンで以前のバージョンにアクセスすることはできません)。コマンド ラインでも同じように動作するはずですが (上で概説したようにビルドします)、IDE 内から再現する方法を次に示します。
CLRHost.exe のビルド:
- ファイル -> 新しいプロジェクト
- Visual C++ -> Win32 -> Win32 コンソール アプリケーション (プロジェクトに「CLRHost」という名前を付けます)
- アプリケーション設定 -> 追加オプション -> 空のプロジェクト
- 「完了」を押します
- ソリューション エクスプローラーで [ソース ファイル] を右クリックします。追加 -> 新しい項目 -> Visual C++ -> C++ ファイル。CLRHost.cpp という名前を付けて、投稿から CLRHost.cpp の内容を貼り付けます。
- プロジェクト -> プロパティ。[構成プロパティ] -> [C/C++] -> [コード生成] -> [C++ 例外を有効にする] を [SEH 例外 (/EHa) を使用してはい] に変更し、[基本的なランタイム チェック] を [デフォルト] に変更します。
- 建てる。
CLR.DLL のビルド:
- ファイル -> 新しいプロジェクト
- Visual C++ -> CLR -> クラス ライブラリ (プロジェクトに「CLR」という名前を付けます)
- 自動生成されたすべてのファイルを削除します
- プロジェクト -> プロパティ。構成プロパティ -> C/C++ -> プリコンパイル済みヘッダー -> プリコンパイル済みヘッダー。「プリコンパイル済みヘッダーを使用しない」に変更します。
- ソリューション エクスプローラーで [ソース ファイル] を右クリックします。追加 -> 新しい項目 -> Visual C++ -> C++ ファイル。CLR.cpp という名前を付けて、投稿から CLR.cpp の内容を貼り付けます。
- Native.cpp という名前の新しい C++ ファイルを追加し、投稿からコードを貼り付けます。
- ソリューション エクスプローラーで "Native.cpp" を右クリックし、プロパティを選択します。C/C++ -> 一般 -> 共通言語ランタイム サポートを「共通言語ランタイム サポートなし」に変更します。
- プロジェクト -> プロパティ -> デバッグ。"Command" を CLRhost.exe を指すように変更し、"Command Arguments" を引用符を含めて "$(TargetPath)" に変更し、"Debugger Type" を "Mixed" に変更します。
- ビルドしてデバッグします。
Global のデストラクタにブレークポイントを配置すると、次のスタック トレースが得られます。
> clr.dll!Global::~Global() Line 11 C++
clr.dll!`dynamic atexit destructor for 'g''() + 0xd bytes C++
clr.dll!_CRT_INIT(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 416 C
clr.dll!__DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 522 + 0x11 bytes C
clr.dll!_DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 472 + 0x11 bytes C
mscoreei.dll!__CorDllMain@12() + 0x136 bytes
mscoree.dll!_ShellShim__CorDllMain@12() + 0xad bytes
ntdll.dll!_LdrpCallInitRoutine@16() + 0x14 bytes
ntdll.dll!_LdrShutdownProcess@0() + 0x141 bytes
ntdll.dll!_RtlExitUserProcess@4() + 0x74 bytes
kernel32.dll!74e37a0d()
mscoreei.dll!RuntimeDesc::ShutdownAllActiveRuntimes() + 0x10e bytes
mscoreei.dll!_CorExitProcess@4() + 0x27 bytes
mscoree.dll!_ShellShim_CorExitProcess@4() + 0x94 bytes
msvcr110d.dll!___crtCorExitProcess() + 0x3a bytes
msvcr110d.dll!___crtExitProcess() + 0xc bytes
msvcr110d.dll!__unlockexit() + 0x27b bytes
msvcr110d.dll!_exit() + 0x10 bytes
CLRHost.exe!__tmainCRTStartup() Line 549 C
CLRHost.exe!wmainCRTStartup() Line 377 C
kernel32.dll!@BaseThreadInitThunk@12() + 0x12 bytes
ntdll.dll!___RtlUserThreadStart@8() + 0x27 bytes
ntdll.dll!__RtlUserThreadStart@8() + 0x1b bytes
スタンドアロンの実行可能ファイルとして実行すると、Hans Passant によって観察されたものと非常によく似たスタック トレースが得られます (ただし、CRT のマネージド バージョンは使用されていません)。
> clrexe.exe!Global::~Global() Line 10 C++
clrexe.exe!`dynamic atexit destructor for 'g''() + 0xd bytes C++
msvcr110d.dll!__unlockexit() + 0x1d3 bytes
msvcr110d.dll!__cexit() + 0xe bytes
[Managed to Native Transition]
clrexe.exe!<CrtImplementationDetails>::LanguageSupport::_UninitializeDefaultDomain(void* cookie) Line 577 C++
clrexe.exe!<CrtImplementationDetails>::LanguageSupport::UninitializeDefaultDomain() Line 594 + 0x8 bytes C++
clrexe.exe!<CrtImplementationDetails>::LanguageSupport::DomainUnload(System::Object^ source, System::EventArgs^ arguments) Line 628 C++
clrexe.exe!<CrtImplementationDetails>::ModuleUninitializer::SingletonDomainUnload(System::Object^ source, System::EventArgs^ arguments) Line 273 + 0x6e bytes C++
kernel32.dll!@BaseThreadInitThunk@12() + 0x12 bytes
ntdll.dll!___RtlUserThreadStart@8() + 0x27 bytes
ntdll.dll!__RtlUserThreadStart@8() + 0x1b bytes