LINUX下动态链接库的使用(dlopen/dlsym/dlclose/dlerror)

走远了吗. 提交于 2019-12-18 00:31:53

dlopen

功能:打开一个动态链接库 
包含头文件: #include <dlfcn.h> 
函数定义: void * dlopen( const char * pathname, int mode ); 
函数描述: 在dlopen的()函数以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程。使用dlclose()来卸载打开的库。 
mode:
  RTLD_LAZY 暂缓决定,等有需要时再解出符号 
  RTLD_NOW 立即决定,返回前解除所有未决定的符号。 
  RTLD_LOCAL 

  RTLD_GLOBAL 允许导出符号 
  RTLD_GROUP 
  RTLD_WORLD 

返回值: 
  打开错误返回NULL 
  成功,返回库引用 

编译时候要加入 -ldl (指定dl库) 
  
例如 
  gcc test.c -o test -ldl

  dlopen()是一个强大的库函数。该函数将打开一个新库,并把它装入内存。该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的。比如 Apache Web 服务器利用这个函数在运行过程中加载模块,这为它提供了额外的能力。一个配置文件控制了加载模块的过程。这种机制使得在系统中添加或者删除一个模块时,都不需要重新编译了。 
  可以在自己的程序中使用 dlopen()。dlopen() 在 dlfcn.h 中定义,并在 dl 库中实现。它需要两个参数:一个文件名和一个标志。文件名可以是我们学习过的库中的 soname。标志指明是否立刻计算库的依赖性。如果设置为 RTLD_NOW 的话,则立刻计算;如果设置的是 RTLD_LAZY,则在需要的时候才计算。另外,可以指定 RTLD_GLOBAL,它使得那些在以后才加载的库可以获得其中的符号。 

  当库被装入后,可以把 dlopen() 返回的句柄作为给 dlsym() 的第一个参数,以获得符号在库中的地址。使用这个地址,就可以获得库中特定函数的指针,并且调用装载库中的相应函数。

dlsym
   

dlsym()的函数原型是 void* dlsym(void* handle,const char* symbol) 该函数在<dlfcn.h>文件中。 
handle是由dlopen打开动态链接库后返回的指针,symbol就是要求获取的函数的名称,函数返回值是void*,指向函数的地址,供调用使用

取动态对象地址:
#include <dlfcn.h>
void *dlsym(void *pHandle, char *symbol);
dlsym根据动态链接库操作句柄(pHandle)与符号(symbol),返回符号对应的地址。
使用这个函数不但可以获取函数地址,也可以获取变量地址。比如,假设在so中定义了一个void mytest()函数,那在使用so时先声明一个函数指针:void (*pMytest)(),然后使用dlsym函数将函数指针pMytest指向mytest函数,
pMytest = (void (*)())dlsym(pHandle, "mytest");

dlclose


dlclose() 包含头文件: #include <dlfcn.h> 
函数原型为: int dlclose (void *handle); 
函数描述: dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。

dlerror

dlerror() 包含头文件: #include <dlfcn.h> 
函数原型: const char *dlerror(void); 
函数描述: 当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。

 

编译函数源程序时选用-shared选项即可创建动态链接库,注意应以.so后缀命名,最好放到公用库目录(如/lib,/usr/lib等)下面,并要写好用户接口文件,以便其它用户共享。使用动态链接库,源程序中要包含dlfcn.h头文件,写程序时注意dlopen等函数的正确调用,编译时要采用-rdynamic选项与-ldl选项 ,以产生可调用动态链接库的执行代码。

示例一

//foo.c    Load the math library, and print the cosine of 2.0: 
#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));
 
     cac_func = (CAC_FUNC)dlsym(handle, "sub");
     printf("sub: %d\n", cac_func(9,2));
 
     cac_func = (CAC_FUNC)dlsym(handle, "mul");
     printf("mul: %d\n", cac_func(3,2));
 
     cac_func = (CAC_FUNC)dlsym(handle, "div");
     printf("div: %d\n", cac_func(8,2));
 
     //关闭动态链接库
     dlclose(handle);
     return 0;
 }


 gcc -rdynamic -o foo foo.c -ldl

示例二

//主程序
#include <stdlib.h>
#include <dlfcn.h>
#include <stdio.h>

//申明结构体
typedef struct __test {
    int i;
    void (* echo_fun)(struct __test *p);
}Test;

//供动态库使用的注册函数
void __register(Test *p) {
    p->i = 1;
    p->echo_fun(p);
}

