【问题标题】:Dll safe exception handlingdll安全异常处理
【发布时间】:2018-03-06 22:09:00
【问题描述】:

我正在考虑在我正在编写的一些库中使用异常处理。 代码库最终可能在多个 dll 中实现。 我知道跨 dll 使用异常处理是一个坏主意 - 但是,执行以下操作是否安全?

class IDllSafeException
{
public:
    virtual void AddRef() = 0;
    virtual void Release() = 0;
    virtual int GetErrorCode() const = 0;
    virtual const char * What() const = 0;
};

假设这是一种有效的方法,我真的很想按值而不是指针来捕获。这样我就可以有一个自动调用 Release() 的包装类。

这样……

template <typename T>
class SafeExceptionT
{
public:
    SafeExceptionT(T *);            
    SafeExceptionT(SafeExceptionT &);
    SafeExceptionT & operator=(SafeExceptionT &);
    ~SafeExceptionT();

    int GetErrorCode() const;
    const char * What() const;
private:
    T * m_pException;
};

typedef SafeExceptionT<IDllSafeException> SafeException;

作为一个模板意味着所有的 catch 块都将生成它们自己的布局并在析构函数中调用 can m_pException->Release() (在复制发生时使用 AddRef() )。我也可以根据需要实现不同类型的异常。

我的 catch 块将如下所示...

try
{
    ThrowSomething();
}
catch(SafeException except)
{       
    const char * psz = except.What();
    int nErrorCode = except.GetErrorCode();
}

...

void ThrowSomething()
{   
    // Concrete implementation omitted for brevity
    throw new DllSafeException(1, "something went wrong");
}

我原以为这会起作用,但我的异常处理程序没有被捕获,即使我有一个 SafeException 的非显式构造函数。

但是我似乎只能通过指针捕捉...

try
{
    ThrowSomething();
}
catch(SafeException except)
{       
    /// IGNORED!
    ...
}
catch(IDllSafeException * pExcept)
{       
    // CAUGHT BY POINTER but we now have to micro-manage.
    const char * psz = pExcept->What();
    int nErrorCode = pExcept->GetErrorCode();
    pExcept->Release();
}

对于异常处理程序中的类型匹配方式是否有特殊规则?

【问题讨论】:

  • 跨 dll 使用异常处理并不是一个坏主意。它只需要非常小心的处理,就像一般的 C++ 异常一样。如果在这个 dll 中定义了要抛出的异常,那么你需要从这个 dll 中为这个类导入 rtti。如果它是在其他地方定义的,那么您需要从那里为异常类导入 rtti。此外,您应该按值抛出并通过 const 引用捕获。如果您仍然想抛出一个指针,那么制作虚拟析构函数而不是 COM 样式的 Release 并重新发明智能指针。
  • 您不能在 DLL 边界上抛出异常,尤其是基于类的异常,除非 DLL 和调用者都是针对同一个运行时库构建的,共享同一个 RTTI,同一个内存管理器等。只是不要这样做,这是不安全的。
  • 感谢 cmets,尽管似乎存在分歧。如果使用虚拟方法并通过指针抛出,我会认为内存管理问题会得到解决。此外,任何想法为什么异常没有被值作为 SafeException 捕获?如果这会成为问题,我可能会重新使用返回码。
  • 如果它没有被捕获,很可能你没有从 dll 导入异常类。上面列出的条件(“DLL 和调用者都是针对同一个运行时库构建的,共享同一个 RTTI,同一个内存管理器”)实际上是安全使用从一般 dll 导入的 C++ 代码的条件,而不仅仅是异常.
  • 如果我将析构函数设为虚拟,这不会导致错误 dll 中的堆释放吗?这就是我选择参考计数的原因。不过,如果我错了,很高兴得到纠正。

标签: c++ exception dll


【解决方案1】:

如果有人感兴趣,上面的代码确实通过使用抽象基类并依赖于跨编译器的一致 vtable 布局成功地规避了 dll 边界异常问题。

我在 VS2008 和 VS2017 上对此进行了测试。我在每个环境中都内置了一个 dll,每个环境中都有一个控制台应用程序。 dll 引发异常,异常类在内部聚合一个对象以确保发生堆分配。

我还在不同的 dll/控制台应用程序之间混合和匹配了 C 运行时版本(静态与 dll)。我可以成功地引发/捕获跨 dll 划分的异常。

正如预期的那样,vtable 布局确保 delete/~tor 发生在正确的二进制文件中。

唯一需要注意的是你必须通过抽象接口抛出。这也意味着确保您手动调用 pEx->Release。我知道这不是很漂亮,但我仍然不知道为什么我的其他类没有通过它的非显式构造函数捕获异常。

VS2008 Example Debug Session

【讨论】:

    猜你喜欢
    • 2017-01-16
    • 2019-12-28
    • 1970-01-01
    • 2020-04-07
    • 1970-01-01
    • 2011-09-01
    • 2012-12-03
    • 2013-10-23
    • 1970-01-01
    相关资源
    最近更新 更多