【问题标题】:Using shared_ptr in dll-interfaces在 dll 接口中使用 shared_ptr
【发布时间】:2010-12-09 00:06:12
【问题描述】:

我的 dll 中有一个抽象类。

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

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

IBase * CreateInterface();

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

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

boost::shared_ptr<IBase> CreateInterface();

并且不需要Release() 函数。

问题。

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

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

【问题讨论】:

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


    【解决方案1】:

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

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

    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() 或关注异常绕过他们的控制流。

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

    但是有一个可能的漏洞:在你的应用程序中使用shared_ptr&lt;IBase&gt;,即

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

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

    【讨论】:

    • 答案中似乎有一个错字:“boost::shared_ptr IBase* CreateInterface()”。从中删除“IBase*”。
    【解决方案2】:

    我建议不要在界面中使用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 的实现也可能会发生变化,这可能会让您进入麻烦。我会回避使用第二个选项。

    【讨论】:

    • -1 因为您可以将“正确”删除绑定到 shared_ptr 并且 boost::shared_ptr 已经在 tr1 中。因此它非常接近标准。 +1 提到名称修改是另一个问题点 =0 ...那又如何?
    • 您可以指定一个自定义删除器供 shared_ptr 使用,这不是问题。问题是“标准”(TR1)没有指定应该如何实现 shared_ptr,即它的内存布局应该是什么以及它应该在哪里存储它的引用计数。 DLL 和客户端可以使用两个完全符合标准的编译器进行编译,但在内部对 shared_ptr 的含义不一致。 是问题所在。所以不要在 DLL 接口中使用 shared_ptr ......它可能会回来咬你。
    • @Marting:我接受,无论哪种情况,由 shared_ptr 的不同实现引起的问题都不能由自定义删除器解决。但是标题问题应该是:为 DLL 提供 C++ 接口有用吗?
    • 是的,这是下一个问题......我可能会回答:不。问题是(非托管)C++ 没有真正的 ABI——DLL 仅使用 C 设计心里。 COM 接口是为了解决这个问题而发明的,Alexey 的第一个选项在精神上非常接近 COM 接口。正如您在回答中指出的那样,COM 接口有其自身的问题,我可能会选择将所有对象隐藏在纯 C 接口后面。啊,.NET 世界中的人不知道他们拥有它有多好...... ;)
    • @Martin:我也遇到过这些问题,最后为我回答了:不。我宁愿提供一个安全的 C++ 接口,在不太可能的情况下,程序员想要使用他刚刚拥有的库切换到我的版本。我的观众肯定会很少……
    【解决方案3】:
    1. 使用shared_ptr 将确保在DLL 中调用资源释放函数。
    2. 查看this question 的答案。

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

    【讨论】:

      【解决方案4】:

      关于您的第一个问题:我是在进行有根据的猜测,而不是根据经验说话,但在我看来,第二种情况内存释放将被称为“在 .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() 方法(不必是虚拟的,只是不要使其内联),其唯一目的是调用正确的内存释放函数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-10-29
        • 1970-01-01
        • 2011-05-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多