getprocaddress和dlsym均需目标模块已加载,windows用loadlibrary/getmodulehandle获取句柄,linux用dlopen打开so;二者均不处理加载,仅查询地址,且须注意符号名修饰、类型转换及forwarder/data符号等限制。

Windows下用GetProcAddress查DLL导出符号必须先加载模块
直接调用GetProcAddress前,得确保DLL已加载进当前进程地址空间——它不负责加载,只查地址。常见错误是传入未加载的模块句柄(比如NULL或无效HMODULE),返回NULL但没检查错误码,结果后续调用崩溃。
正确流程是:先LoadLibrary或GetModuleHandle拿到有效句柄,再传给GetProcAddress。注意GetModuleHandle对延迟加载DLL可能返回NULL,此时需改用LoadLibrary强制加载。
示例片段:
HMODULE hMod = LoadLibrary(L"kernel32.dll");
if (hMod) {
FARPROC pFunc = GetProcAddress(hMod, "CreateFileW");
if (!pFunc) {
DWORD err = GetLastError(); // 错误码非0说明符号不存在或拼写错
}
}
Linux下dlsym依赖dlopen且符号名不含修饰
dlsym和GetProcAddress行为类似,但前提是目标SO已用dlopen打开。未dlopen就直接dlsym(RTLD_DEFAULT, ...)只能查当前可执行文件或全局符号,查不到未显式加载的SO里的符号。
关键细节:C++编译器会对函数名做name mangling,dlsym传的必须是mangled名。调试时可用nm -D libxxx.so或objdump -T确认真实导出名。若要避免mangling,函数声明需加extern "C"。
常见坑:
-
dlopen默认RTLD_LOCAL,符号不导出到全局,后续dlsym(RTLD_DEFAULT)查不到 -
dlerror()必须在每次dlsym后立即调用,否则被下次调用覆盖 - SO路径传相对路径时,依赖
LD_LIBRARY_PATH或RPATH,不是当前工作目录
跨平台枚举所有已加载模块并遍历符号不可行
操作系统不提供“列出当前进程所有DLL/SO及其全部导出符号”的标准API。Windows有EnumProcessModules能列模块基址,但无法直接获取其导出表;Linux下/proc/self/maps可读内存映射,但SO的符号表通常不在内存中(除非带调试信息且已加载)。
可行折中方案:
- Windows:用
EnumProcessModules+ 手动解析PE头部的导出表(需读取IMAGE_EXPORT_DIRECTORY,处理ordinal/name RVA,兼容性差且易受ASLR影响) - Linux:用
dl_iterate_phdr遍历加载的ELF,再用libelf或手动解析.dynsym段(要求SO含动态符号表,且程序有读权限) - 更实际的做法:只监控自己主动
LoadLibrary/dlopen过的模块,记录句柄+符号名列表
符号地址本身不等于可安全调用的函数指针
拿到地址只是第一步。C++里必须用正确类型强制转换,否则调用时参数压栈/返回值处理错位,轻则逻辑错误,重则栈破坏。例如导出函数是int func(int, char*),却用typedef void(*)() fp_t转,调用必崩。
建议做法:
- 定义与导出函数签名完全一致的函数指针类型,用
reinterpret_cast(C++风格)或C风格强转 - Windows上某些DLL导出的是forwarder(如
ntdll.dll里转发到kernel32),GetProcAddress返回的是跳转指令地址,不能当普通函数调用 - SO里符号可能是数据而非函数(如
errno),dlsym返回地址后需按变量类型解引用,不能当函数调
最麻烦的是模板实例化或类成员函数——它们根本不会出现在动态符号表里,这类符号无法通过运行时dlsym/GetProcAddress获取。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











