跨 DLL 边界抛出异常"trick"是一个坏主意吗?

Is this "trick" to throw exceptions across DLL boundaries a bad idea?

本文关键字:一个 边界 DLL 抛出异常 trick      更新时间:2023-10-16

我正在构建一个共享库,我希望在不同的编译器(如Windows上的MSVC和GCC)之间实现ABI兼容。我从这篇博文中获得了灵感。我唯一错过的是跨 DLL 边界抛出异常的能力......所以我做了这个小技巧:

我的图书馆.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();
}

我的图书馆.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 时有大量警告,因为"并非所有控制路径都返回值"......

我在这里错过了什么吗?这真的是一个好主意吗?

这可能有效,但前提是两个代码库的抛出机制和堆栈展开代码基本相同且兼容。

为了在抛出后销毁它应该销毁的每个对象,客户端代码必须能够理解 DLL 代码如何设置其堆栈以及在哪里注册要清理的析构函数等。 如果幸运的话,您的客户端和 DLL 代码编译器可能已经足够一致,可以正常工作。

哪种破坏了你的设计要点。


可能有效的方法是仔细封送 DLL 边界上的异常。

DLL 的"C"API 返回有关引发哪些异常(如果有)的信息。 客户端编译的仅C++头文件包装器调用"C"API,在客户端编译的代码中检测异常,解包并引发异常。

在DLL内部,"C"API执行try{}catch(){},并调用内部C++库。 catch 子句将异常信息填充到"C"API 返回值中,然后返回。

现在,内部异常被抛出,在C++ DLL中捕获,在"C"API中封送DLL边界,打包回客户端代码端的异常,然后重新抛出。

通常,跨 DLL 边界引发异常是一个坏主意,除非一切都在您的完全控制之下。

我的意思是:抛出器和捕获器应该使用相同的编译器版本、相同的标准库和相同的运行时实例。所有这些事情都可以抛弃异常处理 - 希望只是崩溃,但可能会有一个更不幸的微妙结果。