使用静态存储期间销毁本机对象

时间:2022-09-01 18:36:39

2012-12-09 Summary:

2012-12-09总结:

  • In a normal mixed-mode application global native C++ destructors run as finalizers. It's not possible to change that behavior or the associated timeout.
  • 在正常的混合模式应用程序中,全局本机c++析构函数作为终结器运行。不可能更改该行为或相关超时。
  • A mixed-mode assembly DLL runs C++ constructors/destructors during DLL load/unload - exactly as a native DLL.
  • 混合模式的程序集DLL在DLL加载/卸载期间运行c++构造函数/析构函数—与本机DLL完全相同。
  • Hosting the CLR in a native executable using the COM interface allows both the deconstructors to behave as in a native DLL (the behavior I desire) and setting the timeout for finalizers (an added bonus).
  • 使用COM接口将CLR托管在本地可执行文件中,可以让解构程序在本地DLL中运行(我希望的行为),并为终结器设置超时(附加的好处)。
  • As far as I can tell the above applies to at least Visual Studio 2008, 2010 and 2012. (Only tested with .NET 4)
  • 就我所知,至少在Visual Studio 2008, 2010和2012上都是如此。(仅用。net 4测试)

The actual CLR hosting executable I plan on using is very similar to the one outlined in this question except for a few minor changes:

