我的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,第二种情况是否可以正常工作。
答案 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
是一个模板类,实现完全包含在头文件中。)
回答您的具体问题:
我不太确定你在这里问的是什么......我假设你的DLL将包含从IBase
派生的类的实现。在两种情况下,它们的析构函数代码(以及代码的其余部分)都将包含在DLL中。但是,如果客户端启动对象的销毁(通过在第一种情况下调用delete
或在第二种情况下让shared_ptr
的最后一个实例超出范围),则析构函数将从客户端代码中调用。
名称修改通常会阻止您的DLL与其他编译器一起使用......但shared_ptr
的实现可能会在同一编译器的新版本中发生变化,并且可能会发生变化你陷入困境。我会回避使用第二种选择。
答案 2 :(得分:2)
shared_ptr
将确保将在DLL中调用资源释放函数。 解决这个问题的方法是创建一个纯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()方法(不必是虚拟的,只是不使其内联),其唯一目的是调用正确的内存释放功能。