一、动态链接库源文件示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//my.h
#ifndef __my__
#define __my__

#if defined(__cplusplus)
extern "C"
{
#endif

int max(int a, int b);

#if defined(__cplusplus)
}
#endif

#endif
1
2
3
4
5
6
7
//my.c
#include "my.h"

int max(int a, int b)
{
return a>b?a:b;
}

二、生成动态链接库

1
gcc -shared -fPIC my.c -o libmy.so 

三、使用动态链接库示例

1
2
3
4
5
6
7
8
9
//main.c
#include <stdio.h>
#include "my.h"

int main()
{
printf("%d\r\n",max(1,100));
return 0;
}

四、动态链接库编译选项

使用动态链接库,需要设置动态库的搜索路径,先后顺序是:

  1.编译目标代码时指定的动态库搜索路径;

  2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径;

  3.配置文件/etc/ld.so.conf中指定的动态库搜索路径;

  4.默认的动态库搜索路径/lib/usr/lib。

因此有4种方法,

1
2
3
4
5
6
7
8
9
#1
gcc main.c -o main -L. -lmy -Wl,-rpath='.' #'.'指当前目录
#或者
gcc main.c -o main -L. -lmy -Wl,-rpath,-Wl,'.'

#2
export LD_LIBRARY_PATH=. #当前目录
gcc main.c -o main -lmy

对于方法3,查看虚拟机,

1
2
3
4
5
6
7
8
9
ly@qzwjer:luatest$ cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf

ly@qzwjer:luatest$ ls /etc/ld.so.conf.d
fakeroot-x86_64-linux-gnu.conf libc.conf vmware-tools-libraries.conf x86_64-linux-gnu.conf
ly@qzwjer:luatest$ cat /etc/ld.so.conf.d/libc.conf
# libc default configuration
/usr/local/lib

因此可以在 /etc/ld.so.conf.d/ 目录下建立自己的 conf 文件,或在 libc.conf 中,加入动态链接库的目录。

方法4不需赘言。

五、ldd查看使用的共享库

可以通过 ldd a 来看程序 a 使用的共享函数库。

六、动态加载的函数库

以下内容原文链接:https://blog.csdn.net/ithomer/article/details/7346146

动态加载的函数库Dynamically loaded (DL) libraries是一类函数库,它可以在程序运行过程中的任何时间加载。它们特别适合在函数中加载一些模块和plugin扩展模块的场合,因为它可以在当程序需要某个plugin模块时才动态的加载。例如,Pluggable Authentication Modules(PAM)系统就是用动态加载函数库来使得管理员可以配置和重新配置身份验证信息。

Linux系统下,DL函数库与其他函数库在格式上没有特殊的区别,我们前面提到过,它们创建的时候是标准的object格式。主要的区别就是这些函数库不是在程序链接的时候或者启动的时候加载,而是通过一个API来打开一个函数库,寻找符号表,处理错误和关闭函数库。通常C语言环境下,需要包含这个头文件。

Linux中使用的函数和Solaris中一样,都是dlpoen() API。当然不是所有的平台都使用同样的接口,例如HP-UX使用shl_load()机制,而Windows平台用另外的其他的调用接口。如果你的目的是使得你的代码有很强的移植性,你应该使用一些wrapping函数库,这样的wrapping函数库隐藏不同的平台的接口区别。一种方法是使用glibc函数库中的对动态加载模块的支持,它使用一些潜在的动态加载函数库界面使得它们可以夸平台使用。具体可以参考http://developer.gnome.org/doc/API/glib/glib-dynamic-loading-of-modules.html. 另外一个方法是使用libltdl,是GNU libtool的一部分,可以进一步参考CORBA相关资料。

4.1. dlopen()
dlopen函数打开一个函数库然后为后面的使用做准备。C语言原形是:

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

如果文件名filename是以“/”开头,也就是使用绝对路径,那么dlopne就直接使用它,而不去查找某些环境变量或者系统设置的函数库所在的目录了。否则dlopen()就会按照下面的次序查找函数库文件:

  1. 环境变量LD_LIBRARY指明的路径。

  2. /etc/ld.so.cache中的函数库列表。

  3. /lib目录,然后/usr/lib。不过一些很老的a.out的loader则是采用相反的次序,也就是先查 /usr/lib,然后是/lib。
    dlopen()函数中,参数flag的值必须是RTLD_LAZY或者RTLD_NOW,RTLD_LAZY的意思是resolve undefined symbols as code from the dynamic library is executed,而RTLD_NOW的含义是resolve all undefined symbols before dlopen() returns and fail if this cannot be done’。
    如果有好几个函数库,它们之间有一些依赖关系的话,例如X依赖Y,那么你就要先加载那些被依赖的函数。例如先加载Y,然后加载X。

    dlopen()函数的返回值是一个句柄,然后后面的函数就通过使用这个句柄来做进一步的操作。如果打开失败dlopen()就返回一个NULL。如果一个函数库被多次打开,它会返回同样的句柄。
    如果一个函数库里面有一个输出的函数名字为_init,那么_init就会在dlopen()这个函数返回前被执行。我们可以利用这个函数在我的函数库里面做一些初始化的工作。我们后面会继续讨论这个问题的。

4.2. dlerror()

通过调用dlerror()函数,我们可以获得最后一次调用dlopen(),dlsym(),或者dlclose()的错误信息。

4.3. dlsym()

如果你加载了一个DL函数库而不去使用当然是不可能的了,使用一个DL函数库的最主要的一个函数就是dlsym(),这个函数在一个已经打开的函数库里面查找给定的符号。这个函数如下定义:

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

函数中的参数handle就是由dlopen打开后返回的句柄,symbol是一个以NIL结尾的字符串。如果dlsym()函数没有找到需要查找的symbol,则返回NULL。如果你知道某个symbol的值不可能是NULL或者0,那么就很好,你就可以根据这个返回结果判断查找的symbol是否存在了;不过,如果某个symbol的值就是NULL,那么这个判断就有问题了。标准的判断方法是先调用dlerror(),清除以前可能存在的错误,然后调用dlsym()来访问一个symbol,然后再调用dlerror()来判断是否出现了错误。一个典型的过程如下:

1
2
3
4
5
6
7
dlerror();		/*clear error code */
s = (actual_type)dlsym(handle, symbol_being_searched_for);
if((error = dlerror()) != NULL){
/* handle error, the symbol wasn't found */
} else {
/* symbol found, its value is in s */
}

4.4. dlclose()

dlopen()函数的反过程就是dlclose()函数,dlclose()函数用力关闭一个DL函数库。Dl函数库维持一个资源利用的计数器,当调用dlclose的时候,就把这个计数器的计数减一,如果计数器为0,则真正的释放掉。真正释放的时候,如果函数库里面有_fini()这个函数,则自动调用_ fini()这个函数,做一些必要的处理。Dlclose()返回0表示成功,其他非0值表示错误。

4.5. DL Library Example

下面是一个例子。例子中调入math函数库,然后打印2.0的余弦函数值。例子中每次都检查是否出错。应该是个不错的范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(int argc, char *argv){
void *handle;
char *error;

double (*cosine )(double);
handle = dlopen("/lib/libm.so.6", RTLD_LAZY);
if(!handle){
fputs(dlerror(), stderr);
exit(1);
}

cosine = dlsym(handle, "cos");
if((error = dlerror()) != NULL){
fputs(error, stderr);
exit(1);
}

printf("%f", (*cosine)(2, 0));

dlclose(handle);

return 0;
}

如果这个程序名字叫foo.c,那么用下面的命令来编译:

1
gcc -o foo foo.c –ldl