You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复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依赖主程序的具体符号,耦合性低、灵活性高。

  1. 修改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
  1. 修改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);
}
  1. 修改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;
}
  1. 修改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运行时引用(不推荐)

此方式耦合性极高,仅适用于特定场景:

  1. 修改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
};

// 其余代码保持不变
  1. 修改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);
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 18:05:29