当本机(C ++)异常传播到CLR组件时,不会调用析构函数

时间:2010-03-23 18:27:33

标签: .net exception c++-cli destructor raii

我们拥有大量本机C ++代码,并且已经编译成DLL。

然后我们有几个包含C ++ / CLI代理代码的dll来包装C ++接口。

最重要的是,我们有C#代码调用C ++ / CLI包装器。

标准的东西,到目前为止。

但是我们有很多情况允许将原生C ++异常传播到.Net世界,我们依赖.Net将这些包装为System.Exception对象的能力,并且大多数情况下这都能正常工作。

但是,我们发现当异常传播时,不会调用throw中的对象的析构函数!

经过一些研究,我们发现这是一个众所周知的问题。然而,解决方案/解决方案似乎不太一致。我们确实发现,如果使用/ EHa而不是/ EHsc编译本机代码,问题就会消失(至少在我们的测试用例中)。但是我们更喜欢使用/ EHsc,因为我们自己将SEH异常翻译成C ++异常,我们宁愿让编译器有更多的优化空间。

是否还有其他解决方法可以解决此问题 - 除了在(本机)try-catch-throw(除C ++ / CLI层之外)中包装本地管理边界的每个调用之外?

4 个答案:

答案 0 :(得分:3)

不幸的是,我不相信有任何好的解决方法。 MS平台上的C ++异常实现是使用SEH异常(IIRC)实现的。 CLR挂钩到SEH处理以捕获本机异常并将它们处理为CLR异常。因为它在SEH级别捕获它们,所以异常看起来像是C ++的SEH异常,并且运行或不运行析构函数。

因此,您已经注意到最好的两个选项是

  • 使用/ EHa编译
  • 在功能的入口和出口处添加try / catch

理想情况下,你应该做第二个。根据我的经验,允许C ++异常跨越组件边界被认为是不好的做法。

使用_set_seh_translatorDocumentation)可能还有一个hacky解决方案。但是我强烈建议避免使用该功能,因为它可能无意中破坏CLR异常处理并导致许多不必要的问题。

答案 1 :(得分:3)

我认为你没有做到这一点。使用_set_se_translator()已经要求您使用/ EHa进行编译。来自MSDN Library page

You must use /EHa when using _set_se_translator.

更严重的是,您在使用托管代码时会破坏托管代码。 CLR依赖于SEH异常来检测各种不幸事件。它使用SetUnhandledExceptionFilter来捕获它们。特别是以这种方式引发NullReferenceException和OverflowException(x86)。当您注入自己的__try块时,您将阻止此异常流入CLR。虽然您将“处理”它,但您将无法查看异常的确切原因。托管的try / catch块无法检测到它。

/ EHa效率的解决方案(如果实际上是一个问题)是64位代码。它的基于函数表的堆栈展开非常高效,尝试块的开销为零。

答案 2 :(得分:3)

/ E编译器开关的MSDN页面确实说明了这种行为: -

http://msdn.microsoft.com/en-us/library/1deeycx5(VS.80).aspx

以下是相关引用: -

  

如果你使用/ EHs,那么你的捕获   子句不会捕获异步   例外。另外,在Visual C ++ 2005中,   当范围内的所有对象   生成异步异常   即使是,也不会被破坏   处理异步异常。

基本上/ EHsc是乐观的视图 - 它假设唯一的例外是真正的C ++风格的例外,并将相应地进行优化。另一方面,/ EHa采用悲观的观点并假设任何代码行都可能导致生成异常。

如果你能保证你永远不会导致访问冲突,或页内错误或其他SEH,那么使用/ EHsc。但如果您正在撰写服务和/或想要提供“尽力而为”,那么/ EHa将是必要的。

我也同意@JaredPar关于不允许异常跨越模块边界的观点。 @nobugz关于CLR处理异常的方式的说法可能是正确的,但我认为.Net代码使用P / Invoke直接调用本机代码并调用C ++ / CLI互操作DLL之间存在差异。在前一种情况下,CLR必须代表您处理这种情况,而在后一种情况下,您可以控制并可以相应地进行翻译。

答案 3 :(得分:1)

2.0版本的CLR中存在一个导致此问题的错误。在4.0 CLR上运行托管可执行文件将允许按预期方式调用析构函数。

有关详细信息,请参阅Boost shared mutex not released after exception thrown