Linux系统下的dlopen、dlsym、dlclose

本文介绍了如何在Linux环境下创建和使用动态链接库。通过具体示例详细讲解了使用gcc编译器生成动态链接库的方法,以及如何利用dlopen、dlsym等函数动态加载和调用库中的函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、前言

  为了使程序方便扩展,具备通用性,可以采用插件形式。采用异步事件驱动模型,保证主程序逻辑不变,将各个业务已动态链接库的形式加载进来,这就是所谓的插件。linux提供了加载和处理动态链接库的系统调用,非常方便。本文先从使用上进行总结,涉及到基本的操作方法,关于动态链接库的本质及如何加载进来,需要进一步学习,后续继续补充。如何将程序设计为插件形式,挖掘出主题和业务之间的关系,需要进一步去学习。

2、生产动态链接库

编译参数 gcc -fPIC -shared 

例如将如下程序编译为动态链接库libcaculate.so,程序如下:

复制代码
int add(int a,int b)
{
    return (a + b);
}

int sub(int a, int b)
{
    return (a - b);
}

int mul(int a, int b)
{
    return (a * b);
}

int div(int a, int b)
{
    return (a / b);
}
复制代码

编译如下: gcc -fPIC -shared caculate.c -o libcaculate.so 

3、dlopen、dlsym函数介绍

      在linux上man dlopen可以看到使用说明,函数声明如下:

复制代码
#include <dlfcn.h>

void *dlopen(const char *filename, int flag);

char *dlerror(void);

void *dlsym(void *handle, const char *symbol);

int dlclose(void *handle);
复制代码

  dlopen以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程,dlerror返回出现的错误,dlsym通过句柄和连接符名称获取函数名或者变量名,dlclose来卸载打开的库。 dlopen打开模式如下:

  RTLD_LAZY 暂缓决定,等有需要时再解出符号 
  RTLD_NOW 立即决定,返回前解除所有未决定的符号。

采用上面生成的libcaculate.so,写个测试程序如下:

复制代码
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <dlfcn.h>
 4 
 5 //动态链接库路径
 6 #define LIB_CACULATE_PATH "./libcaculate.so"
 7 
 8 //函数指针
 9 typedef int (*CAC_FUNC)(int, int);
10 
11 int main()
12 {
13     void *handle;
14     char *error;
15     CAC_FUNC cac_func = NULL;
16 
17     //打开动态链接库
18     handle = dlopen(LIB_CACULATE_PATH, RTLD_LAZY);
19     if (!handle) {
20     fprintf(stderr, "%s\n", dlerror());
21     exit(EXIT_FAILURE);
22     }
23 
24     //清除之前存在的错误
25     dlerror();
26 
27     //获取一个函数
28     *(void **) (&cac_func) = dlsym(handle, "add");
29     if ((error = dlerror()) != NULL)  {
30     fprintf(stderr, "%s\n", error);
31     exit(EXIT_FAILURE);
32     }
33     printf("add: %d\n", (*cac_func)(2,7));
34 
35     cac_func = (CAC_FUNC)dlsym(handle, "sub");
36     printf("sub: %d\n", cac_func(9,2));
37 
38     cac_func = (CAC_FUNC)dlsym(handle, "mul");
39     printf("mul: %d\n", cac_func(3,2));
40 
41     cac_func = (CAC_FUNC)dlsym(handle, "div");
42     printf("div: %d\n", cac_func(8,2));
43 
44     //关闭动态链接库
45     dlclose(handle);
46     exit(EXIT_SUCCESS);
47 }
复制代码

编译选项如下:gcc -rdynamic -o main main.c -ldl

测试结果如下所示:

 