int main(void) {
    void *handle = NULL;
    char *myso = "./mylib.so";

    if((handle = dlopen(myso, RTLD_NOW)) == NULL) {
        printf("dlopen - %sn", dlerror());
        exit(-1);
    }

    return 0;
}
//动态库
#include <stdio.h>
#include <stdlib.h>

//申明结构体类型
typedef struct __test {
    int i;
    void (*echo_fun)(struct __test *p);
}Test;

//申明注册函数原型
void __register(Test *p);

static void __printf(Test *p) {
    printf("i = %dn", p->i);
}

//动态库申请一个全局变量空间。这种 ".成员"的赋值方式为c99标准
static Test config = {
    .i = 0,
    .echo_fun = __printf,
};

//加载动态库的自动初始化函数
void _init(void) {
    printf("initn");
    __register(&config);//调用主程序的注册函数
}

 

动态库编译: gcc -shared -fPIC -nostartfiles -o mylib.so mylib.c

主程序编译: gcc test.c -ldl -rdynamic

主程序通过dlopen()加载一个.so的动态库文件, 然后动态库会自动运行 _init() 初始化函数, 初始化函数打印一个提示信息, 然后调用主程序的注册函数给结构体重新赋值, 然后调用结构体的函数指针, 打印该结构体的值. 这样就充分的达到了主程序和动态库的函数相互调用和指针的相互传递.

gcc参数 -rdynamic 用来通知链接器将所有符号添加到动态符号表中(目的是能够通过使用 dlopen 来实现向后跟踪).

gcc参数 -fPIC 作用: 当使用.so等类的库时,当遇到多个可执行文件共用这一个库时, 在内存中,这个库就不会被复制多份,让每个可执行文件一对一的使用,而是让多个可执行文件指向一个库文件,达到共用. 宗旨:节省了内存空间,提高了空间利用率.

示例三

//生成动态库 hello.c函数原型:
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

typedef struct {
 const char *module;
 int  (*GetValue)(char *pszVal);
 int   (*PrintfHello)();
} hello_ST_API;


int GetValue(char *pszVal)
{
 int retval = -1;
 
 if (pszVal)
  retval = sprintf(pszVal, "%s", "123456");
  printf("%s, %d, pszVer = %s\n", __FUNCTION__, __LINE__, pszVal);
 return retval;
}

int PrintfHello()
{
 int retval = -1;
 
 printf("%s, %d, hello everyone\n", __FUNCTION__, __LINE__);
 return 0;
}

const hello_ST_API  Hello = {
     .module = "hello",
   GetValue,
   PrintfHello,
};

 

编译的时候用指令:gcc -shared -o hello.so hello.c
上面的函数是用一个全局结构体hello来指向。在dlsym定义中说不仅可以获取函数的地址,还可以获取全局变量的地址。所以此处是想通过dlsym来获取全局变量的地址。好处自己慢慢体会。

//dlopen代码
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>

typedef struct {
 const char *module;
 int  (*GetValue)(char *pszVal);
 int   (*PrintfHello)();
} hello_ST_API;


int main(int argc, char **argv)
{
 hello_ST_API *hello;
 int i = 0;
 void *handle;
 char psValue[20] = {0};
 
 handle = dlopen(“库存放的绝对路径,你可以试试相对路径是不行的", RTLD_LAZY);
 if (! handle) {
  printf("%s,%d, NULL == handle\n", __FUNCTION__, __LINE__);
  return -1;
 }
 dlerror();

 hello = dlsym(handle, "Hello");
 if (!hello) {
  printf("%s,%d, NULL == handle\n", __FUNCTION__, __LINE__);
  return -1;
 }

 if (hello && hello->PrintfHello)
  i = hello->PrintfHello();
  printf("%s, %d, i = %d\n", __FUNCTION__, __LINE__, i);
 if (hello && hello->GetValue)
  i = hello->GetValue(psValue);

 if (hello && hello->module)
  {
   printf("%s, %d, module = %s\n", __FUNCTION__, __LINE__, hello->module);
  }

    dlclose(handle);
    return 0;
}

编译指令:gcc -o test hello_dlopen.c -ldl

运行./test结果如下。

PrintfHello, 27, hello everyone
main, 36, i = 0
GetValue, 19, pszVer = 123456
main, 42, module = hello

可以看到结果正常出来了。看到没用?dlsym找到全局结构体hello后,可以直接用这个全局结构体指针来使用库里面的函数了,因为我们有时候提供的库不仅仅是一个两个函数的,一般的一个库都会存在多个函数,用这种方式就可以直接使用了。不然找函数名称的话要写多个dlsym

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!