动态链接库dlopen函数的使用
一、函数说明
#include <dlfcn.h> void *dlopen(const char *filename, int flag);
//dlopen用于打开指定名字(filename)的动态链接库,并返回操作句柄 void *dlsym(void *handle, const char *symbol);
//根据动态链接库操作句柄与符号,返回符号对应的地址。使用这个函数不但可以获取函数地址,也可以获取变量地址。handle是由dlopen打开动态链接库后返回的指针,symbol就是要求获取的函数或全局变量的名称.
int dlclose(void *handle);
//用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。 char *dlerror(void);
//当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。
二、生成动态链接库
caculate.c
gcc -fPIC -shared caculate.c -o libcaculate.so
int add(int a, int b) { return (a + b); }
三、动态链接库的加载
dltest.c
gcc -rdynamic -o main dltest.c -ldl
-ldl表示:显示加载动态库libdl.so
#include <stdio.h> #include <stdlib.h> #include <dlfcn.h> #define LIB_CACULATE_PATH "./libcaculate.so" typedef int (*CAC_FUNC) (int, int); int main() { void *handle; char *error; CAC_FUNC cac_func = NULL;
//打开动态链接库 handle = dlopen(LIB_CACULATE_PATH, RTLD_LAZY); if (!handle) { fprintf(stderr, "%s\n", dlerror()); exit(EXIT_FAILURE); }
//清除之前存在的错误
dlerror();
//获取一个函数 *(void **) (&cac_func) = dlsym(handle, "add"); if ((error = dlerror()) != NULL) { fprintf(stderr, "%s\n", error); exit(EXIT_FAILURE); } printf("add:%d\n", (*cac_func)(2,7));
//关闭动态链接库 dlclose(handle); exit(EXIT_FAILURE); return 0; }
原文链接:动态链接库dlopen的函数的使用_A493203176的博客-CSDN博客_dlopen
四、dlsym函数的使用
比如,假设在so中定义了一个void mytest()函数,那在使用so时先声明一个函数指针:void (*pMytest)(int),
然后使用dlsym函数指针pMytest指向mytest函数,pMytest = (void (*)(int))dlsym(handle, "mytest");
=============================================================
补充:
一、编译参数-fPIC
linux下使用gcc -fPIC作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code).
如果不加-fPIC,则加载.so文件的代码段时,代码段引用的数据对象需要重定位, 重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的copy。每个copy都不一样,取决于这个.so文件代码段和数据段内存映射的位置。不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位的.(因为它里面的代码并不是位置无关代码) 如果被多个应用程序共同使用,那么它们必须每个程序维护一份so的代码副本了.(因为so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)
我们总是用fPIC来生成so,也从来不用fPIC来生成a。
fPIC与动态链接可以说基本没有关系,libc.so一样可以不用fPIC编译,只是这样的so必须要在加载到用户程序的地址空间时重定向所有表目. 因此,不用fPIC编译so并不总是不好。
如果你满足以下4个需求/条件时,可以不使用-fPIC选项。
1.该库可能需要经常更新
2.该库需要非常高的效率(尤其是有很多全局量的使用时)
3.该库并不很大.
4.该库基本不需要被多个应用程序共享
原文链接:gcc -fPIC - 时令 - 博客园 (cnblogs.com)
二、编译参数-rdynamic
1、是一个链接器选项,当将所有的*.o
和库链接到最终可执行文件中时,实际上就使用了它。
2、该参数的作用是:将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号,但不包括静态符号,比如被static修饰的函数)都添加到动态符号表(即.dynsym表)里,以便那些通过dlopen()或backtrace()(这一系列函数使用.dynsym表内符号)这样的函数使用。
三、符号和符号表
符号就是其实程序中的变量名、函数名。本质是:指被分配了存储空间。如果是函数名则指代码所在区;如果是变量名则指其所在的静态数据区。
所有定义的符号的值就是其目标所在的首地址。因此,符号的解析就是将符号引用和符号定义建立关联后,将引用符号的地址重定位为相关联的符号定义的地址。
每个可重定位目标模块m都有一个符号表,它包含了在m中定义和引用的符号。
原文链接:程序的链接(三):符号和符号表 - 简书 (jianshu.com)
四、函数指针
函数指针是指向函数的指针变量
#include <stdio.h> int MyFun(int x) { fprintf(stdout, "%d\n", x); } int main() { int (*FunP)(int); //声明函数指针
int a = 2; FunP = MyFun; FunP(a); return 0; }
说明:
1)MyFun的函数名与FunP函数指针都是一样的,即都是函数指针。MyFun函数名是一个函数指针常量,而FunP是一个函数数指针变量,这是它们的关系。
2)但函数名调用如果都如(*MyFun)(10)这样,那书写与读起来都是不方便和不习惯的,所以C语言的设计者们才会设计成又可允许MyFun(10)这种形式的调用
3)为统一起见,FunP函数指针变量也可以FunP(10)的形式来调用
4)赋值时,即可FunP = &MyFun形式,也可FunP = MyFun
5)在函数的声明处:
void MyFun(int); /* 不能写成void (*MyFun)(int) */
void (*FunP)(int); /* 不能写成void FunP(int) */
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理