跨DLL的内存/堆管理

时间:2010-02-15 13:31:17

标签: c++ dll memory-management

虽然这似乎是一个非常常见的问题,但我没有收集太多信息:如何在DLL边界之间创建一个关于内存分配的安全接口?

众所周知

// in DLL a
DLLEXPORT MyObject* getObject() { return new MyObject(); }
// in DLL b 
MyObject *o = getObject();
delete o;

肯定会导致崩溃。 但是,由于上面的交互是 - 我敢说 - 并不罕见,因此必须有一种方法来确保安全的内存分配。

当然,可以提供

// in DLL a
DLLEXPORT void deleteObject(MyObject* o) { delete o; }

但也许有更好的方法(例如smart_ptr?)。我在阅读STL容器时也读过有关使用自定义分配器的内容。

所以我的询问更多是关于 关于本主题的文章和/或文献的一般指示 。是否有特殊的谬误需要注意(异常处理?)并且这个问题仅限于DLL还是“受到”的UNIX共享对象?

8 个答案:

答案 0 :(得分:13)

正如您的建议,您可以使用boost::shared_ptr来解决该问题。在构造函数中,您可以传递自定义清理函数,该函数可以是创建指针的dll的deleteObject-Method。例如:

boost::shared_ptr< MyObject > Instance( getObject( ), deleteObject );

如果您的dll不需要C接口,则可以让getObject返回shared_ptr。

答案 1 :(得分:9)

重载operator newoperator delete等。 al用于所有DLL类并在DLL中实现它们:

 void* MyClass::operator new(size_t numb) {
    return ::operator new(num_bytes);
 }

 void MyClass::operator delete(void* p) {
    ::operator delete(p);
 }
 ...

这可以很容易地放在DLL导出的所有类的公共基类中。

这样,分配和释放完全在DLL堆上完成。老实说,我不确定它是否有任何严重的陷阱或可移植性问题 - 但它对我有用。

答案 2 :(得分:5)

你可能会说它“肯定会导致崩溃”。有趣 - “可能”意味着与“肯定”完全相反。

现在,这句话主要是历史性的。有一个非常简单的解决方案:使用1个编译器,1个编译器设置,并链接到CRT的DLL形式。 (你可以逃脱后者)

没有要链接的特定文章,因为现在这不是问题。无论如何,你需要1个编译器,1个设置规则。简单的事情sizeof(std::string)取决于它,否则你会发生大规模的ODR违规。

答案 3 :(得分:3)

答案 4 :(得分:2)

在某些情况下可能适用的另一个选项是保留DLL内的所有分配和解除分配,并防止对象跨越该边界。您可以通过提供句柄来执行此操作,以便创建MyObject在DLL代码中创建它并返回一个简单的句柄(例如unsigned int),通过该句柄执行客户端的所有操作:

// Client code
ObjHandle h=dllPtr->CreateObject();
dllPtr->DoOperation(h);
dllPtr->DestroyObject(h);

由于所有分配都发生在dll中,因此可以通过包装在shared_ptr中来确保它被清除。这几乎是John Lakos在大规模C ++中提出的方法。

答案 5 :(得分:1)

在“分层”架构(非常常见的场景)中,最深层的组件负责提供问题的策略(可能如上所述返回shared_ptr<>或“调用者负责删除此”或“永远不要删除它,但在完成后调用releaseFooObject()并且之后不要访问它”或......)并且靠近用户的组件负责遵循该策略。

双向信息流使得责任更难以表征。


  

此问题仅限于DLL还是“受到”的UNIX共享对象?

实际上它比这更糟糕:你可以通过静态链接库轻松解决这个问题。在单个执行上下文中存在代码边界,这使得有可能滥用或误传某些工具。

答案 6 :(得分:1)

  

众所周知

// in DLL a
DLLEXPORT MyObject* getObject() { return new MyObject(); }
// in DLL b 
MyObject *o = getObject();
delete o;
     

肯定会导致崩溃。

上述具有明确定义的特征取决于MyObject类型的定义方式。

Iff这个类有一个虚拟析构函数(并且析构函数没有内联定义),它不会崩溃并且会表现出明确定义的行为。

通常引用崩溃原因的原因是delete做了两件事:

  • call destructor
  • 免费记忆(通过致电operator delete(void* ...)

对于具有非虚拟析构函数的类,它可以“内联”执行这些操作,这会导致DLL“b”内的delete可能尝试从“a”堆中释放内存= =崩溃。

但是,如果MyObject的析构函数是virtual,那么在调用“free”函数之前,编译器needs to determine the actual run-time class of the pointer可以将正确的指针传递给operator delete()

  

C ++要求您必须将完全相同的地址传递给运算符   删除操作符new返回的内容。当您分配对象时   使用new,编译器隐式知道具体的类型   object(编译器用于传递正确内存的对象)   例如,尺寸为operator new。)

     

但是,如果你的班级有基础   使用虚拟析构函数的类,并通过a删除您的对象   指向基类的指针,编译器不知道具体的类型   在呼叫站点,因此无法计算正确的地址   传递给operator delete()。为什么,你可能会问?因为存在   多重继承,基类指针的地址可能是   与内存中对象的地址不同。

     

那么,那会发生什么   例如,当您删除具有虚拟析构函数的对象时,   编译器调用所谓的删除析构函数   而不是通常的序列   调用普通的析构函数,然后调用operator delete()来回收   记忆。

     

由于删除析构函数是一个虚函数,因此   运行时将调用具体类型的实现,并且   该实现能够计算正确的地址   内存中的对象。该实现的作用是调用   常规析构函数,计算对象的正确地址,以及   然后在该地址上调用operator delete()。

似乎GCC(来自链接文章)和MSVC都是通过在“删除析构函数”的上下文中调用dtor以及“free”函数来实现这一点。必要时,这个助手会存在于你的DLL中,并且总是使用正确的堆,即使“a”和“b”有不同的一个。

答案 7 :(得分:0)

我写了an article关于使用C ++ 11的unique_ptr自定义删除工具来通过DLL边界(或Linux中的共享对象库)传递对象。本文中描述的方法不会使用删除器“污染”unique_ptr签名。