我正在构建一个共享库,我希望在不同的编译器(如Windows上的MSVC和GCC)之间兼容ABI。我的灵感来自this blog post。我唯一遗漏的是能够跨DLL边界抛出异常......所以我做了这个小技巧:
MyLibrary.hpp
class Thrower{
static void(*m_thowFunc)(const char*);
public:
static void setThrowFunction(void(*func)(const char*)){
m_thowFunc = func;
}
static void throwException(const char* msg){
m_thowFunc(msg);
}
};
extern "C" {
EXPORT void MyLibrary_setThrowFunction(void(*func)(const char*));
EXPORT void MyLibrary_foo();
}
MyLibrary.cpp
extern "C" {
EXPORT void MyLibrary_setThrowFunction(void(*func)(const char*)){
Thrower::setThrowFunction(func);
}
EXPORT void MyLibrary_foo(){
Thrower::throwException("oops, an error occured...");
}
}
并在客户端代码中
void defaultThrowFunction(const char* msg){
throw std::runtime_error(msg);
}
int main(){
MyLibrary_setThrowFunction(&defaultThrowFunction);
try{
MyLibrary_foo();
}catch(std::runtime_error& e){
//handle exception
}
}
这就像一个魅力。我可以在客户端代码中处理从DLL抛出的异常(事实上,由客户端代码抛出)。我所知道的唯一缺点是,在编译DLL时我有大量的警告,因为“并非所有控制路径都返回一个值”...
我在这里遗漏了什么吗?这真的是个好主意还是根本没有?
答案 0 :(得分:1)
这可能有效,但前提是两个代码库的抛出机制和堆栈展开代码基本相同且兼容。
为了销毁它之后应该抛出的每个对象,客户端代码必须能够理解DLL代码如何设置其堆栈以及它注册要清理的析构函数的位置等。它可能是如果你幸运的话,你的客户端和DLL代码编译器足以同意这个工作的情况。
哪种方式破坏了你设计的重点。
可能有效的方法是仔细编组DLL边界上的异常。
" C" DLL的API返回有关抛出的异常(如果有)的信息。客户端编译的C ++头文件包装器调用" C" API,并在客户端编译的代码中检测异常,解开它并抛出它。
在DLL中," C" API执行try{}catch(){}
,并调用内部C ++库。 catch
子句将异常信息填充到" C" API返回值,并返回。
现在抛出内部异常,在C ++ DLL中捕获,在" C"中的DLL边界上编组。 API,在客户端代码端打包回异常,并重新抛出。
答案 1 :(得分:0)
除非一切都在您的完全控制之下,否则在DLL边界内抛出异常通常是一个坏主意。
通过一切,我的意思是:投掷者和捕手应该使用相同的编译器版本,相同的标准库和相同的运行时实例。所有这些事情都可以摒弃异常处理 - 希望只是一次崩溃,但可能会带来更不可思议的微妙结果。