在dll-interfaces中使用shared_ptr

时间:2009-10-22 07:56:56

标签: c++ dll boost abstract-class shared-ptr

我的dll中有一个抽象类。

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

我想在加载dll的exe文件中获取IBase。 第一种方法是创建以下功能

IBase * CreateInterface();

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

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

boost::shared_ptr<IBase> CreateInterface();

并且不需要Release()功能。

问题。

1)在第二种情况中的dll(不在exe文件中)中调用析构函数和内存释放是否正确?

2)如果使用不同的编译器(或不同的设置)编译exe-file和dll,第二种情况是否可以正常工作。

4 个答案:

答案 0 :(得分:18)

第一个问题的答案:调用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()或者注意绕过令人惊讶的控制流程的异常。

回答你的第二个问题:其他answer明确说明了这种方法的缺点:你的观众必须使用相同的编译器,链接器,设置,库像你一样如果它们可能相当多,这可能是您图书馆的主要缺点。您必须选择:安全与较大的受众

但是可能存在漏洞:在您的申请中使用shared_ptr<IBase>,即

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

因此,没有实现特定对象跨DLL边界传递。然而,你的指针被安全隐藏在shared_ptr后面,即使DestroyFromLibrary正在抛出异常,他也会在正确的时间调用func()

答案 1 :(得分:6)

我建议不要在界面中使用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的实现可能会在同一编译器的新版本中发生变化,并且可能会发生变化你陷入困境。我会回避使用第二种选择。

答案 2 :(得分:2)

  1. 使用shared_ptr将确保将在DLL中调用资源释放函数。
  2. 查看this question的答案。
  3. 解决这个问题的方法是创建一个纯C接口和一个精简的完全内联C ++包装器。

答案 3 :(得分:1)

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

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

IBase *p = CreateInterface();
delete p;

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