Dll导出构造函数有导致堆损坏的风险

Dll exporting a constructor risks causing heap corruption

本文关键字:损坏 构造函数 Dll      更新时间:2023-10-16

我正在研究一个插件系统,该系统允许用户以DLL文件的形式开发自己的模块。模块应该使用由应用程序的所有组件导入的DLL中定义的对象。下面是一个示例对象的样子:

#include <boost/system/api_config.hpp>
#if defined BOOST_WINDOWS_API
    #ifdef EXPORT
        #define API    __declspec(dllexport)
    #else
        #define API    __declspec(dllimport)
    #endif
#else
    #define API
#endif
class A
{
public:
    API A();
    API virtual ~A();
};

所有dll都是静态构建的(使用它们自己的CRT),并且具有完全相同的编译标志。我知道通过DLL边界交换对象可能会很麻烦,所以我几乎在任何地方都使用boost::shared_ptr。但是,对象构造函数有一个困难:如果我在堆栈上(从不同的DLL)创建对象,一切都按预期工作。但是如果我使用new操作符,当对象被删除时堆就会损坏。

A a; // Works fine, no problem when the object goes out of scope.
A* b = new A();
delete b; // Causes heap corruption!

这个问题的正确解决方法是什么?我觉得如果我必须在对象的DLL(如A* A::create() { return new A(); })中定义方法,代码将不太可读。在最坏的情况下,我正在考虑使new操作符私有,以确保用户不会使用它。

根据你对我评论的回应:/MT/MTd没有可以很好地使用dll(即使它们是默认的)。如果你想要使用dll和动态分配,必须使用/MD/MDd。当您使用/MT/MTd时,您可以有效地告诉系统为每个DLL使用单独的堆。也就是说在一个文件中进行分配,在另一个文件中进行删除,将破坏堆。当析构函数为虚函数时,实际的delete将在析构函数中,而不是delete表达式中的。(实际的问题是mallocfree,由operator new()operator delete()函数

解决这个问题的经典方法是使用factory方法用于动态分配和静态或成员函数删除。另一种选择(没有尝试,但我认为它会工作)是定义非内联operator new()operator delete()成员,它们只转发给mallocfree。(当然,在operator new的情况下,您必须检查你从malloc得到的指针不是空的,抛出一个std::bad_alloc(如果是)

但是这些都是不应该的情况下的变通方法存在。一切工作正常与/MD/MDd,这是你应该使用什么(即使这意味着你不能合法使用)在没有许可证的机器上部署调试版本

我认为这个问题可能与您正在混合堆的事实有关。当您构建带有所有CRT静态链接的DLL时,这意味着它维护自己的堆。但是,来自主机进程的命令newdelete正在使用进程堆。我认为这种配置会引起问题。我认为最好的方法是添加两个方法到您的DLL: CreateA()DestroyA(),并仅使用它们分配/销毁堆对象从DLL a

解决方案就是永远不要从DLL导出构造函数。而是导出一个工厂函数。永远不要在使用具体类时导出它。只导出纯抽象类。无论如何,对于减少耦合和所有相关的好东西来说,这是一个很好的样式。