【问题标题】:Is this "trick" to throw exceptions across DLL boundaries a bad idea?这种跨 DLL 边界抛出异常的“技巧”是一个坏主意吗?
【发布时间】:2015-08-25 22:36:23
【问题描述】:

我正在构建一个共享库,我希望它能够在不同编译器(如 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();
}

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

我在这里遗漏了什么吗?这真的是个好主意吗?

【问题讨论】:

  • 在 C++11 中使用 [[noreturn]] 删除有关控制路径的警告 (en.cppreference.com/w/cpp/language/attributes)。解决方案看起来不错。
  • 如果 C++11 不是一个选项(或者您的编译器版本不支持该功能):有供应商特定的扩展来指示编译器函数不会返回值: MSVC 中的 __declspec(noreturn) 和 GCC 中的 __attribute__ ((noreturn))
  • [[noreturn]] 属性将不起作用,因为函数可能想要返回一些东西......例如: int foo(int a){ if(a>0){ return a; }else{ Thrower::throwException("负值");}}
  • 对于哪个函数原型编译器会发出警告?
  • 我刚刚明白你的意思是在Thrower::throwException 方法上使用[[noreturn]]...确实警告消失了。谢谢!

标签: c++ exception dll


【解决方案1】:

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

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

哪种方式会破坏您的设计要点。


一种可行的方法是仔细地在 DLL 边界上编组异常。

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

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

现在抛出内部异常,在 C++ DLL 中捕获,在“C”API 中在 DLL 边界上编组,在客户端代码端打包回异常,然后重新抛出。

【讨论】:

  • 感谢您的详细回答。我要看看你的替代解决方案。
【解决方案2】:

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

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

【讨论】:

  • 这就是我这样做的原因。确保 throw 和 catch 由同一个编译器完成...
猜你喜欢
  • 2011-07-03
  • 2011-02-22
  • 1970-01-01
  • 2011-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-10
相关资源
最近更新 更多