这个 InteropServices.SEHException 的警告怎么可能是真的?

How can this warning of InteropServices.SEHException be real?

我通过用我捕获的异常帧包装非托管调用来防止非托管 C++ 异常从我的 C++/CLI 代码中转义 const std::exception&。但是我有一个代码路径,其中非托管 C++ throw 立即触发 SEHException 的警告,即使在 C++/CLI 的堆栈框架上显然有一个 catch 子句拦截它.

我不明白 SEHException 会发生在哪里。我想知道它是不是真的。

这是 C++ 代码在调用堆栈中抛出异常的方式。

StringMap ParseTopLevelMap(std::istream& in)
{
    StringMap yamlmap;
    if (!TryParseTopLevelMap(in, yamlmap))
        throw std::runtime_error("Unable to parse map");  // Causes SEHException warning.

    return yamlmap;
}

我在上面做 throw 的那一刻,我立即在输出 window:

中得到这个输出
Exception thrown: 'System.Runtime.InteropServices.SEHException' in MyCompany.Sdk_v143.dll

但我非常清楚地在 C++/CLI 的调用框架中有一个 catch(const std::exception& ex) 更高层并且它 确实 被调用。在这里(注意catch

bool ScanContext::TryLoad(GsScan^ scan, String^ path, [SRI::Out]ScanContext^% ctx)
{
    try
    {
        ctx = nullptr;
        auto sPath= ToSdk(name);
        ctx =  gcnew ScanContext(scan, gs::LoadScanContext(scan->sdkScan(), sPath));
    }
    catch (const std::exception& ex)
    {
        std::cerr << ex.what() << std::endl;     // This DOES execute
    }

    return ctx != nullptr; // If we did not throw, this is non-null and we succeeded.
}

因此 上面的 catch 子句 被调用,错误输出消息被转储,并且控制 returns 通常返回到我的托管 C# 代码调用 C++/CLI。

那么 SEHException 去哪儿了?

为了仔细检查,我将“例外”对话框设置为在 SEHException 实际中断。它确实做到了。 这是中断点的调用堆栈。我的 C++ 代码正在抛出 std::runtime_error

    ntdll.dll!NtWaitForSingleObject()   Unknown
    KernelBase.dll!WaitForSingleObjectEx()  Unknown
    ntdll.dll!RtlpExecuteHandlerForException()  Unknown
    ntdll.dll!RtlDispatchException()    Unknown
    ntdll.dll!KiUserExceptionDispatch() Unknown
    KernelBase.dll!RaiseException() Unknown
>   vcruntime140d.dll!_CxxThrowException(void * pExceptionObject, const _s__ThrowInfo * pThrowInfo) Line 75 C++
    gscored_v143.dll!gs::detail::YAML::ParseTopLevelMap(std::basic_istream<char,std::char_traits<char>> & in) Line 298  C++
    gscored_v143.dll!gs::ScanContext::loadContent(std::basic_istream<char,std::char_traits<char>> & is, const std::string & loadFolder) Line 172    C++
    gscored_v143.dll!gs::ScanContext::loadContent(std::basic_istream<char,std::char_traits<char>> & is) Line 63 C++
    gscored_v143.dll!gs::ScanContext::Load(const std::shared_ptr<gs::Scan> & scan, const std::string & name) Line 884   C++
    gscored_v143.dll!gs::LoadScanContext(const std::shared_ptr<gs::Scan> & scan, const std::string & name) Line 310 C++
    [Managed to Native Transition]  
    MyCompany.Sdk_v143.dll!MyCompany::Sdk::ScanContext::TryLoad(MyCompany::Sdk::Scan^ scan, System::String^ name, MyCompany::Sdk::ScanContext^% ctx) Line 660   C++
    MyCompany.Services.dll!MyCompany.Services.ScanService.GetScanContext(MyCompany.Sdk.Scan scan, string name) Line 1725    C#
 

这只是一个错误的警告吗?

有意思。

documentation exactly what's going on here, but there's an interesting article over at Code Project which explains how (unmanaged) C++ exceptions are handled by the Microsoft compiler. Basically, when you call throw an SEH exception is generated (via RaiseException) 中一点也不明显,然后被运行时库捕获并映射到 C++ 异常。

现在的问题是如何将其映射到托管 C++ 异常,以便您可以在托管代码中捕获它,其背后的机制一点也不清楚,但 SEHException 显然以某种方式适合它.

也许 .NET 框架通过它自己的 try ... catch 块捕获您的非托管 throw 并使用不同的异常代码再次调用 RaiseException(一个对应于 / 结果 SEHException),这就是你在调试器中捕获的内容。然后它会捕获(通过 __try ... __except)并使用某种魔法生成托管代码异常。反正就是这样。

至于是否需要关心这个,文档接着说:

Note that the SEHException class does not cause unmanaged C++ exception destructors to be called. To ensure that unmanaged C++ exception destructors are called, use the following syntax in the catch block.

C#
catch  
{  
     // Handle catch here.  
}

现在我真的不确定 'C++ exception destructors' 是什么意思(我不知道有这样的东西)但可能是 yamlmap 没有被正确销毁。如果那是真的,我会感到惊讶,但它可能值得检查。在 managed 代码中处理异常对我来说也没有意义。也许这只是一个错字。

回到那篇代码项目文章,它引用了一个事实,即非托管 C++ throw 引发的 SEH 异常的异常代码是 0xE06D7363。如果你突破了这一点,你可能会更多地了解这一切。再一次,也许不是。