EXE加载DLL(两个默认堆问题)

本文详细解析了C++编程中使用静态库和动态库时产生的两个默认堆问题,分析了其根本原因在于模块间运行时库不一致。通过实例解释了如何在不同模块间正确使用new和delete,避免资源泄露或程序崩溃。提出了遵循微软警告、使用单一运行时库版本或提供跨模块内存管理函数等解决方案。
摘要由CSDN通过智能技术生成

文章结构:

前言及问题的抛出

(1)问题描述[关于两个默认堆的产生]

(2)微软警告[来自微软的解释]

(3)原因分析[导致异常的原因分析]

(4)针对该问题的应对方案[结论]


前言及问题的抛出:

堆,我们都知道,是动态申请的空间,需要我们显示进行申请及释放资源。譬如说,我们调用了malloc,就要调用free,我们调用了new,就要调用delete。否则会产生内存泄露。

但是,你有没有想过,即便是我们正常地配对使用new和delete,也有可能会出现问题呢?此处,抛出我们今天需要讨论的问题:两个默认堆的问题。


(1)问题描述。首先说一下,什么情况下,会出现两个默认堆。我们可以参考如下博文:

  http://hi.baidu.com/gensoft/item/4b80ea9aa520c5f629164716

总结之:当exe链接到静态C库,DLL连接到动态C库时,就会产生两个默认堆。此时在一个模块里面new,在另外一个模块里面delete,就会出现问题!


(2)其实,对于该问题,msdn中就已经指出警告:

图片链接

该警告msdn的链接:

http://msdn.microsoft.com/zh-cn/library/cc438633(v=vs.71).aspx


(3)归根结底,产生该问题的根本原因是:模块之间使用的运行时库不一致!

     我们应该知道,new和delete的调用,最后是会依赖于运行时库的实现的。

     举例子说明一下:

     试想一下,当A.exe链接到静态C库,B.DLL连接到动态C库时:

     在A.exe的进程空间中,调用new函数,申请一段空间SAPCE。实际上是用静态C库提供的接口进行资源的申请的。如果这个时候,A.exe加载了B.DLL,并且将上述空间SAPCE的指针传给B.DLL,在B.DLL内,执行delete(SPACE)。那么,实际上执行的,是动态C库的delte接口,进行资源的释放!!!

     到现在,我们是不是都觉察到这是怎么一回事了吧。

     很明显,静态C库的new和delete是应该配对使用的,动态C库的new和delete也是应该配对使用的。不配对使用,虽然还没深入了解两者实现的具体不一致之处,我们也可以用拍一下脑袋,这不配对,带来的结果就是未知!未知,意味着结果不可控。不可控,啥都有可能发生!堆栈被破坏啦,程序崩溃了等等,让你找不着北。


(4)结论:

     结论一:我们可以乖乖地遵循微软的警告,不要混合使用运行时库的静态版本和动态版本。

     结论二:(嘻嘻,偏偏不遵循警告的做法,用一下我老大教我的法子)

上述问题的出现,目前看到的,主要是由于运行时库版本不同,却混合使用,然后在资源申请和释放阶段出问题。那么,我们可以切入出现问题的地方! 譬如:就上述例子而言,对于A.exe,可以申请一段空间SPACE,同时,A.exe可以提供一个函数,该函数负责释放该段空间SPACE!这样的话,B.dll在释放空间时,调用A.exe提供的这个函数即可。那么,SAPCE的申请和释放,使用的new和delete就是调用的一直的运行时库版本。

--------------------------------------------------------------------------------------------

(5)补充另外一位仁兄提供的网络上的资料,对该问题的描述更加详细到位:

一个模块一个堆,一个线程一个栈。
dll里malloc的内存,在exe里free会出错。

