gpt4 book ai didi

.net - 销毁具有静态存储持续时间的本地对象

转载 作者:行者123 更新时间:2023-12-01 18:47:48 25 4
gpt4 key购买 nike

2012-12-09 摘要:

  • 在普通的混合模式应用程序中,全局原生 C++ 析构函数作为终结器运行。无法更改该行为或相关的超时。
  • 混合模式程序集 DLL 在 DLL 加载/卸载期间运行 C++ 构造函数/析构函数 - 与 native DLL 完全一样。
  • 使用 COM 接口(interface)在 native 可执行文件中托管 CLR 允许解构器在 native DLL 中的行为(我想要的行为)并设置终结器的超时(额外的好处)。
  • 据我所知,上述内容至少适用于 Visual Studio 2008、2010 和 2012。(仅使用 .NET 4 进行测试)

  • 我计划使用的实际 CLR 托管可执行文件与此问题中概述的非常相似,除了一些小的更改:
  • 设置 OPR_FinalizerRun到某个值(目前为 60 秒,但可能会更改),如 Hans Passant 建议的那样。
  • 使用 ATL COM 智能指针类(这些在 Visual Studio 的快速版本中不可用,所以我在这篇文章中省略了它们)。
  • 加载 CLRCreateInstance来自 mscoree.dll动态(在没有安装兼容的 CLR 时允许更好的错误消息)。
  • 将命令行从主机传递到指定的 Main程序集 DLL 中的函数。

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

    2012-12-02 更新在帖子底部。

    我正在使用带有 .NET 4 的 Visual Studio 2012 开发混合模式 C++/CLI 应用程序,并惊讶地发现某些 native 全局对象的析构函数没有被调用。调查该问题后发现,它们的行为类似于 this post 中所述的托管对象。 .

    我对这种行为感到非常惊讶(我理解托管对象的行为)并且在任何地方都找不到它的记录,也没有在 C++/CLI standard 中。也没有在 destructors and finalizers 的描述中.

    遵循 Hans Passant 评论中的建议,我将程序编译为程序集 DLL 并将其托管在一个小的 native 可执行文件中,这确实给了我所需的行为(析构函数有足够的时间完成并在构造它们的同一线程中运行)!

    我的问题:
  • 我可以在独立的可执行文件中获得相同的行为吗?
  • 如果(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 文件中,全局析构函数在 DomainUnload 期间作为终结器运行,无论它们是放在 native 代码还是 CLR 代码中。在 Visual Studio 2008、2010 和 2012 中就是这种情况。
  • 在由 native 应用程序托管的混合模式 DLL 中,全局 native 对象的析构函数在 DLL_PROCESS_DETACH 期间运行,在托管方法运行并且所有其他清理发生后。它们与构造函数在同一线程中运行,并且没有与它们关联的超时(所需的行为)。正如预期的那样,可以使用 /clr 控制全局托管对象(放置在使用 ICLRPolicyManager->SetTimeout(OPR_ProcessExit, <timeout>) 编译的文件中的非引用类)的时间析构函数。 .

  • 危险的猜测,我认为全局 native 构造函数/析构函数在 DLL 场景中“正常”运行(定义为我所期望的行为)的原因是允许使用 LoadLibraryGetProcAddress关于 native 功能。因此,我希望依靠它在可预见的 future 不会改变是相对安全的,但希望从官方来源/文件中获得某种确认/否认。

    更新 2 :

    在 Visual Studio 2012 中(使用 Express 和 Premium 版本进行测试,不幸的是我无法访问这台机器上的早期版本)。它应该在命令行上以相同的方式工作(构建如上所述),但这里是如何从 IDE 中重现。

    构建 CLRHost.exe:
  • 文件 -> 新建项目
  • Visual C++ -> Win32 -> Win32 控制台应用程序(将项目命名为“CLRHost”)
  • 应用程序设置 -> 附加选项 -> 空项目
  • 按“完成”
  • 右键单击解决方案资源管理器中的源文件。添加 -> 新项目 -> Visual C++ -> C++ 文件。将其命名为 CLRHost.cpp 并粘贴帖子中 CLRHost.cpp 的内容。
  • 项目 -> 属性。配置属性 -> C/C++ -> 代码生成 -> 将“Enable C++ Exceptions”更改为“Yes with SEH Exceptions (/EHa)”并将“Basic Runtime Checks”更改为“Default”
  • build 。

  • 构建 CLR.DLL:
  • 文件 -> 新建项目
  • Visual C++ -> CLR -> 类库(将项目命名为“CLR”)
  • 删除所有自动生成的文件
  • 项目 -> 属性。配置属性 -> C/C++ -> 预编译头文件 -> 预编译头文件。更改为“不使用预编译头”。
  • 右键单击解决方案资源管理器中的源文件。添加 -> 新项目 -> Visual C++ -> C++ 文件。将其命名为 CLR.cpp 并粘贴帖子中 CLR.cpp 的内容。
  • 添加一个名为 Native.cpp 的新 C++ 文件并粘贴帖子中的代码。
  • 右键单击解决方案资源管理器中的“Native.cpp”并选择属性。将 C/C++ -> 常规 -> 公共(public)语言运行时支持更改为“无公共(public)语言运行时支持”
  • 项目 -> 属性 -> 调试。将“命令”更改为指向 CLRhost.exe,将“命令参数”更改为“$(TargetPath)”(包括引号),将“调试器类型”更改为“混合”
  • 构建和调试。

  • 在 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

    最佳答案

    首先解决简单的问题:

    CLR 定制的一个很好的资源是 Steven Pratschner's book “自定义 Microsoft .NET Framework 公共(public)语言运行时”。请注意它已过时,.NET 4.0 中的托管接口(interface)已更改。 MSDN 对此并没有多说,但托管接口(interface)有据可查。

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

    请注意,您的 3000 毫秒 sleep 刚好处于边缘,您应该使用 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>.?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

    请注意,这与您在问题中的观察不同。该代码由 CRT 的托管版本 (msvcm90.dll) 触发。并且此代码在专用线程上运行,由 CLR 启动以卸载应用程序域。你可以在 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 困住了。

    关于.net - 销毁具有静态存储持续时间的本地对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13632187/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com