动态链接器由操作系统提供,用户代码不应手写;应使用dlopen/dlsym(Linux/macOS)或LoadLibrary/GetProcAddress(Windows)等系统API加载和解析符号,而非自行解析ELF/PE文件结构。

动态链接器不是用户代码该实现的东西
你不需要、也不应该在 C++ 应用里“手写一个动态链接器”。操作系统内核和 C 运行时(如 ld-linux.so、dyld、ntdll.dll)已经提供了完整、安全、经过验证的动态链接能力。所谓“运行时加载 .so/.dll 并解析符号”,标准做法是调用系统提供的 API,而不是自己解析 ELF/PE 文件头、重定位表、GOT/PLT、符号哈希桶……那些属于 glibc 或 musl 的内部职责。
用 dlopen/dlsym 加载共享库并获取函数指针
Linux/macOS 下最直接的方式就是 POSIX 的 dlopen 系列接口。Windows 上对应的是 LoadLibrary + GetProcAddress,但跨平台项目通常用抽象封装(如 dlfcn.h 的 Windows 兼容层)或 CMake 的 find_library + 条件编译。
-
dlopen("libfoo.so", RTLD_LAZY)返回句柄,失败时返回nullptr,错误信息用dlerror()获取 -
dlsym(handle, "my_function")返回void*,必须显式reinterpret_cast成目标函数类型,否则调用会 UB - 必须配对调用
dlclose(handle),否则资源泄漏;但注意:多次dlopen同一路径返回同一句柄,dlclose只是引用计数减一 - 符号未定义、版本不匹配、依赖缺失时,
dlopen失败,dlerror()返回类似"undefined symbol: xxx"的字符串
Windows 上用 LoadLibraryEx 和 GetProcAddress 的关键差异
Windows 的 DLL 加载行为更严格:默认不搜索 PATH,且符号名可能带修饰(C++ name mangling)。除非导出用 extern "C",否则 GetProcAddress 查不到 C++ 成员函数或重载函数。
- 加载时建议用
LoadLibraryEx(path, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH),否则默认只查系统目录和 EXE 所在目录 - 导出符号必须在 DLL 工程中显式声明:
__declspec(dllexport)(MSVC)或__attribute__((visibility("default")))(GCC/Clang +-fvisibility=hidden) -
GetProcAddress返回FARPROC(即void*),同样需强制转型;传入的符号名是 ANSI 字符串,Unicode 路径要用LoadLibraryW - 没有等价于
RTLD_GLOBAL的 flag,DLL 间符号不可见,除非用GetModuleHandle+ 显式导出表遍历(不推荐)
真正需要“解析符号”的场景:插件系统或调试工具
如果你在写插件宿主(比如游戏引擎加载 mod)、二进制分析工具或自定义加载器(如 WASM AOT runtime),才可能需要读取 ELF/PE 的符号表。这时不要从零写,而应复用成熟库:
- Linux:用
libelf(elf_begin,elf_getscn,elf_symtab)或libbfd(binutils 底层) - 跨平台:用
LLVM Object库(llvm::object::ELFObjectFile/COFFObjectFile),它能统一处理符号节、重定位、动态段 - 绝对避免手动解析:ELF 的
.dynsym和.symtab结构依赖字节序、class(32/64)、ABI 版本;PE 的 export directory 嵌套多层,且有 ordinal 导出、forwarder chain 等特例
符号解析本身不等于链接——你拿到地址后,仍要处理重定位(R_X86_64_RELATIVE、R_X86_64_GLOB_DAT 等)、GOT 填充、TLS 模型适配。这些正是原生动态链接器每天在做的事,也是它不能被轻易替代的原因。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











