在DLL中用CRT静态库申请内存,EXE释放是不行的

本文详细介绍了在Windows环境下使用C/C++时,DLL与多个相同EXE实例间共享静态数据的问题及解决方法。通过全局变量的使用及其带来的copy-on-write机制引发的内存复制问题,提出了利用数据段共享和链接器参数来避免内存复制的解决方案。

因为malloc/free,new/delete都是调用HeapAlloc/HeapFree来实现来实现内存分配是释放的。

查看Windows的API可以看到,这两个函数都需要一个Heap的HANDLE做为参数。CRT库采用了全局变量来保存这个HANDLE。如果是CRT静态链接,CRT库的代码会链接到各个DLL中去,也包括这个全局变量。

也就是说,每个使用CRT静态链接的dll中都有一个自己的全局堆句柄,他们自己都在这个句柄上使用内存。当释放dll中分配的内存时由于使用的堆句柄不一致于是出错。

 

当使用CRT动态链接时,有于每个dll都是去调用CRT库的dll函数来分配和释放内存的,使用的是同一个句柄,所以就没有这个问题。


在Windows via C/C++的chap17中讲到,在通常情况下,一个dll文件同时被多处关联使用或exe文件同时有多个实例在运行,通过memory mapping在内存中实际只存在一份dll或exe的数据。如果dll或exe中的全局变量被修改,则通过copy-on-write,将被修改的内容所在page在内存中复制出一份,并将引用关联改为新的page。这样就不会影响到其他使用同一份page的程序,发生修改的进程中对应内容的地址修改为新的page地址。这样在设计dll时应尽量避免使用全局变量,否则根据copy-on-write的规则,很可能造成内存中存在多份内容大体相同,而仅有少量修改所导致的内存复制。
如果需要将dll或exe中的全局变量在多个实例中共享,可以采用以下方法。

// Tell the compiler to put this initialized variable in its own Shared // section so it is shared by all instances of this application. #pragma data_seg("Shared") volatile LONG g_lApplicationInstances = 0; #pragma data_seg() // Tell the linker to make the Shared section // readable, writable, and shared. #pragma comment(linker, "/Section:Shared,RWS")
本文转自:http://www.mindair.net/2011/03/02/dll及多个相同exe之间共享静态数据/

