2012-11-29 32 views
24

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.dllCLRCreateInstance動態(以允許更好的錯誤消息沒有安裝兼容CLR時)。
  • 傳遞命令行上從主機到指定Main功能在裝配DLL。

感謝所有花時間閱讀問題和/或評論的人。


2012年12月2日在更新後的底部。

我正在使用Visual Studio 2012與.NET 4混合模式C++/CLI應用程序,並驚訝地發現一些本地全局對象的析構函數沒有被調用。調查這個問題後發現,它們的行爲與this post中所述的被管理對象相似。

我很驚訝這種行爲(我理解它的管理對象),無法在C++/CLI standarddestructors and finalizers的描述中找到它的記錄。

按照Hans Passant的意見,我將程序編譯爲一個程序集DLL,並將它存放在一個小的本地可執行文件中,它給我所需的行爲(給予足夠的時間在同一線程中完成並運行的析構函數因爲他們被構造)!

我的問題:

  1. 我可以在獨立的可執行文件相同的行爲?
  2. 如果(1)是不可行的是有可能配置爲可執行的處理超時策略(即基本上主叫ICLRPolicyManager->SetTimeout(OPR_ProcessExit, INFINITE))?這將是一個可接受的解決方法。
  3. 這是在哪裏記錄/我如何才能更多地瞭解這個話題?我寧願不依賴可能改變的行爲。

要重現編譯如下文件內容如下:

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中的方案是,允許在本地函數使用LoadLibraryGetProcAddress。因此,我希望在可預見的將來依靠它不會發生變化是相對安全的,但希望以任何方式從官方來源/文件中得到某種確認/否認。

更新2

在Visual Studio 2012(與快遞和高級版本測試,我很遺憾沒有獲得早期版本的這臺機器上)。它應該在命令行上以相同的方式工作(如上所述構建),但是這裏是如何從IDE內重現的。

大廈CLRHost.exe:

  1. 文件 - >新建項目
  2. 的Visual C++ - > Win32的 - > Win32控制檯應用程序(將項目命名爲 「CLRHost」)
  3. 應用程序設置 - >附加選項 - >空項目
  4. 按「完成」
  5. 右鍵單擊解決方案資源管理器中的源文件。添加 - >新建項目 - > Visual C++ - > C++文件。將其命名爲CLRHost.cpp並從帖子中粘貼CLRHost.cpp的內容。
  6. 項目 - >屬性。配置屬性 - > C/C++ - >代碼生成 - >將「啓用C++異常」更改爲「有SEH異常(/ EHa)」和「基本運行時檢查」爲「默認」
  7. 構建。

大廈CLR.DLL:

  1. 文件 - >新建項目
  2. 的Visual C++ - > CLR - >類庫(將項目命名爲 「CLR」)
  3. 刪除所有自動生成的文件
  4. 項目 - >屬性。配置屬性 - > C/C++ - >預編譯頭文件 - >預編譯頭文件。更改爲「不使用預編譯頭」。
  5. 右鍵單擊解決方案資源管理器中的源文件。添加 - >新建項目 - > Visual C++ - > C++文件。將其命名爲CLR.cpp並從帖子中粘貼CLR.cpp的內容。
  6. 添加一個名爲Native.cpp的新C++文件並粘貼帖子中的代碼。
  7. 右鍵單擊解決方案資源管理器中的「Native.cpp」,然後選擇屬性。將C/C++ - > General - > Common Language RunTime支持更改爲「No Common Language RunTime Support」
  8. Project - > Properties - > Debugging。將「Command」指向CLRhost.exe,將「Command Arguments」改爲「$(TargetPath)」,包括引號,「調試器類型」改爲「Mixed」
  9. 構建和調試。

在全球的析構函數中放置一個斷點提供了以下堆棧跟蹤:

> 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  
+1

您是相當套牢CLRHost.exe,你的程序的入口點必須在非託管允許加載CLR和重新配置它。如果單個可執行文件非常重要,可以使用IHostAssemblyStore從嵌入在EXE中的資源加載程序集。很難通過簡單的設置使其具有競爭力。EXE –

