26

我的 dll 中有一个抽象类。

class IBase {
  protected:
       virtual ~IBase() = 0;
  public:
       virtual void f() = 0;
};

我想进入IBase加载 dll 的 exe 文件。第一种方法是创建以下功能

IBase * CreateInterface();

并在中添加虚函数Release()IBase

第二种方法是创建另一个函数

boost::shared_ptr<IBase> CreateInterface();

并且不需要任何Release()功能。

问题。

1)在第二种情况下,在dll(而不是在exe文件中)调用析构函数和内存释放是真的吗?

2)如果 exe 文件和 dll 是用不同的编译器(或不同的设置)编译的,第二种情况是否能正常工作。

4

4 回答 4

20

第一个问题的答案:调用 dll 中的虚拟析构函数 - 有关其位置的信息嵌入在您的对象中(在 vtable 中)。在内存释放的情况下,这取决于您的用户的纪律性IBase。如果他们知道他们必须调用Release()并认为异常可以以令人惊讶的方向绕过控制流,那么将使用正确的。

但是如果CreateInterface()返回shared_ptr<IBase>它可以将正确的释放函数绑定到这个智能指针。您的库可能如下所示:

Destroy(IBase* p) {
    ... // whatever is needed to delete your object in the right way    
}

boost::shared_ptr<IBase> CreateInterface() {
    IBase *p = new MyConcreteBase(...);
    ...
    return shared_ptr<IBase>(p, Destroy); // bind Destroy() to the shared_ptr
}                                         // which is called instead of a plain
                                          // delete

因此,您的 DLL 的每个用户都可以轻松防止资源泄漏。他们永远不必费心调用Release()或关注异常绕过他们的控制流。

要回答您的第二个问题:其他答案清楚地说明了这种方法的缺点:您的听众必须使用与您相同的编译器、链接器、设置、库。如果它们可能很多,这可能是您图书馆的主要缺点。您必须选择:安全与更大的受众

但是有一个可能的漏洞:在你的应用程序中使用shared_ptr<IBase>,即

{
    shared_ptr<IBase> p(CreateInterface(), DestroyFromLibrary);
    ...
    func();
    ...
}

因此,没有实现特定的对象通过 DLL 边界。然而,您的指针安全地隐藏在 之后,即使 是否抛出异常shared_ptr,谁也会在正确的时间调用。DestroyFromLibraryfunc()

于 2009-10-22T08:43:30.837 回答
8

我建议不要shared_ptr在界面中使用。即使在 DLL 的接口中使用 C++(而不是仅使用“extern C”例程)也是有问题的,因为名称修改会阻止您将 DLL 与不同的编译器一起使用。使用shared_ptr特别成问题,因为正如您已经确定的那样,不能保证 DLL 的客户端将使用与shared_ptr调用者相同的实现。(这是因为shared_ptr是一个模板类,并且实现完全包含在头文件中。)

要回答您的具体问题:

  1. 我不太确定你在这里问什么......我假设你的 DLL 将包含从IBase. 在这两种情况下,它们的析构函数的代码(以及其余代码)都将包含在 DLL 中。但是,如果客户端启动对象的销毁(通过delete在第一种情况下调用或shared_ptr在第二种情况下让最后一个实例超出范围),那么将从客户端代码中调用析构函数。

  2. 名称修改通常会阻止您的 DLL 与不同的编译器一起使用......但shared_ptr即使在同一编译器的新版本中,其实现也可能会发生变化,这可能会给您带来麻烦。我会回避使用第二个选项。

于 2009-10-22T08:07:19.743 回答
2
  1. 使用shared_ptr将确保在 DLL 中调用资源释放函数。
  2. 看看这个问题的答案。

解决这个问题的一种方法是创建一个纯 C 接口和一个围绕它的完全内联的 C++ 包装器。

于 2009-10-22T08:19:06.243 回答
1

关于您的第一个问题:我是在进行有根据的猜测,而不是根据经验说话,但在我看来,第二种情况下内存释放将被称为“在 .exe 中”。打电话时会发生两件事delete object;:首先,调用析构函数,其次,释放对象的内存。第一部分,析构函数调用,肯定会像你期望的那样工作,在你的 dll 中调用正确的析构函数。但是,由于 shared_ptr 是类模板,它的析构函数是在你的 .exe 中生成的,因此它会在你的 exe 中调用 operator delete() 而不是 .dll 中的那个。如果两者链接到不同的运行时版本(甚至静态链接到相同的运行时版本),这应该会导致可怕的未定义行为(这是我不完全确定的部分,但这样做似乎是合乎逻辑的) . 有一种简单的方法可以验证我所说的是否属实 -覆盖全局运算符 delete在你的 exe 中,而不是你的 dll 中,在其中放置一个断点,看看在第二种情况下调用了什么(我自己会这样做,但不幸的是,我有这么多时间来放松)。

请注意,第一种情况存在相同的问题(您似乎意识到了这一点,但以防万一)。如果您在 exe 中执行此操作:

IBase *p = CreateInterface();
delete p;

那么你就在同一个陷阱中——在 dll 中调用 operator new 并在 exe 中调用 operator delete。您需要在 dll 中使用相应的 DeleteInterface(IBase *p) 函数或在 IBase 中使用 Release() 方法(不必是虚拟的,只是不要使其内联),其唯一目的是调用正确的内存释放函数。

于 2009-10-22T09:44:15.437 回答