<think>我们正在回答用户的问题:在Visual Studio中创建使用C++动态链接库的方法。 根据用户提供的引用[1]引用[2],我们将分步骤说明。 步骤1:创建动态链接库项目(DLL项目) 步骤2:在DLL项目中编写代码并导出函数或类 步骤3:编译生成DLL文件对应的LIB文件(导入库) 步骤4:在客户端项目中使用DLL(隐式链接或显式链接) 下面详细说明: ### 1. 创建动态链接库项目 在Visual Studio中: - 新建项目 -> 选择“动态链接库(DLL)”项目模板(或者选择“Windows桌面向导”,然后选择动态链接库)。 - 设置项目名称位置,然后创建。 ### 2. 编写DLL代码并导出 在DLL项目中,我们需要将需要导出的函数或类用`__declspec(dllexport)`标记。 例如,创建一个头文件`MathLibrary.h`: ```cpp // MathLibrary.h - 包含导出函数的声明 #pragma once // 使用预处理器宏来简化导出声明 #ifdef MATHLIBRARY_EXPORTS #define MATHLIBRARY_API __declspec(dllexport) #else #define MATHLIBRARY_API __declspec(dllimport) #endif // 导出函数 extern "C" MATHLIBRARY_API int Add(int a, int b); extern "C" MATHLIBRARY_API int Subtract(int a, int b); // 导出类 class MATHLIBRARY_API Calculator { public: int Multiply(int a, int b); int Divide(int a, int b); }; ``` 注意:`extern "C"`用于避免C++的名称修饰,使得函数名在DLL中保持简单(这样使用GetProcAddress时可以用函数名直接获取)。如果不需要显式加载,也可以不使用。 然后,在源文件中实现: ```cpp // MathLibrary.cpp #include "MathLibrary.h" int Add(int a, int b) { return a + b; } int Subtract(int a, int b) { return a - b; } int Calculator::Multiply(int a, int b) { return a * b; } int Calculator::Divide(int a, int b) { if (b == 0) { throw std::invalid_argument("b cannot be zero!"); } return a / b; } ``` 注意:在DLL项目的预处理器定义中,添加`MATHLIBRARY_EXPORTS`宏(通常创建DLL项目时,VS会自动添加一个与项目名称相关的导出宏,如项目名为MathLibrary则宏为MATHLIBRARY_EXPORTS)。这样在编译DLL时,函数类会被导出;在客户端包含此头文件时,由于没有定义`MATHLIBRARY_EXPORTS`,所以会使用`__declspec(dllimport)`导入。 ### 3. 编译生成DLLLIB 编译项目(选择Debug或Release配置),在输出目录(如x64/Debug)会生成: - 动态链接库文件:`MathLibrary.dll` - 导入库文件:`MathLibrary.lib`(注意:这个.lib文件不是静态库,而是导入库,它包含了DLL导出的函数符号信息,用于隐式链接) ### 4. 在客户端项目中使用DLL #### 方法1:隐式链接(需要.lib、.dll头文件) - 将DLL项目的头文件(如`MathLibrary.h`)复制到客户端项目的源代码目录中(或添加包含路径)。 - 将生成的导入库文件(`MathLibrary.lib`)复制到客户端项目的目录中(或添加库路径)。 - 在客户端代码中包含头文件,并调用函数或类。 - 在客户端项目的链接器设置中,添加导入库文件(`MathLibrary.lib`)到“附加依赖项”(或者直接在代码中用`#pragma comment(lib, "MathLibrary.lib")`)。 客户端代码示例: ```cpp #include "MathLibrary.h" #include <iostream> int main() { // 使用导出的函数 int sum = Add(3, 5); std::cout << "3 + 5 = " << sum << std::endl; // 使用导出的类 Calculator calc; int product = calc.Multiply(3, 5); std::cout << "3 * 5 = " << product << std::endl; return 0; } ``` 注意:在运行时,DLL文件(`MathLibrary.dll`)必须放在客户端可执行文件所在的目录,或者系统路径中。 #### 方法2:显式链接(动态加载,仅需.dll文件) 使用Windows API:`LoadLibrary`、`GetProcAddress``FreeLibrary`。 客户端代码示例: ```cpp #include <iostream> #include <windows.h> // 定义函数指针类型 typedef int (*AddFunc)(int, int); int main() { HINSTANCE hDll = LoadLibrary(L"MathLibrary.dll"); if (hDll == NULL) { std::cerr << "Failed to load DLL!" << std::endl; return 1; } // 获取Add函数地址 AddFunc add = (AddFunc)GetProcAddress(hDll, "Add"); if (add == NULL) { std::cerr << "Failed to find Add function!" << std::endl; FreeLibrary(hDll); return 1; } int result = add(3, 5); std::cout << "3 + 5 = " << result << std::endl; FreeLibrary(hDll); return 0; } ``` 注意:显式链接不需要头文件.lib文件,但需要知道函数的签名(以便定义函数指针),并且函数名必须是导出的名称(对于C++函数,由于名称修饰,通常需要查看修饰后的名称,或者使用`extern "C"`来避免修饰)。 ### 注意事项(根据引用[1]): - 在隐式链接时,客户端项目的配置(Debug/Release)必须与DLL的配置一致,因为Debug版的DLL对应Debug版的导入库(通常带'd'后缀,如`MathLibrary_d.lib`,但取决于项目设置),Release版同理。在链接器的附加依赖项中要正确填写[^1]。 - 如果使用显式链接,则只需要.dll文件,但调用复杂,且不能直接使用类。 通过以上步骤,可以在Visual Studio中创建使用C++动态链接库。 §§相关问题§§ 1. 如何在C++动态链接库中导出C++类? 2. 隐式链接显式链接在性能使用场景上有何区别? 3. 如何避免在动态链接库中出现名称修饰(name mangling)问题? </think> ### Visual Studio中创建使用C++动态链接库的方法 #### 一、创建动态链接库(DLL) ##### 1. 创建DLL项目 1. 打开Visual Studio → 新建项目 → 选择 **"动态链接库(DLL)"** 模板 2. 设置项目名称(如 `MathLibrary`)位置 3. 在解决方案资源管理器右键项目 → 属性 → 配置属性 → 常规 - 配置类型:**动态库(.dll)** - 字符集:**使用Unicode字符集** ##### 2. 编写导出代码 在头文件中声明导出函数/类(示例:`MathLibrary.h`): ```cpp // 导出宏定义 #ifdef MATHLIBRARY_EXPORTS #define MATH_API __declspec(dllexport) #else #define MATH_API __declspec(dllimport) #endif // 导出函数 extern "C" MATH_API int Add(int a, int b); extern "C" MATH_API int Subtract(int a, int b); // 导出类 class MATH_API Calculator { public: int Multiply(int a, int b); int Divide(int a, int b); }; ``` 在源文件中实现功能(示例:`MathLibrary.cpp`): ```cpp #include "MathLibrary.h" int Add(int a, int b) { return a + b; } int Subtract(int a, int b) { return a - b; } int Calculator::Multiply(int a, int b) { return a * b; } int Calculator::Divide(int a, int b) { return (b != 0) ? a / b : 0; } ``` > 关键点: > - `__declspec(dllexport)` 标记需导出的符号 > - `extern "C"` 避免C++名称修饰(name mangling) > - 预定义宏 `MATHLIBRARY_EXPORTS`(VS自动添加)[^1] ##### 3. 编译生成 1. 选择配置(Debug/Release)→ 生成解决方案 2. 输出文件: - 动态库:`MathLibrary.dll` - 导入库:`MathLibrary.lib`(隐式链接必需) - 头文件:`MathLibrary.h` --- #### 二、使用动态链接库 ##### 方法1:隐式链接(推荐) 1. **配置客户端项目** - 添加头文件路径: 项目属性 → C/C++ → 常规 → 附加包含目录 → 添加DLL头文件路径 - 添加导入库: 链接器 → 输入 → 附加依赖项 → 添加 `MathLibrary.lib` **注意:Debug配置必须用`MathLibraryd.lib`(带'd'后缀),Release用`MathLibrary.lib`**[^1] - 设置库路径: 链接器 → 常规 → 附加库目录 → 添加`.lib`文件所在路径 2. **代码调用** ```cpp #include "MathLibrary.h" // 包含DLL头文件 int main() { // 调用导出函数 int sum = Add(3, 5); // 使用导出类 Calculator calc; int product = calc.Multiply(3, 5); return 0; } ``` 3. **运行时要求** - 将`MathLibrary.dll`放在客户端可执行文件同级目录 - 或复制到系统目录(如`C:\Windows\System32`) ##### 方法2:显式链接(动态加载) 无需头文件`.lib`,直接通过API加载: ```cpp #include <windows.h> int main() { HINSTANCE hDLL = LoadLibrary(L"MathLibrary.dll"); if (hDLL) { // 获取函数指针 typedef int (*AddFunc)(int, int); AddFunc add = (AddFunc)GetProcAddress(hDLL, "Add"); if (add) { int result = add(3, 5); // 调用DLL函数 } FreeLibrary(hDLL); // 卸载DLL } return 0; } ``` > **适用场景**: > - 插件式架构 > - 运行时按需加载 > - 避免名称修饰问题(需使用`extern "C"`)[^2] --- #### 关键注意事项 1. **ABI兼容性** - 客户端DLL必须使用**相同编译器**(如MSVC) - 避免跨CRT版本传递内存指针(可能引发崩溃) 2. **资源管理** - 内存分配/释放应在同一模块中完成 - 推荐在DLL中提供资源释放函数 3. **调试支持** - Debug版DLL需链接Debug版客户端 - 在VS中可设置多项目解决方案,同时调试DLL客户端 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值