从 DLL 中删除指针

Deleting pointer from DLL

本文关键字:指针 删除 DLL      更新时间:2023-10-16

ALL,

我有以下问题:

class Base
{
public:
    Base();
    virtual ~Base();
};
class Derived1 : public Base
{
public:
    Derived1();
    virtual Derived1();
};
class Derived2 : public Base
{
public:
    Derived2();
    virtual Derived2();
};

DLL 中定义的基类,它是静态链接的,每个子类都在其自己的 DLL 中定义,该 DLL 按需加载。

class Foo
{
public:
    ~Foo();
    void CreateObject();
    void SetPointer(Base *base) { m_p = base; };
private:
    void *m_p;
}
Foo::~Foo()
{
    delete m_p;
    m_p = NULL;
}
extern "C" void CreateObject()
{
     Base *base = NULL;
     Foo *foo = new Foo();
     foo->CreateObject( base );
     foo->SetBase( base );
     delete foo;
}
void Foo::CreateObject(Base *m_p)
{
    if( <cond1> )
        m_p = new Derived1();
    if( <cond2> )
        m_p = new Derived2();
}

运行此代码,我遇到内存泄漏。通过调试器运行,我看到从未调用 Derived1 类的析构函数。

我该如何解决它?析构函数是虚拟的,应该调用。唯一的问题是内存分配发生在 DLL 内部,但析构函数在主应用程序内部调用。

是否可以修复内存泄漏,或者我将不得不重新考虑设计?

谢谢。

在我看来

,您正在尝试实现一个插件系统,因此这个答案将尝试勾勒出您如何构建这样的东西以及想到的一些陷阱。

C++和 DLL 有一些与之相关的有趣挑战。 例如,必须格外小心:

  • 在 DLL 之间传递的对象的分配和解除分配("新建"和"删除")
  • 在 DLL 之间引发和捕获异常
  • 跨 DLL 边界传递 STL 对象
  • 函数重载

我在这里经验最多的方法是仔细定义从 DLL 导出的函数,然后标记 extern "C",避免重载它们,并且永远不会从这些函数中抛出异常。

尽管 MSVC 确实支持可导出类,但我建议避免使用它们,因为您可能会很快遇到上面列出的问题区域。

无论如何,您可以相对安全地依赖的一件事是在 DLL 之间共享接口类。 接口类是仅包含纯虚拟方法的类。 所以例如:

class Plugin
{
public:
    virtual void DoFoo() = 0;
    virtual void DoBar() = 0;
};

我们将插件的声明放在一个头文件中,该文件可以包含在应用程序以及插件 DLL 的实现中。

请注意,尚未导出任何内容。 我们只打算导出 C 样式的函数。 按照惯例,我们会说我们的插件DLL必须提供"CreatePlugin"函数。 下面是一个示例:

class FirstPlugin : public Plugin
{
public:
    virtual void DoFoo() { std::cout << "FirstPlugin says FOO!n"; }
    virtual void DoBar() { std::cout << "FirstPlugin says BAR!n"; }
};
extern "C"
{
    __declspec(dllexport) Plugin* CreatePlugin()
    {
        return new FirstPlugin();
    }
}

加载 dll 的应用程序可以执行此操作:

typedef Plugin* (*CreatePluginFn)();
HMODULE module = LoadLibrary("first.dll");
CreatePluginFn createPlugin = (CreatePluginFn)GetProcAddress(module, "CreatePlugin");
Plugin* plugin = createPlugin();
plugin->DoFoo();
plugin->DoBar();

我省略了必要的免费图书馆调用。 更有趣的是我们如何处理释放我们创建的插件。 应用程序不一定知道 CreatePlugin 如何分配插件实例,因此应用程序"删除插件"并不安全。 相反,我们需要告诉插件 DLL 本身我们已经完成了它。

最明显的方法是在插件中添加一个"销毁"方法:

class Plugin
{
public:
    virtual void Destroy() = 0;
    virtual void DoFoo() = 0;
    virtual void DoBar() = 0;
};

可能的实现方式是:

class FirstPlugin : public Plugin
{
public:
    virtual void Destroy() { delete this; }
    virtual void DoFoo() { std::cout << "FirstPlugin says FOO!n"; }
    virtual void DoBar() { std::cout << "FirstPlugin says BAR!n"; }
};

所以现在调用者做:

plugin->Destroy();
plugin = NULL; // we mustn't use plugin after we're destroyed it!

我认为这涵盖了基础知识。 事实证明,当您构建大量这样的代码时,存在常见的模式,例如:

  • 使用引用计数而不是大锤子销毁方法
  • 界面发现(比如问插件"你也可以做Foo吗?
  • 在DLL中支持许多多个"创建插件"样式的函数

这些(例如COM)的现有解决方案可能很有趣。

在一个 DLL 中分配并在另一个 DLL 中解除分配是可以工作的事情,但很棘手。 但是,我认为即使所有代码都在同一个 DLL 中,您也会看到此问题:

在类 Foo 中,指向 Base 的指针存储在 void* 中。 这会导致编译器"忘记"m_p是 Base* 实例,因此它只是释放内存而不调用析构函数。

将m_p作为基础*应该可以解决此问题。