清理跨越Windows DLL模块边界的堆分配资源时出现问题


/* 

我的理解是,将一个或多个动态链接库(DLL)加载到其地址空间的Windows进程将与所有加载的DLL共享该地址空间,这意味着这些DLL可以读取和写入进程地址空间中的任何内存。但是,当在堆上分配对象时,每个模块(无论是.exe还是进程加载的DLL之一)都会从自己的堆中进行分配。因此,针对执行分配的同一堆解除内存分配至关重要。

这一切对我来说都很有意义,我想我可以使用std::unique_ptr来帮助保持事情的有序性。这就是我使用的方法。(我现在手头没有编译器,但我认为这些片段/伪代码将足够清晰,可以传达我的意图:

*/

库.h

class ILibrary
{
public:
virtual void DoStuff() = 0;
};
struct Deleter
{
void operator()(ILibrary *p)
{
delete p;
}
};
typedef std::unique_ptr<ILibrary, Deleter> Ptr;
//*MyLibrary.dll*
//Includes Library.h
//Exports:
void GetMyLibrary(Ptr & library)
{
library = Ptr(new MyLibrary);  // point (1)
}
//**Program.exe**
//Includes Library.h
//Imports MyLibrary.dll (GetMyLibrary export)
int main()
{
Ptr local;
MyLibrary->GetMyLibrary(local);
local->DoStuff();
} // heap corruption on cleanup

您可以看到,我的库和主程序都使用相同的头library.h。我创建了变量local来保存指向我的库的指针。GetMyLibrary方法(在DLL中调用)将new unique_ptr分配给我传入的引用。我在"点1"使用赋值,因为我希望DLL上下文中的Deleter用于清理,而不是最初分配给主程序中local变量的Deleter。这意味着,当local超出作用域时,我希望它的清理触发DLL的Deleter,而不是最初分配给它的Deleter(即,我使用library = Ptr(new MyLibrary)而不是library.reset(new MyLibrary),因为我希望从DLL的上下文调用Deleter)

无论如何,该程序似乎可以工作,只是在清理过程中,当本地unique_ptr销毁时,我得到了一个堆损坏异常(在调试中),这让我相信我删除了错误的堆(即unique_ptr没有像我预期的那样运行)

最终,我以另一种似乎更干净的方式解决了这个问题,但我只是好奇为什么上面的方法失败了?

您的deleter是unique_ptr的一部分,当指针超出范围时,它会从main调用。

您应该在DLL中提供GetMyLibrary()/FreeMyLibrary(),并在那里处理内存分配/释放(在应用程序端使用RAII),或者将分配器传递给GetMyLibrary(),并由应用程序负责内存分配和释放。

最新更新