在C++ COM对象内部处理bad_alloc异常的正确方法是什么?

3
我正在处理各种名为C++ COM DirectShow过滤器的内容,通过COM互操作从C#客户端调用。代码中几乎没有使用C++异常,主要异常是可能会抛出bad_alloc异常的operator new。
有没有一种干净的方法可以处理bad_alloc异常,并且C#客户端能够捕获?
新的处理程序是否可以抛出一些可以被COM互操作客户端捕获的SEH异常?
还是最好链接到Visual Studio库中向后兼容的非抛出版本的new,并检查每个分配?
一种繁琐的替代方案是为数百个COM入口点编写try/catch,但这似乎不值得,因为bad_alloc异常很少可恢复。
DirectShow基类通常检查operator new的空返回值,因为它们似乎是为早期版本的Visual C++编写的,这些版本不会抛出bad_alloc异常。

在这个问题的被接受答案中,提出了一种使编写异常处理代码更容易的方法:在异常处理中重用代码。 - Laserallan
4个回答

4

当需要大量分配内存时,您需要捕获异常并返回E_OUTOFMEMORY。对于小的内存分配失败,继续尝试是没有任何意义的。当程序使用2 GB虚拟内存后,再也不会有好事发生,您可以让它死掉。由于地址空间碎片化,大型内存分配很可能会失败,但仍可能存在大量未使用的空间。


3

COM API合同要求您不允许任何C++异常流过COM API边界。

这意味着在离开您的COM API之前,您需要捕获所有C++异常并将它们转换为HRESULTs。

在某些罕见情况下,您可能能够逃脱这种限制(例如,如果您可以确保COM客户端和COM服务器是使用相同版本的编译器构建的),但有许多情况可能会出现问题(例如,COM服务器位于代理/存根后面(如果您有多个公寓或服务器处于进程外),或者如果从其他语言(如CLR语言之一)调用COM服务器)。

通常,您需要做以下工作:

 catch (...)
 { 
     return E_FAIL;
 }

在每个COM API的结尾加上这个操作将极大地提高您代码的稳健性。

谢谢,Larry。在catch(...)情况下,我可能会返回E_UNEXPECTED来区分“意外异常”模式的失败。尝试从未知异常中恢复是否存在某些危险,可能会导致进程在未知状态下继续运行,从而导致数据损坏和其他损害?客户端代码应该对此可能性敏感吗? - persiflage

1

是的,将所有方法都包装在一个大的try-catch中:

#define BEGIN_COM_METHOD try {

#define END_COM_METHOD \
} catch( const std::bad_alloc& ) {\
     return E_OUTOFMEMORY;\
} catch( ... ) {\
     return E_FAIL;\
}\
return S_OK;

HRESULT YourComClass::SomeMethod()
{
    BEGIN_COM_METHOD
       DoUsefulStuff();
    END_COM_METHOD
}

使用此方法,您不再需要通过 COM 边界传播异常。

0

感谢您的回答。我可以理解为什么C++异常无法可靠地通过COM边界传递。但是,COM契约对跨越COM边界流动的结构化异常有何规定呢?

以下技术如何?它将operator new失败转换为结构化异常。或者说这种做法存在不良后果吗?

void __cdecl OutOfMemoryHandler()
{
    RaiseException(STATUS_NO_MEMORY, 0, 0, NULL);
}

std::set_new_handler(OutOfMemoryHandler);

我注意到 ATL 可以在 AtlThrow 中通过引发 STATUS_NO_MEMORY 结构化异常处理 E_OUTOFMEMORY HRESULT 的类似技巧,尽管行为取决于预处理器定义的符号。

在本机代码中,这种技术也建议设置 /EHa 编译器选项,以便进行堆栈展开并为结构化异常调用析构函数。我可以看出,如果我使用了很多 try/catch 子句,这将导致性能下降,但在当前情况下,我根本没有使用 try/catch 子句。如果我使用 /EHa,仍然会受到性能损失吗?

无论如何,结构化异常处理编译器设置可能是一个好主意。另一个堆栈溢出帖子描述了一个恶心的 bug,在其中 C++ 对象包装的临界区(常用于 DirectShow 基础类)在调用的模块中引发结构化异常时未能解锁。编译器设置仅设置为 C++ 异常处理,当引发结构化异常时,包装临界区的对象未被销毁,稍后发生死锁。


不,你不应该允许任何异常通过COM边界,这是绝对的。 - sharptooth
1
https://devblogs.microsoft.com/oldnewthing/20110120-00/?p=11713 - Martin Ba
Raymond Chen 的博客文章真的很有趣,感谢您的发布。 - persiflage

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接