如何修复C动态库访问主程序external全局变量的编译错误?
问题:动态库访问主程序全局变量出现未定义引用错误
我正在学习C语言动态库相关知识,现有名为ProgA的C程序,通过Windows API动态加载LibB共享库,希望LibB能访问ProgA中声明为external的全局变量newtestimport,但编译时出现未定义引用错误。
演示代码
file header.h
#ifndef TEST_H #define TEST_H typedef struct test_import_s { int some_field; } test_import_t; extern test_import_t newtestimport; #endif
file prog_a.c
#include <stdio.h> #include <windows.h> #include "header.h" test_import_t newtestimport = { .some_field = 42 }; int main() { HINSTANCE hinstLib; typedef void (*FunctionPointer)(); newtestimport.some_field = 42; hinstLib = LoadLibrary("lib_b.dll"); if (hinstLib != NULL) { FunctionPointer initialize_lib_b; initialize_lib_b = (FunctionPointer)GetProcAddress(hinstLib, "initialize_lib_b"); if (initialize_lib_b != NULL) { initialize_lib_b(); } FreeLibrary(hinstLib); } return 0; }
file lib_b.c
#include <stdio.h> #include "header.h" test_import_t *timp; void initialize_lib_b() { timp = &newtestimport; int some_field = timp->some_field; printf("Result from function: %d\n", some_field); }
file CMakeLists.txt
cmake_minimum_required(VERSION 3.24) project(dynamic-library-2 C) set(CMAKE_C_STANDARD 23) add_library(lib_b SHARED lib_b.c) set_target_properties(lib_b PROPERTIES PREFIX "" OUTPUT_NAME "lib_b") add_executable(prog_a prog_a.c) target_link_libraries(prog_a lib_b)
编译错误信息
====================[ Build | prog_a | Debug ]================================== C:\Users\user1\AppData\Local\JetBrains\Toolbox\apps\CLion\ch-0\223.8617.54\bin\cmake\win\x64\bin\cmake.exe --build C:\Users\user1\projects\learning-c\cmake-build-debug --target prog_a -j 9 [1/2] Linking C shared library dynamic-library-2\lib_b.dll FAILED: dynamic-library-2/lib_b.dll dynamic-library-2/liblib_b.dll.a cmd.exe /C "cd . && C:\Users\user1\AppData\Local\JetBrains\Toolbox\apps\CLion\ch-0\223.8617.54\bin\mingw\bin\gcc.exe -fPIC -g -Wl,--export-all-symbols -shared -o dynamic-library-2\lib_b.dll -Wl,--out-implib,dynamic-library-2\liblib_b.dll.a -Wl,--major-image-version,0,--minor-image-version,0 dynamic-library-2/CMakeFiles/lib_b.dir/lib_b.c.obj -lkernel32 -luser32 -lgdi32 -lwinspool -lshell32 -lole32 -loleaut32 -luuid -lcomdlg32 -ladvapi32 && cd ." C:\Users\user1\AppData\Local\JetBrains\Toolbox\apps\CLion\ch-0\223.8617.54\bin\mingw\bin/ld.exe: dynamic-library-2/CMakeFiles/lib_b.dir/lib_b.c.obj:lib_b.c:(.rdata$.refptr.newtestimport[.refptr.newtestimport]+0x0): undefined reference to `newtestimport' collect2.exe: error: ld returned 1 exit status ninja: build stopped: subcommand failed.
解决方案
错误原因
编译LibB时,链接器找不到newtestimport的定义——该变量存在于主程序ProgA中,而非LibB。直接用extern声明后引用,链接器会尝试在LibB的编译依赖中查找该变量,但主程序是后续加载LibB的进程,编译LibB时主程序并未作为依赖存在,因此触发未定义引用错误。
方案1:通过初始化函数传递变量指针(推荐)
这是动态库交互的常规做法,避免DLL依赖主程序的具体符号,耦合性低、灵活性高。
- 修改header.h,更新初始化函数的签名,让它接收变量指针:
#ifndef TEST_H #define TEST_H typedef struct test_import_s { int some_field; } test_import_t; extern test_import_t newtestimport; // 修改初始化函数,接收test_import_t类型的指针参数 void initialize_lib_b(test_import_t* import_ptr); #endif
- 修改lib_b.c,移除对
newtestimport的直接引用,改用传入的指针:
#include <stdio.h> #include "header.h" test_import_t *timp; void initialize_lib_b(test_import_t* import_ptr) { timp = import_ptr; int some_field = timp->some_field; printf("Result from function: %d\n", some_field); }
- 修改prog_a.c,调用初始化函数时传递全局变量的地址:
#include <stdio.h> #include <windows.h> #include "header.h" test_import_t newtestimport = { .some_field = 42 }; int main() { HINSTANCE hinstLib; // 更新函数指针类型,匹配新的函数签名 typedef void (*FunctionPointer)(test_import_t*); newtestimport.some_field = 42; hinstLib = LoadLibrary("lib_b.dll"); if (hinstLib != NULL) { FunctionPointer initialize_lib_b; initialize_lib_b = (FunctionPointer)GetProcAddress(hinstLib, "initialize_lib_b"); if (initialize_lib_b != NULL) { // 传递全局变量的地址给DLL initialize_lib_b(&newtestimport); } FreeLibrary(hinstLib); } return 0; }
- 修改CMakeLists.txt,移除主程序对LibB的编译时链接(动态加载无需编译时依赖):
cmake_minimum_required(VERSION 3.24) project(dynamic-library-2 C) set(CMAKE_C_STANDARD 23) add_library(lib_b SHARED lib_b.c) set_target_properties(lib_b PROPERTIES PREFIX "" OUTPUT_NAME "lib_b") add_executable(prog_a prog_a.c) # 移除编译时链接语句 # target_link_libraries(prog_a lib_b)
方案2:主程序导出变量,DLL运行时引用(不推荐)
此方式耦合性极高,仅适用于特定场景:
- 修改prog_a.c,导出全局变量(Windows下使用
__declspec(dllexport)):
#include <stdio.h> #include <windows.h> #include "header.h" // 导出变量,让DLL能在运行时找到它 __declspec(dllexport) test_import_t newtestimport = { .some_field = 42 }; // 其余代码保持不变
- 修改lib_b.c,将
extern声明改为__declspec(dllimport):
#include <stdio.h> #include "header.h" // 声明为从主程序导入的变量 __declspec(dllimport) test_import_t newtestimport; test_import_t *timp; void initialize_lib_b() { timp = &newtestimport; int some_field = timp->some_field; printf("Result from function: %d\n", some_field); }
- 修改CMakeLists.txt,移除主程序对LibB的编译时链接:
cmake_minimum_required(VERSION 3.24) project(dynamic-library-2 C) set(CMAKE_C_STANDARD 23) add_library(lib_b SHARED lib_b.c) set_target_properties(lib_b PROPERTIES PREFIX "" OUTPUT_NAME "lib_b") add_executable(prog_a prog_a.c) # 移除编译时链接语句 # target_link_libraries(prog_a lib_b)
方案对比
方案1遵循动态库设计原则,DLL不依赖主程序的符号,仅通过参数接收数据,可移植性和可维护性更强;方案2强制绑定主程序和DLL的符号,主程序名称、变量名不能随意修改,耦合性极高,仅在特殊场景下使用。
内容的提问来源于stack exchange,提问作者user5864789
相关产品推荐
相关产品推荐

