我正在开发可供C或C ++应用程序使用的C ++组件dll。 暴露的dll函数如下
#include <tchar.h>
#ifdef IMPORT
#define DLL __declspec(dllimport)
#else
#define DLL __declspec(dllexport)
#endif
extern "C" {
DLL bool __cdecl Init();
DLL bool __cdecl Foo(const TCHAR*);
DLL bool __cdecl Release();
}
这些函数的内部实现是未公开的C ++类,我假设使用这种样式,dll可以在C或C ++应用程序中使用。 问题是我没有处理任何类型的c ++异常(即bad_alloc),我将这些东西留给了调用者(更高层)。 经过与同事的多次辩论,我应该捕获所有异常并返回错误代码或至少是错误的,因为在C应用程序的情况下,它无法处理C ++异常?真的吗?我一般应该做些什么?如果您正在开发将由其他系统使用的组件,那么处理exeptions是否有经验法则。
答案 0 :(得分:18)
如果这是使用MSVC的Windows,那么是的,您可以在C中捕获异常,但是您无法很好地捕获它们。 C ++异常通过OS ABI的结构化异常处理机制提供,Microsoft具有__try, __except, __finally C扩展来处理OS结构化异常。请注意,这些包括您通常希望终止程序并记录错误报告的访问冲突,被零除等。您可以通过代码0xE04D5343(4D 53 43 =“MSC”)识别C ++异常并将其余部分打开。
大家都这么说,你可能不希望在DLL边界上抛出异常,当然,如果你只暴露一个C API,那肯定不会。
答案 1 :(得分:17)
C没有异常,因此通常你应该捕获所有异常并返回错误代码和/或提供一个返回上一个错误信息的函数。
答案 2 :(得分:13)
作为一般规则,您应该 从不 允许C ++异常传播到模块的边界之外。这是因为C ++标准没有规定必须如何实现异常传播,因此这是编译器(和编译器标志)和操作系统相关的。您不能保证调用模块的代码将使用与模块相同的编译器标志使用相同的编译器进行编译。事实上,正如您在使用此问题进行演示时,您无法保证调用模块的代码将使用相同的语言编写。
有关详细信息,请参阅Sutter和Alexandrescu在C ++编码标准中的第62项。
答案 3 :(得分:8)
好的,因为它被要求:
C ++示例代码:
#include <typeinfo>
#include <exception>
extern "C" {
void sethandler(void (*func)(void)) { std::set_terminate(func); }
int throwingFunc(int arg) {
if (arg == 0)
throw std::bad_cast();
return (arg - 1);
}
}
C示例代码:
#include <stdio.h>
extern int throwingFunc(int arg);
extern void sethandler(void (*func)(void));
void myhandler(void)
{
printf("handler called - must've been some exception ?!\n");
}
int main(int argc, char **argv)
{
sethandler(myhandler);
printf("throwingFunc(1) == %d\n", throwingFunc(1));
printf("throwingFunc(-1) == %d\n", throwingFunc(-1));
printf("throwingFunc(0) == %d\n", throwingFunc(0));
return 0;
}
当我编译这两个时,将它们链接在一起并运行它(Ubuntu 10.04,gcc 4.4.5,x64),我得到:
$ ./xx throwingFunc(1) == 0 throwingFunc(-1) == -2 handler called - must've been some exception ?! Aborted
因此,当可以从C中捕获异常时,这几乎不够 - 因为std::terminate()
之后的C ++运行时行为未定义,并且因为处理程序没有获得任何状态信息(以解除异常类型)和/或来源)。处理程序无法清理任何内容。
顺便说一下,我在本例中故意选择std::bad_cast()
作为异常类型。这是因为抛出说明了std::set_unexpected()
和std::set_terminate()
之间行为的差异 - 将为所有非std::bad_*
异常调用意外的处理程序,而为了捕获标准异常,终止处理程序需要......看到困境?线束太宽而无法实际使用:(
答案 4 :(得分:7)
如果调用者被设计为处理它们,则仅向调用者传播异常。我猜你的情况terminate()
将在任何异常转义C ++代码后立即调用,因为从C ++运行时开始,该异常尚未处理。
COM服务器设计也出现了同样的情况 - 客户端可以使用任何语言/技术。 rul是没有异常应该逃避COM服务器方法 - 必须在HRESULT和(可选)IErrorInfo中捕获和转换所有异常。你应该在你的情况下做同样的事情。
如果C代码夹在两层C ++代码propagating exceptions to C code is still a very bad idea之间。