使用__declspec(dllimport)访问DLL全局变量报C2370重定义错误
问题根本原因
- MSVC对DLL导出函数和导出全局变量的默认处理逻辑存在明显差异:
- 导出函数即使不用
__declspec(dllimport)声明,仅用普通extern声明,链接时导入库(.lib)中的存根代码也会自动完成地址跳转,程序可以正常运行。 - 导出全局变量的处理逻辑完全不同:普通
extern real_T In1;声明会让编译器认为该变量存储在当前程序模块的内存空间中,直接生成直接寻址的读写指令。但实际上DLL导出的全局变量在应用程序侧的导入表中仅存储了指向DLL内部变量地址的指针,直接读写相当于修改了导入表本身的内存地址(该区域通常是只读的,或是无效地址),自然会触发崩溃。
- 导出函数即使不用
- 手动添加
__declspec(dllimport) real_T In1;后,编译器会识别这是DLL导入的变量,生成通过导入表指针间接寻址的指令,就可以正确访问DLL中的变量。但头文件中已存在extern real_T In1;,两个声明的存储类不一致,因此会报重定义错误。
无需修改原头文件的最优解决方案
有两种常用方案,均不需要改动原DLL提供的头文件:
方案1:预处理器临时替换extern关键字(最便捷,仅需应用侧修改)
在应用代码引入头文件的位置,临时用宏把extern替换为带dllimport的声明,引入完成后取消宏即可,示例代码如下:
#include <stddef.h> #include <stdio.h> #include "rtwtypes.h" // 引入头文件前临时替换extern #define extern __declspec(dllimport) extern #include "simple.h" // 立即取消宏定义,避免影响后续其他代码 #undef extern void main(void) { int i; simple_initialize(); // 可正常读写导入的全局变量 In1 = 10; for (i = 0; i < 10; i++) { simple_step(); } simple_terminate(); }
该方案不需要修改原DLL的任何文件,仅需调整应用侧的头文件引入逻辑即可,对函数声明也不会产生负面影响,反而会提升调用性能。
方案2:修改DLL头文件增加条件编译(标准工程化方案,可同时适配DLL编译和应用侧调用)
如果有权限修改DLL项目的代码,可以在simple.h中增加条件编译宏,自动区分DLL编译和调用场景的声明:
// 在simple.h头部添加以下宏定义 #ifdef SIMPLE_DLL_COMPILE // 该宏仅在DLL项目的编译配置中定义 #define SIMPLE_API __declspec(dllexport) #else #define SIMPLE_API __declspec(dllimport) #endif // 把原来的变量声明修改为 extern SIMPLE_API real_T In1; extern SIMPLE_API real_T Gain; extern SIMPLE_API real_T Increment; // 函数声明也可以用这个宏,性能比默认extern更好 SIMPLE_API void simple_initialize(void); SIMPLE_API void simple_step(void); SIMPLE_API void simple_terminate(void);
仅需要在DLL项目的预处理器配置中添加SIMPLE_DLL_COMPILE的定义,应用侧调用时不需要做任何额外配置,直接引入头文件即可正常读写全局变量,不会出现冲突或崩溃问题。
内容的提问来源于stack exchange,提问作者Amit Deshpande
相关产品推荐
相关产品推荐

