一、函数说明

#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

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;
}

 四、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.该库基本不需要被多个应用程序共享

 二、编译参数-rdynamic

1、是一个链接器选项,当将所有的*.o和库链接到最终可执行文件中时,实际上就使用了它。

2、该参数的作用是:将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号,但不包括静态符号,比如被static修饰的函数)都添加到动态符号表(即.dynsym表)里,以便那些通过dlopen()或backtrace()(这一系列函数使用.dynsym表内符号)这样的函数使用。

三、符号和符号表

符号就是其实程序中的变量名、函数名。本质是:指被分配了存储空间。如果是函数名则指代码所在区;如果是变量名则指其所在的静态数据区。

所有定义的符号的值就是其目标所在的首地址。因此,符号的解析就是将符号引用和符号定义建立关联后,将引用符号的地址重定位为相关联的符号定义的地址。

每个可重定位目标模块m都有一个符号表,它包含了在m中定义和引用的符号。

  四、函数指针

函数指针是指向函数的指针变量

#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) */