+0

@HansPassant:一個EXE是不是我的主要關注點(即提示我的問題已經取決於幾個DLL文件的實際應用程序),我感興趣的是它的原生部分表現或多或少像它會在一個標準的原生應用程序(並沒有主機EXE會減輕調試等)。大多數情況下,我只想知道在哪裏我可以讀取爲什麼它的行爲有所不同,取決於它的加載/託管方式。我對使用一種方法感到不自在,我不知道詳細信息(可能CRT /編譯器的更新突然使託管的場景像EXE一樣?) – user786653

+0

我沒有得到的是,在獨立的EXE中,析構函數運行作爲ProcessExit期間的終結器,但在宿主方案中,析構函數在DLL卸載期間運行(ExecuteInDefaultAppDomain返回並且clrhost繼續運行,而不運行全局析構函數),即它不作爲終結器運行(據我所知)。 – user786653

回答

8

獲取簡單的問題的方式進行第一:

CLR自定義的好資源是Steven Pratschner's book「自定義Microsoft .NET Framework公共語言運行時」。請注意,它已過時,.NET 4.0中的主機接口已更改。 MSDN並沒有多說這個,但是託管接口已經有很好的文檔了。

通過更改調試器設置,將Type從「Auto」更改爲「Managed」或「Mixed」,可以使調試更簡單。

請注意,您的3000毫秒睡眠只是在邊緣,您應該測試5000毫秒。如果C++類出現在使用/ clr生效的代碼中,即使使用了#pragma unmanaged,那麼您將需要重寫終結器線程超時。經測試在.NET 3.5 SP1的CLR版本,下面的代碼行之有效給析構函數足夠的時間來完成運行:

ICLRControl* pControl; 
if (FAILED(hr = pRuntimeHost->GetCLRControl(&pControl))) { 
    goto out; 
} 
ICLRPolicyManager* pPolicy; 
if (FAILED(hr = pControl->GetCLRManager(__uuidof(ICLRPolicyManager), (void**)&pPolicy))) { 
    goto out; 
} 
hr = pPolicy->SetTimeout(OPR_FinalizerRun, 60000); 
pPolicy->Release(); 
pControl->Release(); 

我拿起一分鐘作爲一個合理的時間,必要時調整。請注意,MSDN文檔有一個錯誤,它不會將OPR_FinalizerRun顯示爲允許的值,但它確實可以正常工作。設置終結器線程超時還可以確保託管終結器在間接破壞非託管C++類(一種非常常見的場景)時不會超時。

當您使用/ clr編譯的CLRHost運行此代碼時,您會看到一件事,即對GetCLRManager()的調用將失敗,並返回HOST_E_INVALIDOPERATION返回碼。已加載以執行CLRHost.exe的默認CLR主機不會讓您覆蓋該策略。所以你很抱歉擁有一個專用的EXE來託管CLR。

當我測試了這款具有CLRHost加載一個混合模式的組裝,調用堆棧看起來像這樣的析構函數設置斷點時:

CLRClient.dll!Global::~Global() Line 24 C++ 
[Managed to Native Transition] 
CLRClient.dll!<Module>[email protected]@YMXXZ() + 0x1b bytes  
CLRClient.dll!_exit_callback() Line 449 C++ 
CLRClient.dll!<CrtImplementationDetails>::LanguageSupport::_UninitializeDefaultDomain(void* cookie = <undefined value>) Line 753 C++ 
CLRClient.dll!<CrtImplementationDetails>::LanguageSupport::UninitializeDefaultDomain() Line 775 + 0x8 bytes C++ 
CLRClient.dll!<CrtImplementationDetails>::LanguageSupport::DomainUnload(System::Object^ source = 0x027e1274, System::EventArgs^ arguments = <undefined value>) Line 808 C++ 
msvcm90d.dll!<CrtImplementationDetails>.ModuleUninitializer.SingletonDomainUnload(object source = {System.AppDomain}, System.EventArgs arguments = null) + 0xa1 bytes 
    // Rest omitted 