CRT(C运行时期库)不是使用进程缺省的堆来实现malloc(new中调用malloc)的,而是使用一个全局句柄HANDLE _crtheap来分配内存的。这个_crtheap是在XXXCRTStartUp(CRT提供的进口点函数)中创建的。 
由于CRT静态连接,则楼主的DLL里有也有一个CRT,因此也有一个_crtheap。而在dll中的new使用dll中的_crtheap句柄分配堆,在exe中的delete使用exe中的_crtheap释放堆,当然失败!

1。在DLL中输出一个函数给EXE调用,专门用来释放由DLL分配的内存;
2。用GlobalAlloc()代替new,用GlobalFree()代替delete;
3。使用单一的堆,分配内存使用HeapAlloc(GetProcessHeap(),0,size),释放内存使用HeapFree(GetProcessHeap(),0,p);
4。把dll和exe的Settings的C/C++选项卡的Code Generation的Use Run-time liberary改成Debug Multithreaded DLL,在Release版本中改成Multithreaded DLL;这样使用一个CRT了——MSVCRT.DLL。


C语言 C++语言 Windows 平台 COM IMalloc 接口 BSTR
申请 malloc() new GlobalAlloc() CoTaskMemAlloc() Alloc() SysAllocString()
重新申请 realloc()
GlobalReAlloc() CoTaskRealloc() Realloc() SysReAllocString()
释放 free() delete GlobalFree() CoTaskMemFree() Free() SysFreeString()

  以上这些函数必须要按类型配合使用(比如:new 申请的内存,则必须用 delete 释放)。在 COM 内部,当然你可以随便使用任何类型的内存分配释放函数,但组件如果需要与客户进行内存的交互,则必须使用上表中的后三类函数族。IMalloc 接口又是对 CoTaskXXX() 函数族的一个包装。包装后,同时增强了一些功能,比如:IMalloc::GetSize()可以取得尺寸,使用 IMallocSpy 可以监视内存的使用。


------------------------------------------------------------------------------------------------------

OK,结束。希望有问题大家不吝指出,大家共同进步哈!

(读者请注意,尽信书不如无书。上述描述还不够深入且未贴近真相。哥要再研究研究运行时库实现、堆实现等相关知识后,再持续补充、纠正!!!)


有两种常见的方法可以实现将 .exe 文件和 .dll 文件分开在不同目录: 1. 使用 nuget 包管理器 在 .NET Core 中,可以使用 nuget 包管理器将依赖项打包到单独的文件夹中。要实现这一点,可以在 .csproj 文件中添加以下代码: ``` <PropertyGroup> <OutputPath>bin\$(Configuration)\</OutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> </PropertyGroup> <ItemGroup> <PackageReference Include="Your.Package.Name" Version="x.x.x" /> </ItemGroup> ``` 这将确保包将被安装到单独的文件夹中,并且将 .exe 文件和 .dll 文件分开。在项目文件夹中,会创建一个名为“bin”(默认情况下)的文件夹,其中包含 .exe 文件和 .dll 文件的两个子文件夹。 2. 使用命令行工具 另一种方法是使用命令行工具手动分离 .exe 文件和 .dll 文件。可以使用以下命令创建 .exe 文件: ``` dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained false ``` 这将创建一个单独的 .exe 文件,其中包含所有的依赖项。然后,可以将 .dll 文件移动到单独的文件夹中,以便在运行时加载它们。要移动 .dll 文件,可以使用以下命令: ``` xcopy /Y /S /I /E /Q /R /H bin\Release\netcoreapp3.1\publish\*.dll Your\Destination\Folder ``` 这将复制所有的 .dll 文件到指定的目标文件夹中。在运行程序时,需要确保 .dll 文件可以被正确加载。可以使用以下命令来运行程序: ``` dotnet YourProgramName.exe --depsfile YourProgramName.deps.json --runtimeconfig YourProgramName.runtimeconfig.json ``` 这将确保 .dll 文件能够被正确加载,并且 .exe 文件和 .dll 文件被分离在不同的文件夹中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值