简单生成动态库(实例讲解)
一、动态库的介绍
Linux下动态库文件的文件名形如 libxxx.so,其中so是Shared Object 的缩写,即可以共享的目标文件。
在链接动态库生成可执行文件时,并不会把动态库的代码复制到执行文件中,而是在执行文件中记录对动态库的引用。
程序执行时,再去加载动态库文件。如果动态库已经加载,则不必重复加载,从而能节省内存空间。
二、动态库生成三步骤
√设计源码文件
√编译位置无关码(PIC)型.o文件
√D链接动态库
编译位置无关码(PIC)型.o文件的方法一般采用C语言编译器的“-KPIC” 或者“fpic”选项。
PIC是 Position Independent Code 的缩写,表示要生成位置无关的代码,这是动态库需要的特性。不同系统之间是有区别的。
三、不同系统编译区别
①Sco和Solaris系列
cc -O -KPIC –G –o libd1.so d1.c
cc -O -KPIC –G –o libd2.so d2.c
编译器选项 -KPIC ,创建动态库的标志-G
cc –O -KPIC –c d1.c
cc –G –o libd1.so d1.o
②HP-UNIX系列
cc +Z –c d1.c
ld –b –o libd1.so d1.o
编译器选项 +Z,创建动态库的连接器标志是-b
③AIX系列
xlc_r4 –c d1.c
ld –G –bnoentry –bexpall –lc d1.o –O libd1.so
④Linux和其它使用gcc编译器系列的UNIX
gcc –fpic –c d1.c
gcc –shared –o libd1.so d1.o
当然。可以把编译和创建合并一起
Gcc –O –fpic –shared –o libd1.so d1.c
编译器选项 –fpic ,创建动态库的连接器标志是–shared
四、动态库的调用
动态库调用分两种方式:隐式和显示
4.1、隐式调用
设计一个调用自建动态库的例子,程序testdl.c调用libmax.so中的函数,相关代码如下:
①调用库函数代码
/*max.c*/
int max(int n1, int n2, int n3)
{
int max_num = n1;
max_num = max_num < n2? n2: max_num;
max_num = max_num < n3? n3: max_num;
return max_num;
}
编译生成共享库
[root@localhost chap301]# gcc -fPIC -shared -o libmax.so max.c
[root@localhost chap301]# ls
libmax.so max.c max.h
为动态库编写接口文件:目的是为了让用户知道我们的动态库中有哪些接口可用。
/*max.h*/
#ifndef __MAX_H__
#define __MAX_H__
int max(int n1, int n2, int n3);
#endif
②新建测试代码程序testdl.c,链接动态库生成可执行文件testdl
/*testdl.c*/
#include <stdio.h>
#include "max.h"
int main(int argc, char *argv[])
{
int a = 80, b = -10, c = 101;
printf("max number is %d.\n", max(a, b, c));
return 0;
}
[root@localhost chap301]# gcc -o testdl testdl.c -L. -lmax
[root@localhost chap301]# ls
libmax.so max.c max.h testdl testdl.c
注意,如果同一目录下同时存在同名的动态库和静态库,比如 libmax.so 和 libmax.a 都在当前路径下,则gcc会优先链接动态库。
[root@localhost chap301]# ./testdl
./testdl: error while loading sharedlibraries: libmax.so: cannot open shared object file: No such file or directory
动态库libmax.so确实在当前目录下,但是却说找不到,为何?
③动态库的查找路径分析
如何让执行程序testdl到特定的目录去找动态库呢?介绍三个方法:
⑴编辑/etc/ld.so.conf文件
原来Linux是通过/etc/ld.so.cache 文件搜寻要链接的动态库的。但是,/etc/ld.so.cache 是 ldconfig 程序读取 /etc/ld.so.conf 文件生成的。
现在我们把libmax.so 所在的路径添加到/etc/ld.so.conf 中,再以root权限运行ldconfig 程序,更新 /etc/ld.so.cache ,就可以找到 libmax.so。
[root@localhost chap301]#mv libmax.so /c_fun/dll
[root@localhost chap301]# ls
max.c max.h testdl testdl.c
[root@localhost chap301]# vim /etc/ld.so.conf.d/testdl.conf
[root@localhost chap301]# cat /etc/ld.so.conf.d/testdl.conf
/c_fun/dll
[root@localhost chap301]# ldconfig
[root@localhost chap301]# ./testdl
max number is 101.
这样执行程序testdl可以找到动态库testdl.so,得到正确的运行结果。
注意, /etc/ld.so.conf 中并不必包含 /lib 和 /usr/lib,ldconfig程序会自动搜索这两个目录。这也是为何很多程序在安装时直接把dll库直接cp到这两个目录,这样可执行程序即可自动找到dll原因!
⑵使用环境变量来注册动态库
本例中,如果把动态库移出其它的目录,执行程序是否还能正常执行呢?现在我们做个实验来分析:
[root@localhost chap301]# mkdir dll
[root@localhost chap301]# mv /c_fun/dll/libmax.so ./dll
[root@localhost chap301]# ./testdl
./testdl: error while loading sharedlibraries: ./libmax.so: cannot open shared object file: No such file ordirectory
程序执行失败,无法找到libmax.so位置。
这时我们可以使用Linux的环境变量把动态库进行注册,即把libmax.so动态库的路径注册到环境变量,程序执行时遍历这个变量逐个查找。如下:
[root@localhostchap301]# LD_LIBRARY_PATH=./dll
[root@localhost chap301]# export LD_LIBRARY_PATH
[root@localhost chap301]# ./testdl
max number is 101.
正常运行,LD_LIBRARY_PATH=./dll 是告诉 testdl先在当前路径下的dll目录中去寻找链接的动态库。
⑶编译时指定路径
即在编译时明确指定动态库的路径
[root@localhost chap301]# mkdir dllpath
[root@localhost chap301]# ls
dll max.c max.h testdl.c
[root@localhost chap301]# gcc -O -o testdl testdl.c ./dll/libmax.so
[root@localhost chap301]# ./testdl
max number is 101.
上述例子中我们可以看出,动态库隐式调用与静态库很接近。
4.2、显式调用
通过函数(如dlopen()函数)调用动态库代码,一般会经过打开动态库、获取动态库对象地址、调用动态库对象,错误检查和关闭动态库五个步骤。
①打开动态库
void *dlopen(const char *pathname,int mode)
pathname:带路径的动态库名称
mode:动态库加载方式:RTLD_LAZY RTLD_NOW
②获取动态库对象的地址
void dlsym(void *handle,const char *name)
handle:有上面dlopen函数返回的句柄
name:待调用的动态库对象(库中函数名或变量名)
③调用动态库对象
函数调用:
定义动态库中函数类型的变量,如本文中libmax.so库中int max()函数。
int (*pFunc)();
pFunc=(int(*)())dlsym(void pHandle,”max”);
if(pFunc)
{
inta = 80, b = -10, c = 101;
printf("maxnumber is %d.\n", pFunc(a, b, c));
}
④错误检查
char *dlerror(void);
无错误返回NULL
⑤关闭动态库
int dlclose(void *handle);
现在我们通过具体实例来讲解
代码文件max.c、max.h同上范例一样,只需要重新编辑testdl.c文件,使用显示的调用方法来使用libmax.so动态库中函数max()。如下:
[root@localhost chap302]# pwd
/c_fun/mycode/chap302
[root@localhost chap302]# ls
libmax.so max.c max.h testdl.c
[root@localhost chap302]# vim testdl.c
/*testdl.c*/
#include<stdio.h>
#include<dlfcn.h>
#include"max.h"
intmain(int argc, char *argv[])
{
void *pHandle;
int (*pFunc)();
char *error;
pHandle=dlopen("./libmax.so",RTLD_NOW);
error=dlerror();
if(error)
{
printf("lib loaderror:%s\n",error);
exit(1);
}
if(!pHandle)
{
printf("Cann't find libxxx.so filesof DLL \n");
return 0;
}
pFunc=(int(*)())dlsym(pHandle,"max");
if(pFunc)
{
int a = 80, b = -10, c = 101;
printf("max number is %d.\n",pFunc(a, b, c));
}
else
{
printf("Cann't find max() func\n");
}
dlclose(pHandle);
return 1;
}
[root@localhost chap302]# gcc -O -o testdl testdl.c -ldl
[root@localhost chap302]# ls
libmax.so max.c max.h testdl testdl.c
[root@localhost chap302]# ./testdl
max number is 101.
五、动态库的Makefile例子
/*Makefile*/
#use make build: build libmax.so
#use make test: build elf and run
#use make clean: clean all *.o *.so
.PHONY: build test clean
build: libmax.so
libmax.so: max.o
gcc -o $@ -shared $<
max.o: max.c
gcc -c -fPIC $<
test: testdl
testdl: testdl.c libmax.so
#gcc -o $@ testdl.c -L. -lmax
gcc -o $@ testdl.c -ldl
LD_LIBRARY_PATH=.
./testdl
clean:
rm -f *.o *.so
[root@localhost chap302]# make build
gcc -c -fPIC max.c
gcc -o libmax.so -shared max.o
[root@localhost chap302]# ls
libmax.so Makefile max.c max.h max.o
技巧:对于elf格式的可执行程序,它先后搜索elf文件的 DT_RPATH 段,环境变量 LD_LIBRARY_PATH,/etc/ld.so.cache文件列表, /lib/,/usr/lib目录, 找到库文件后将其载入内存。