我计划使用的实际CLR托管可执行文件与本问题中概述的非常相似,除了几个小的更改:

  • Setting OPR_FinalizerRun to some value (60 seconds currently, but subject to change) as suggested by Hans Passant.
  • 按照Hans Passant的建议,将OPR_FinalizerRun设置为某个值(当前为60秒,但可能会更改)。
  • Using the ATL COM smart pointer classes (these aren't available in the express editions of Visual Studio, so I omitted them from this post).
  • 使用ATL COM智能指针类(这些在Visual Studio的express版本中是不可用的,因此我将它们从本文中删除)。
  • Lodaing CLRCreateInstance from mscoree.dll dynamically (to allow better error messages when no compatible CLR is installed).
  • 从mscoree Lodaing CLRCreateInstance。动态dll(在没有安装兼容CLR时允许更好的错误消息)。
  • Passing the command line on from the host to the designated Main function in the assembly DLL.
  • 将命令行从主机上传递到程序集DLL中指定的主函数。

Thanks to all who took the time to read the question and/or comment.

感谢所有花时间阅读问题和/或评论的人。


2012-12-02 Update at the bottom of the post.

2012-12-02更新贴底。

I'm working on a mixed mode C++/CLI application using Visual Studio 2012 with .NET 4 and was surprised to discover that the destructors for some of the native global objects weren't getting called. Investigating the issue it turns out that they behave like managed objects as explained in this post.

我正在使用。net 4的Visual Studio 2012开发一个混合模式的c++ /CLI应用程序,我惊讶地发现一些本地全局对象的析构函数没有被调用。调查这个问题的结果是,他们的行为就像这篇文章中解释的管理对象一样。

I was quite surprised by this behavior (I understand it for managed objects) and couldn't find it documented anywhere, neither in the C++/CLI standard nor in the description of destructors and finalizers.

我对这种行为感到非常惊讶(我对托管对象理解),并且在任何地方都找不到它的文档,无论是在c++ /CLI标准中,还是在析构函数和终结器的描述中。

Following the suggestion in a comment by Hans Passant, I compiled the programs as an assembly DLL and hosted it in a small native executable and that does give me the desired behavior (destructors given ample time to finish and running in the same thread as they were constructed)!

按照Hans Passant的注释中的建议,我将程序编译为一个程序集DLL,并将其托管在一个小型的本地可执行文件中,这确实给了我所需的行为(析构函数有足够的时间来完成并在构建的线程中运行)!

My questions:

我的问题:

  1. Can I get the same behavior in an stand alone executable?
  2. 我能在一个单独的可执行文件中得到相同的行为吗?
  3. If (1) isn't feasible is it possible to configure the process timeout policy (i.e. basically calling ICLRPolicyManager->SetTimeout(OPR_ProcessExit, INFINITE)) for the executable? This would be an acceptable workaround.
  4. 如果(1)不可行,那么是否可以配置流程超时策略(即基本上调用ICLRPolicyManager->SetTimeout(OPR_ProcessExit, INFINITE))用于可执行文件?这将是一个可以接受的解决方案。
  5. Where is this documented / how can I educate myself more on the topic? I'd rather not rely on behavior that's liable to change.
  6. 这些文件在哪里?我如何才能在这个主题上进行更多的教育?我宁愿不要依赖那些容易改变的行为。

To reproduce compile the below files as follows:

复制编译下列文件如下:

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 

Unwanted behavior:

不受欢迎的行为:

C:\Temp\clrhost>clr.exe
[1210] Global::Global()
[d10] Global::~Global()

C:\Temp\clrhost>

Running hosted:

主持:

C:\Temp\clrhost>CLRHost.exe clr.dll
[1298] Global::Global()
2a returned.
[1298] Global::~Global()
[1298] Global::~Global() - Done!

C:\Temp\clrhost>

Used files:

使用文件:

// 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:
As far as I can tell the behavior seems to be as follows:

2012-12-02:据我所知,行为表现如下:

  • In a mixed-mode EXE file, global destructors are run as finalizers during DomainUnload regardless of whether they are placed in native code or CLR code. This is the case in Visual Studio 2008, 2010 and 2012.
  • 在混合模式EXE文件中,全局销毁程序在DomainUnload期间作为终结器运行,而不管它们是放在本地代码还是CLR代码中。这是Visual Studio 2008、2010和2012的情况。
  • In a mixed-mode DLL hosted by a native application destructors for global native objects are run during DLL_PROCESS_DETACH after the managed method has run and all other clean up has occurred. They run in the same thread as the constructor and there is no timeout associated with them (the desired behavior). As expected the time destructors of global managed objects (non-ref classes placed in files compiled with /clr) can be controlled using ICLRPolicyManager->SetTimeout(OPR_ProcessExit, <timeout>).
  • 在一个混合模式的DLL中,由本机应用程序析构函数承载的全局本机对象在DLL_PROCESS_DETACH过程中运行,在托管方法运行并发生所有其他清理之后。它们与构造函数运行在同一个线程中,并且没有与它们相关的超时(所需的行为)。如预期的那样,可以使用ICLRPolicyManager->SetTimeout(OPR_ProcessExit, )来控制全局托管对象的时间销毁函数(将非ref类放在使用/clr编译的文件中)。

Hazarding a guess, I think the reason global native constructors/destructors function "normally" (defined as behaving as I would expect) in the DLL scenario is to allow using LoadLibrary and GetProcAddress on native functions. I would thus expect that it is relatively safe to rely on it not changing in the foreseeable future, but would appreciate having some kind of confirmation/denial from official sources/documentation either way.

我冒昧地猜测一下,我认为在DLL场景中,全局本机构造函数/析构函数“正常”(定义为行为如我所愿)的原因是允许在本机函数上使用LoadLibrary和GetProcAddress。因此,我认为在可预见的未来,依靠它不会改变是相对安全的,但是希望能够从官方来源/文件中得到某种确认/否认。

Update 2:

更新2:

In Visual Studio 2012 (tested with the express and premium versions, I unfortunately don't have access to earlier versions on this machine). It should work the same way on the command line (building as outlined above), but here's how to reproduce from within the IDE.

在Visual Studio 2012中(通过express和premium版本测试,我很遗憾无法访问这台机器上的早期版本)。它应该在命令行上以相同的方式工作(如上面所述构建),但是这里介绍了如何从IDE中复制。

Building CLRHost.exe:

构建CLRHost.exe:

  1. File -> New Project
  2. 文件- >新项目
  3. Visual C++ -> Win32 -> Win32 Console Application (Name the project "CLRHost")
  4. Visual c++ -> Win32 -> Win32控制台应用程序(名称项目“CLRHost”)
  5. Application Settings -> Additional Options -> Empty project
  6. 应用程序设置->附加选项->空项目
  7. Press "Finish"
  8. 按“完成”
  9. Right click on Source Files in the solution explorer. Add -> New Item -> Visual C++ -> C++ File. Name it CLRHost.cpp and paste the content of CLRHost.cpp from the post.
  10. 在解决方案资源管理器中右键单击源文件。添加->新项目-> Visual c++ -> c++文件。它CLRHost名称。cpp并粘贴CLRHost的内容。cpp的职位。
  11. Project -> Properties. Configuration Properties -> C/C++ -> Code Generation -> Change "Enable C++ Exceptions" to "Yes with SEH Exceptions (/EHa)" and "Basic Runtime Checks" to "Default"
  12. 项目- >属性。配置属性—> C/ c++ ->代码生成—>更改“启用c++异常”为“Yes with SEH exception (/EHa)”,“基本运行时检查”为“Default”
  13. Build.
  14. 构建。

Building CLR.DLL:

构建CLR.DLL:

  1. File -> New Project
  2. 文件- >新项目
  3. Visual C++ -> CLR -> Class Library (Name the project "CLR")
  4. Visual c++ -> CLR ->类库(将项目命名为“CLR”)
  5. Delete all the autogenerated files
  6. 删除所有自动生成的文件
  7. Project -> Properties. Configuration Properties -> C/C++ -> Precompiled headers -> Prepcompiled headers. Change to "Not Using Precompiled Headers".
  8. 项目- >属性。配置属性-> C/ c++ ->预编译头->预编译头。更改为“不使用预编译头”。
  9. Right click on Source Files in the solution explorer. Add -> New Item -> Visual C++ -> C++ File. Name it CLR.cpp and paste the content of CLR.cpp from the post.
  10. 在解决方案资源管理器中右键单击源文件。添加->新项目-> Visual c++ -> c++文件。CLR命名它。cpp并粘贴CLR的内容。cpp的职位。
  11. Add a new C++ file named Native.cpp and paste the code from the post.
  12. 添加一个名为Native的c++文件。将代码从post中粘贴过来。
  13. Right click on "Native.cpp" in the solution explorer and select properties. Change C/C++ -> General -> Common Language RunTime Support to "No Common Language RunTime Support"
  14. 右键单击“本地。在“解决方案资源管理器”中选择“属性”。将C/ c++ ->通用->通用语言运行时支持更改为“不支持通用语言运行时”
  15. Project -> Properties -> Debugging. Change "Command" to point to CLRhost.exe, "Command Arguments" to "$(TargetPath)" including the quotes, "Debugger Type" to "Mixed"
  16. 项目->属性->调试。将“命令”更改为指向CLRhost。exe,“命令参数”到“$(TargetPath)”,包括引号,“调试器类型”到“混合”
  17. Build and debug.
  18. 构建和调试。

Placing a breakpoint in the destructor of Global gives the following stack trace:

在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    

Running as a standalone executable I get a stack trace that is very similar to the one observed by Hans Passant (though it isn't using the managed version of the CRT):

作为一个独立的可执行文件运行,我得到了一个与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    

2 个解决方案

#1


8  

Getting the easy questions out of the way first:

先把简单的问题解决掉:

A good resource for CLR customization is Steven Pratschner's book "Customizing the Microsoft .NET Framework Common Language Runtime". Beware that it is outdated, the hosting interfaces have changed in .NET 4.0. MSDN doesn't say much about it but the hosting interfaces are well documented.

一个很好的CLR定制资源是Steven Pratschner的书“定制Microsoft . net框架公共语言运行时”。请注意,它已经过时了,在。net 4.0中,托管接口已经发生了变化。MSDN并没有对此做过多的说明,但是托管接口已经有很好的文档说明。

You can make debugging simpler by changing a debugger setting, change the Type from "Auto" to "Managed" or "Mixed".

您可以通过更改调试器设置、将类型从“自动”更改为“托管”或“混合”来简化调试。

Do note that your 3000 msec sleep is just on the edge, you should test with 5000 msec. If the C++ class appears in code that's compiled with /clr in effect, even with #pragma unmanaged in effect then you'll need to override the finalizer thread timeout. Tested on the .NET 3.5 SP1 CLR version, the following code worked well to give the destructor sufficient time to run to completion:

请注意,你的3000毫秒睡眠正处于边缘,你应该用5000毫秒进行测试。如果c++类出现在实际使用/clr编译的代码中,即使在实际使用#pragma未管理的情况下,也需要重写终结器线程超时。在。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();

I picked a minute as a reasonable time, tweak as necessary. Note that the MSDN documentation has a bug, it doesn't show OPR_FinalizerRun as a permitted value but it does in fact work properly. Setting the finalizer thread timeout also ensures that a managed finalizer won't time out when it indirectly destructs an unmanaged C++ class, a very common scenario.

我选择了一分钟作为合理的时间,必要的时候做些调整。请注意,MSDN文档有一个错误,它没有显示OPR_FinalizerRun作为一个允许的值,但是它确实正常工作。设置终结器线程超时也确保了托管终结器在间接地破坏一个非托管c++类时不会超时,这是一个非常常见的场景。

One thing you'll see when you run this code with CLRHost compiled with /clr is that the call to GetCLRManager() will fail with an HOST_E_INVALIDOPERATION return code. The default CLR host that got loaded to execute your CLRHost.exe won't let you override the policy. So you are pretty stuck with having a dedicated EXE to host the CLR.

当您使用/clr编译的CLRHost运行这段代码时,您将看到的一件事情是,对GetCLRManager()的调用将在HOST_E_INVALIDOPERATION返回代码中失败。用于执行CLRHost的默认CLR主机。exe不会让您重写策略。因此,您不得不使用专用的EXE来托管CLR。

When I tested this by having CLRHost load a mixed-mode assembly, the call stack looked like this when setting a breakpoint on the destructor:

当我通过让CLRHost加载一个混合模式程序集来测试它时,在析构函数上设置断点时,调用堆栈看起来是这样的:

CLRClient.dll!Global::~Global()  Line 24    C++
[Managed to Native Transition]  
CLRClient.dll!<Module>.?A0x789967ab.??__Fg@@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

Do note that this is unlike your observations in your question. The code is triggered by the managed version of the CRT (msvcm90.dll). And this code runs on a dedicated thread, started by the CLR to unload an appdomain. You can see the source code for this in the vc/crt/src/mstartup.cpp source code file.

请注意,这与你的问题不同。代码由CRT (msvcm90.dll)的托管版本触发。该代码运行在一个专用线程上,由CLR启动,以卸载appdomain。您可以在vc/crt/src/mstartup中看到这方面的源代码。cpp源代码文件。


The second scenario occurs when the C++ class is part of a source code file that is compiled without /clr in effect and got linked into the mixed-mode assembly. The compiler then uses the normal atexit() handler to call the destructor, just like it normally does in an unmanaged executable. In this case when the DLL gets unloaded by Windows at program termination and the managed version of the CRT shuts down.

第二个场景发生在c++类是源代码文件的一部分时,该文件在没有/clr的情况下编译,并被链接到混合模式程序集中。然后,编译器使用正常的atexit()处理程序调用析构函数,就像它通常在非托管可执行文件中一样。在这种情况下,当DLL在程序终止时被Windows卸载,并且CRT的托管版本关闭时。

Notable is that this happens after the CLR is shutdown and that the destructor runs on the program's startup thread. Accordingly, the CLR timeouts are out of the picture and the destructor can take as long as it wants. The essence of the stack trace is now:

值得注意的是,这发生在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..

This is however a corner case that will only occur when the startup EXE is unmanaged. As soon as the EXE is managed, it will run destructors on AppDomain.Unload, even if they appear in code that was compiled without /clr. So you still have the timeout problem. Having an unmanaged EXE is not very unusual, this will happen for example when you load [ComVisible] managed code. But that doesn't sound like your scenario, you are stuck with CLRHost.

然而,只有在启动EXE未受管理时才会出现这种情况。一旦EXE被管理,它将在AppDomain上运行析构函数。卸载,即使它们出现在没有/clr编译的代码中。所以你仍然有超时问题。拥有一个非托管的EXE并不罕见,例如,当您加载[ComVisible]托管代码时,就会发生这种情况。但这听起来不像你的场景,你被CLRHost困住了。

#2


1  

To answer the "Where is this documented / how can I educate myself more on the topic?" question: you can understand how this works (or used to work at least for the framework 2) if you download and check out the Shared Source Common Language Infrastructure (aka SSCLI) from here http://www.microsoft.com/en-us/download/details.aspx?id=4917.

回答“这个文档在哪里/我如何教育自己关于此主题的更多信息吗?”的问题:你能理解这是如何工作的(或框架用于工作至少2)如果您下载和查看共享源共同语言基础设施(又名SSCLI)从这里http://www.microsoft.com/en-us/download/details.aspx?id=4917。

Once you've extracted the files, you will find in gcEE.ccp ("garbage collection execution engine") this:

一旦您提取了这些文件,您将在gcEE中找到。ccp(“垃圾收集执行引擎”):

#define FINALIZER_TOTAL_WAIT 2000

wich defines this famous default value of 2 seconds. You will also in the same file see this:

维奇定义了2秒这个著名的默认值。你也会在同一个文件中看到这个:

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

That will tell you the Execution Engine will obey the OPR_FinalizerRun policy, if defined, which correspond to the value in the EClrOperation Enumeration. GetEEPolicy is defined in eePolicy.h & eePolicy.cpp.

这将告诉您执行引擎将遵守OPR_FinalizerRun策略(如果定义了的话),该策略对应于EClrOperation枚举中的值。GetEEPolicy定义在eePolicy中。h & eePolicy.cpp。

#1


8  

Getting the easy questions out of the way first:

先把简单的问题解决掉:

A good resource for CLR customization is Steven Pratschner's book "Customizing the Microsoft .NET Framework Common Language Runtime". Beware that it is outdated, the hosting interfaces have changed in .NET 4.0. MSDN doesn't say much about it but the hosting interfaces are well documented.

一个很好的CLR定制资源是Steven Pratschner的书“定制Microsoft . net框架公共语言运行时”。请注意,它已经过时了,在。net 4.0中,托管接口已经发生了变化。MSDN并没有对此做过多的说明,但是托管接口已经有很好的文档说明。

You can make debugging simpler by changing a debugger setting, change the Type from "Auto" to "Managed" or "Mixed".

您可以通过更改调试器设置、将类型从“自动”更改为“托管”或“混合”来简化调试。

Do note that your 3000 msec sleep is just on the edge, you should test with 5000 msec. If the C++ class appears in code that's compiled with /clr in effect, even with #pragma unmanaged in effect then you'll need to override the finalizer thread timeout. Tested on the .NET 3.5 SP1 CLR version, the following code worked well to give the destructor sufficient time to run to completion:

请注意,你的3000毫秒睡眠正处于边缘,你应该用5000毫秒进行测试。如果c++类出现在实际使用/clr编译的代码中,即使在实际使用#pragma未管理的情况下,也需要重写终结器线程超时。在。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();

I picked a minute as a reasonable time, tweak as necessary. Note that the MSDN documentation has a bug, it doesn't show OPR_FinalizerRun as a permitted value but it does in fact work properly. Setting the finalizer thread timeout also ensures that a managed finalizer won't time out when it indirectly destructs an unmanaged C++ class, a very common scenario.

我选择了一分钟作为合理的时间,必要的时候做些调整。请注意,MSDN文档有一个错误,它没有显示OPR_FinalizerRun作为一个允许的值,但是它确实正常工作。设置终结器线程超时也确保了托管终结器在间接地破坏一个非托管c++类时不会超时,这是一个非常常见的场景。

One thing you'll see when you run this code with CLRHost compiled with /clr is that the call to GetCLRManager() will fail with an HOST_E_INVALIDOPERATION return code. The default CLR host that got loaded to execute your CLRHost.exe won't let you override the policy. So you are pretty stuck with having a dedicated EXE to host the CLR.

当您使用/clr编译的CLRHost运行这段代码时,您将看到的一件事情是,对GetCLRManager()的调用将在HOST_E_INVALIDOPERATION返回代码中失败。用于执行CLRHost的默认CLR主机。exe不会让您重写策略。因此,您不得不使用专用的EXE来托管CLR。

When I tested this by having CLRHost load a mixed-mode assembly, the call stack looked like this when setting a breakpoint on the destructor:

当我通过让CLRHost加载一个混合模式程序集来测试它时,在析构函数上设置断点时,调用堆栈看起来是这样的:

CLRClient.dll!Global::~Global()  Line 24    C++
[Managed to Native Transition]  
CLRClient.dll!<Module>.?A0x789967ab.??__Fg@@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

Do note that this is unlike your observations in your question. The code is triggered by the managed version of the CRT (msvcm90.dll). And this code runs on a dedicated thread, started by the CLR to unload an appdomain. You can see the source code for this in the vc/crt/src/mstartup.cpp source code file.

请注意,这与你的问题不同。代码由CRT (msvcm90.dll)的托管版本触发。该代码运行在一个专用线程上,由CLR启动,以卸载appdomain。您可以在vc/crt/src/mstartup中看到这方面的源代码。cpp源代码文件。


The second scenario occurs when the C++ class is part of a source code file that is compiled without /clr in effect and got linked into the mixed-mode assembly. The compiler then uses the normal atexit() handler to call the destructor, just like it normally does in an unmanaged executable. In this case when the DLL gets unloaded by Windows at program termination and the managed version of the CRT shuts down.

第二个场景发生在c++类是源代码文件的一部分时,该文件在没有/clr的情况下编译,并被链接到混合模式程序集中。然后,编译器使用正常的atexit()处理程序调用析构函数,就像它通常在非托管可执行文件中一样。在这种情况下,当DLL在程序终止时被Windows卸载,并且CRT的托管版本关闭时。

Notable is that this happens after the CLR is shutdown and that the destructor runs on the program's startup thread. Accordingly, the CLR timeouts are out of the picture and the destructor can take as long as it wants. The essence of the stack trace is now:

值得注意的是,这发生在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..

This is however a corner case that will only occur when the startup EXE is unmanaged. As soon as the EXE is managed, it will run destructors on AppDomain.Unload, even if they appear in code that was compiled without /clr. So you still have the timeout problem. Having an unmanaged EXE is not very unusual, this will happen for example when you load [ComVisible] managed code. But that doesn't sound like your scenario, you are stuck with CLRHost.

然而,只有在启动EXE未受管理时才会出现这种情况。一旦EXE被管理,它将在AppDomain上运行析构函数。卸载,即使它们出现在没有/clr编译的代码中。所以你仍然有超时问题。拥有一个非托管的EXE并不罕见,例如,当您加载[ComVisible]托管代码时,就会发生这种情况。但这听起来不像你的场景,你被CLRHost困住了。

#2


1  

To answer the "Where is this documented / how can I educate myself more on the topic?" question: you can understand how this works (or used to work at least for the framework 2) if you download and check out the Shared Source Common Language Infrastructure (aka SSCLI) from here http://www.microsoft.com/en-us/download/details.aspx?id=4917.

回答“这个文档在哪里/我如何教育自己关于此主题的更多信息吗?”的问题:你能理解这是如何工作的(或框架用于工作至少2)如果您下载和查看共享源共同语言基础设施(又名SSCLI)从这里http://www.microsoft.com/en-us/download/details.aspx?id=4917。

Once you've extracted the files, you will find in gcEE.ccp ("garbage collection execution engine") this:

一旦您提取了这些文件,您将在gcEE中找到。ccp(“垃圾收集执行引擎”):

#define FINALIZER_TOTAL_WAIT 2000

wich defines this famous default value of 2 seconds. You will also in the same file see this:

维奇定义了2秒这个著名的默认值。你也会在同一个文件中看到这个:

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

That will tell you the Execution Engine will obey the OPR_FinalizerRun policy, if defined, which correspond to the value in the EClrOperation Enumeration. GetEEPolicy is defined in eePolicy.h & eePolicy.cpp.

这将告诉您执行引擎将遵守OPR_FinalizerRun策略(如果定义了的话),该策略对应于EClrOperation枚举中的值。GetEEPolicy定义在eePolicy中。h & eePolicy.cpp。