2012年12月9日總結:銷燬具有靜態存儲持續時間本地對象的
- 在運行作爲終結正常混合模式應用的全球本地C++析構函數。無法更改該行爲或關聯的超時。
- 混合模式程序集DLL在DLL加載/卸載期間運行C++構造函數/析構函數 - 與原生DLL完全一樣。
- 使用COM接口在本機中託管CLR可執行文件允許解構器在本地DLL(我期望的行爲)中運行,併爲終結器設置超時(額外獎勵)。
- 至於我可以告訴上述適用於至少Visual Studio 2008中,2010年和2012年(只有.NET 4測試)
實際CLR的託管可執行我打算使用是非常相似的那個在這個問題概述除了一些小的改動:
- 設置
OPR_FinalizerRun
到某一值(目前60秒但可能會更改)由漢斯帕桑特建議。 - 使用ATL COM智能指針類(這些都不是在Visual Studio的Express版本可用,所以我省略他們從這個職位)。
- Lodaing從
mscoree.dll
CLRCreateInstance
動態(以允許更好的錯誤消息沒有安裝兼容CLR時)。 - 傳遞命令行上從主機到指定
Main
功能在裝配DLL。
感謝所有花時間閱讀問題和/或評論的人。
2012年12月2日在更新後的底部。
我正在使用Visual Studio 2012與.NET 4混合模式C++/CLI應用程序,並驚訝地發現一些本地全局對象的析構函數沒有被調用。調查這個問題後發現,它們的行爲與this post中所述的被管理對象相似。
我很驚訝這種行爲(我理解它的管理對象),無法在C++/CLI standard和destructors and finalizers的描述中找到它的記錄。
按照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月2日:
至於我可以告訴的行爲似乎是如下:
- 在混合模式下的EXE文件,全球析構函數期間運行的終結DomainUnload ,無論它們是否置於本機代碼或CLR代碼。在Visual Studio 2008,2010和2012中就是這種情況。
- 在由本機應用程序託管的混合模式DLL中全局本機對象的析構函數在託管方法運行併發生所有其他清理後的DLL_PROCESS_DETACH 期間運行。它們與構造函數在同一個線程中運行,並且沒有與它們關聯的超時(期望的行爲)。正如預期的那樣,可以使用
ICLRPolicyManager->SetTimeout(OPR_ProcessExit, <timeout>)
來控制全球管理對象(放置在使用/clr
編譯的文件中的非引用類)的時間析構函數。
Hazarding猜測,我認爲全球的默認構造函數/析構函數「正常」的原因(定義爲表現爲我所期望的)在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++ - > General - > Common Language RunTime支持更改爲「No Common Language RunTime Support」
- Project - > Properties - > Debugging。將「Command」指向CLRhost.exe,將「Command Arguments」改爲「$(TargetPath)」,包括引號,「調試器類型」改爲「Mixed」
- 構建和調試。
在全球的析構函數中放置一個斷點提供了以下堆棧跟蹤:
> 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
[email protected]() + 0x136 bytes
[email protected]() + 0xad bytes
[email protected]() + 0x14 bytes
[email protected]() + 0x141 bytes
[email protected]() + 0x74 bytes
kernel32.dll!74e37a0d()
mscoreei.dll!RuntimeDesc::ShutdownAllActiveRuntimes() + 0x10e bytes
[email protected]() + 0x27 bytes
[email protected]() + 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
[email protected]@12() + 0x12 bytes
[email protected]() + 0x27 bytes
[email protected]() + 0x1b bytes
運行作爲一個獨立的可執行文件,我得到一個堆棧跟蹤,這是非常類似於漢斯帕桑特觀察到的(雖然它不使用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++
[email protected]@12() + 0x12 bytes
[email protected]() + 0x27 bytes
[email protected]() + 0x1b bytes
您是相當套牢CLRHost.exe,你的程序的入口點必須在非託管允許加載CLR和重新配置它。如果單個可執行文件非常重要,可以使用IHostAssemblyStore從嵌入在EXE中的資源加載程序集。很難通過簡單的設置使其具有競爭力。EXE –
@HansPassant:一個EXE是不是我的主要關注點(即提示我的問題已經取決於幾個DLL文件的實際應用程序),我感興趣的是它的原生部分表現或多或少像它會在一個標準的原生應用程序(並沒有主機EXE會減輕調試等)。大多數情況下,我只想知道在哪裏我可以讀取爲什麼它的行爲有所不同,取決於它的加載/託管方式。我對使用一種方法感到不自在,我不知道詳細信息(可能CRT /編譯器的更新突然使託管的場景像EXE一樣?) – user786653
我沒有得到的是,在獨立的EXE中,析構函數運行作爲ProcessExit期間的終結器,但在宿主方案中,析構函數在DLL卸載期間運行(ExecuteInDefaultAppDomain返回並且clrhost繼續運行,而不運行全局析構函數),即它不作爲終結器運行(據我所知)。 – user786653