<think>我们正在讨论Windows下替代Linuxdlopen, dlsym, dlclose函数的方案。在Windows中,动态链接库(DLL)的操作使用不同的API。主要API包括:1. LoadLibrary或 LoadLibraryEx:用于加载DLL(相当于dlopen)2. GetProcAddress:用于获取函数地址(相当于dlsym)3.FreeLibrary:用于卸载DLL(相当于dlclose)注意:Windows没有直接对应于dlerror的函数,我们可以使用GetLastError来获取错误代码,然后通过FormatMessage将其转换为可读的消息。另外,Windows下没有与RTLD_LAZY和RTLD_NOW完全对应的标志,但LoadLibraryEx提供了一些标志来控制加载行为,例如DONT_RESOLVE_DLL_REFERENCES(类似于延迟加载,但不完全相同)和LOAD_LIBRARY_SEARCH_*系列标志(用于安全加载)。我们将按照以下步骤进行:1.使用LoadLibrary或LoadLibraryEx加载DLL。2.使用GetProcAddress获取函数指针。3.调用函数。4.使用FreeLibrary卸载DLL。下面是一个示例代码,展示如何在Windows下动态加载一个DLL并调用其中的函数。假设我们有一个DLL,它导出了一个函数:intadd(inta,int b)步骤:1.编写DLL(这里以简单的add函数为例)2.编写主程序动态加载该DLL并调用add函数首先,我们创建DLL项目(这里以C语言为例):文件:mathlib.c__declspec(dllexport) intadd(inta,int b) {returna +b;}编译DLL(使用VisualStudio命令行工具或MinGW):使用MSVC:cl /LD mathlib.c使用MinGW:gcc -shared -o mathlib.dllmathlib.c然后,编写主程序:文件:main.c```c#include <stdio.h> #include<windows.h>int main() {//1.加载DLLHINSTANCEhDll =LoadLibrary(TEXT("mathlib.dll"));if(hDll== NULL) {fprintf(stderr, "Failed toload DLL: errorcode %d\n", GetLastError());return1;}//2.获取函数地址//注意:函数原型为 int(int, int)typedef int(*AddFunc)(int,int);AddFunc add= (AddFunc)GetProcAddress(hDll,"add");if (add ==NULL){fprintf(stderr,"Failedto findfunction:error code%d\n",GetLastError());FreeLibrary(hDll);return1;}//3.调用函数int result= add(10,20);printf("10+20 =%d\n",result);//4.卸载DLLif(!FreeLibrary(hDll)) {fprintf(stderr, "Failed tofree DLL: errorcode %d\n", GetLastError());return1;}return0; }```编译主程序(假设使用MSVC):cl main.c /link user32.lib#注意:在Windows中,LoadLibrary等函数在Kernel32.lib中,但通常链接器会自动链接,如果出现未解析的外部符号,可以指定链接kernel32.lib或者使用MinGW:gcc -o mainmain.c -lkernel32运行:main.exe注意事项:-在Windows中,函数名在DLL中可能会被修饰(特别是C++),为了避免修饰,可以使用extern"C"(在C++中)或者使用.def文件指定导出名称。-如果使用C++,并且不想使用extern "C",那么需要知道修饰后的名称(可以使用dumpbin工具查看导出符号)。关于错误处理:Windows使用GetLastError()获取错误码,然后通过FormatMessage转换为字符串。我们可以写一个辅助函数来打印错误信息。例如,改进错误信息输出: ```c#include <stdio.h> #include<windows.h>void PrintError(constchar*prefix){DWORDerror =GetLastError();if(error==0){return;}LPVOID lpMsgBuf;FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_IGNORE_INSERTS,NULL,error,MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),(LPTSTR)&lpMsgBuf,0,NULL);fprintf(stderr, "%s:%s\n",prefix,(char*)lpMsgBuf);LocalFree(lpMsgBuf); }int main() {HINSTANCEhDll =LoadLibrary(TEXT("mathlib.dll"));if (hDll ==NULL){PrintError("LoadLibrary failed");return1;}//...其余代码同上} ```这样,错误信息就会更友好。总结:Windows下使用LoadLibrary/GetProcAddress/FreeLibrary来替代Linuxdlopen/dlsym/dlclose。相关问题:1.在Windows中如何动态加载DLL并调用C++成员函数?2.如何避免WindowsDLL导出函数时的名称修饰?3.Windows下动态加载DLL时如何处理依赖关系?4.在Windows中,如何实现类似RTLD_LAZY(延迟绑定)的行为?5.Windows下动态加载DLL与静态链接DLL有什么区别?</think>### Windows 下替代 dlopen/dlsym/dlclose 的 API 方案 在 Windows 系统中,动态加载共享库(DLL)的功能由以下 API 提供,它们与 Linux 的 `dlfcn.h` 功能对应: | Linux 函数 | Windows 等效 API | 功能描述 | |-------------|---------------------------|------------------------------| | `dlopen()` | `LoadLibrary()` 或 `LoadLibraryEx()` | 加载 DLL 到进程内存空间 | | `dlsym()` | `GetProcAddress()` | 获取 DLL 中函数的地址 | | `dlclose()` | `FreeLibrary()` | 卸载 DLL 并释放资源 | | `dlerror()` | `GetLastError()` + `FormatMessage()` | 获取详细的错误信息 | #### 核心 API 详解 1. **`LoadLibrary()` / `LoadLibraryEx()`** - 功能:加载指定的 DLL 文件 - 参数: - `LPCTSTR lpFileName`:DLL 文件路径 - `DWORD dwFlags`(仅 `LoadLibraryEx`):加载选项(如 `DONT_RESOLVE_DLL_REFERENCES`) - 返回:`HMODULE` 句柄(失败返回 `NULL`) - 示例: ```c HMODULE hDll = LoadLibrary(TEXT("mylib.dll")); ``` 2. **`GetProcAddress()`** - 功能:获取导出函数的地址 - 参数: - `HMODULE hModule`:DLL 句柄 - `LPCSTR lpProcName`:函数名(或序号值) - 返回:函数指针(失败返回 `NULL`) - 示例: ```c typedef int (*AddFunc)(int, int); AddFunc add = (AddFunc)GetProcAddress(hDll, "add"); ``` 3. **`FreeLibrary()`** - 功能:递减 DLL 引用计数,计数为 0 时卸载 - 参数:`HMODULE hModule` - 返回:成功返回非零值 - 示例: ```c FreeLibrary(hDll); ``` 4. **错误处理** ```c if (hDll == NULL) { DWORD err = GetLastError(); LPVOID msgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, (LPTSTR)&msgBuf, 0, NULL); printf("Error %d: %s", err, (char*)msgBuf); LocalFree(msgBuf); } ``` #### 完整代码示例 ```c #include <windows.h> #include <stdio.h> int main() { // 1. 加载 DLL HMODULE hDll = LoadLibrary(TEXT("mathlib.dll")); if (!hDll) { printf("DLL load failed: %d\n", GetLastError()); return 1; } // 2. 获取函数地址 typedef int (*AddFunc)(int, int); AddFunc add = (AddFunc)GetProcAddress(hDll, "add"); if (!add) { printf("Function not found: %d\n", GetLastError()); FreeLibrary(hDll); return 1; } // 3. 调用函数 int result = add(10, 20); printf("10 + 20 = %d\n", result); // 4. 卸载 DLL if (!FreeLibrary(hDll)) { printf("Unload failed: %d\n", GetLastError()); } return 0; } ``` #### 关键差异说明 1. **名称修饰问题** - Windows C++ 编译器会对函数名进行修饰(如 `?add@@YAHHH@Z`) - 解决方案: - 使用 `extern "C"` 声明导出函数 - 通过 `.def` 文件显式指定导出名称 ```cpp // C++ 示例 extern "C" __declspec(dllexport) int add(int a, int b) { return a + b; } ``` 2. **路径搜索规则** Windows 按以下顺序搜索 DLL: ```mermaid graph LR A[应用程序目录] --> B[系统目录] B --> C[Windows目录] C --> D[PATH环境变量目录] ``` 3. **加载选项对比** | Linux 标志 | Windows 近似方案 | |------------------|-------------------------------| | `RTLD_LAZY` | 默认行为(延迟加载) | | `RTLD_NOW` | `LoadLibraryEx()` + `DONT_RESOLVE_DLL_REFERENCES` | | `RTLD_GLOBAL` | 通过 `.def` 文件设置导出符号 | | `RTLD_LOCAL` | 默认局部作用域 | > **最佳实践**:在 Windows 中建议使用 `LoadLibraryEx()` 配合 `LOAD_LIBRARY_SEARCH_*` 系列标志增强安全性,避免 DLL 劫持攻击[^1]。 #### 常见问题处理 1. **DLL 初始化失败** - 检查依赖项:使用 `Dependency Walker` 工具分析缺失的 DLL - 验证位数匹配:确保 DLL 和应用程序同为 32 位或 64 位 2. **函数地址获取失败** - 使用 `dumpbin /EXPORTS mathlib.dll` 查看实际导出符号 - 对 C++ 函数使用 `GetProcAddress(hDll, (LPCSTR)1)` 通过序号导出 3. **内存泄漏检测** - 使用 `Process Explorer` 监控 DLL 加载状态 - 确保每个 `LoadLibrary()` 都有对应的 `FreeLibrary()`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值