請注意,這是不同於你的問題你的意見。該代碼由CRT的管理版本(msvcm90.dll)觸發。此代碼在專用線程上運行,由CLR啓動以卸載AppDomain。你可以在vc/crt/src/mstartup.cpp源代碼文件中看到這個源代碼。


第二種情況當C++類是無/ CLR有效編譯並得到了鏈接到混合模式組件的源代碼文件的一部分發生。編譯器然後使用正常的atexit()處理程序來調用析構函數,就像通常在非託管可執行文件中執行的一樣。在這種情況下,DLL在程序終止時被Windows卸載並且CRT的管理版本關閉。

值得注意的是,這發生在之後 CLR被關閉,並且析構函數在程序的啓動線程上運行。因此,CLR超時不在圖片中,並且析構函數可以花費盡可能長的時間。堆棧跟蹤的本質是現在:

CLRClient.dll!Global::~Global() Line 12 C++ 
CLRClient.dll!`dynamic atexit destructor for 'g''() + 0xd bytes C++ 
    // Confusingly named functions elided 
    //... 
CLRHost.exe!__crtExitProcess(int status=0x00000000) Line 732 C 
CLRHost.exe!doexit(int code=0x00000000, int quick=0x00000000, int retcaller=0x00000000) Line 644 + 0x9 bytes C 
CLRHost.exe!exit(int code=0x00000000) Line 412 + 0xd bytes C 
    // etc.. 

但是,這是當啓動EXE的非託管只會出現極端情況。一旦EXE被管理,它將運行AppDomain.Unload上的析構函數,即使它們出現在編譯時沒有/ clr的代碼中。所以你仍然有超時問題。有一個非託管的EXE並不是很少見,例如當你加載[ComVisible]託管代碼時會發生這種情況。但是,這聽起來不像你的情況,你堅持使用CLRHost。

+0

謝謝你的回覆。我沒有主動更改託管示例中的超時,這似乎只是因爲在DLL卸載期間運行析構函數才起作用,儘管設置它允許託管析構函數運行時間更長(如預期的那樣),並將超時設置爲例如10 ms仍然允許析構函數運行。儘管你似乎是C++/CLI的最重要的權威(至少在SO上),但我計劃更新這個問題並提供一個獎勵,希望能夠接近更令人滿意的解決方案。 – user786653

+0

哦,我完全讀過你,實際上並沒有使用ICLRPolicyManager。這將需要一些挖掘,爲賞金。 –

+0

我更新了問題。我還安裝了Visual C++ 2010 Express,其行爲與VC++ 2012相同,具有非常相似的調用堆棧。 – user786653

1

要回答「哪裏是這個記錄/我怎樣才能更多教育自己的話題?」問題:如果你從http://www.microsoft.com/en-us/download/details.aspx?id=4917下載並查看Shared Source Common Language Infrastructure(又名SSCLI),你可以理解它是如何工作的(或者至少用於框架2)。

一旦你解壓縮文件,你會發現在gcEE.ccp(「垃圾收集執行引擎」)這樣的:

#define FINALIZER_TOTAL_WAIT 2000 

至極定義爲2秒這個著名的默認值。您還可以在同一文件中看到:

BOOL GCHeap::FinalizerThreadWatchDogHelper() 
{ 
    // code removed for brevity ... 
    DWORD totalWaitTimeout; 
    totalWaitTimeout = GetEEPolicy()->GetTimeout(OPR_FinalizerRun); 
    if (totalWaitTimeout == (DWORD)-1) 
    { 
     totalWaitTimeout = FINALIZER_TOTAL_WAIT; 
    } 

會告訴你執行引擎將服從OPR_FinalizerRun政策,如果定義,這對應於EClrOperation Enumeration價值。 GetEEPolicy定義於eePolicy.h & eePolicy.cpp

+0

感謝您的回覆。我已經(簡單地)看過SSCLI,但據我所知,託管代碼和本地代碼之間的相互作用似乎完全由CRT決定。即在AppDomain卸載期間或卸載DLL期間,本機全局對象的析構函數是否作爲終結器運行不依賴於CLR。 – user786653