UNIX下C语言动态链接库SO
2009-11-22 专题
大恒 daheng99@qq.com
相关技术
gcc编译加-fPIC
Ø 以动态链接库的形式给出的。所以编译这个库的时候我们需要指定-fPIC,也就是指定生成的代码是位置无关的(position independent code)。 Ø 在-fPIC情况下,编译器将把所有的绝对位移用相对于0地址的相对位移代替,然后在引用这些位移的时候用库的装载地址作为基址来寻址。 Ø 动态连接, 这样程序可以支持二进制文件接口, 比如连接libc时一般都使用.so而不是.a, 你总不想在libc更新后重新链接你的程序吧? 实际上这种功能比所谓的share更重要, 应用也更广泛. Ø 也就是说你的应用程序所使用的so文件可以更新,但应用程序无需重新编译。 |
动态库的生成
动态库的生成可分为三个步骤,设计库源码、编译位置无关码(PIC)型.o文件和链接动态库。链接动态库的命令包含特殊标志,与链接静态库和链接可执行目标文件有区别,而且不同的Unix系统,其实现的细节也不尽相同。
编译PIC型.o中间文件的方法一般是采用C语言编译器的“-KPIC”或者“-fpic”选项,有的Unix版本C语言编译器缺省带上了PIC标志。创建最终动态库的方法一般是采用C语言编译器的“-G”或者“-shared”选项,或者直接使用工具ld创建。
本处设计一个创建并调用动态库的例子,分别将源文件d1.c和d2.c编译为动态库d1.so和d2.so,并在主程序中调用之。
1. 设计库源码
程序pr1.c提供了函数print和变量p,如代码3-4所示:
代码3-4动态库源码(节自/code/chapter3/d1.c)
int p = 2;
void print()
{
printf("This isthe first dll src!\n");
}
程序pr2.c也提供了自己的函数print和变量p,如代码3-5所示:
代码3-5动态库源码(节自/code/chapter3/d2.c)
int p = 3;
void print()
{
printf("This isthe second dll src!\n");
}
2. Sco和Solaris系列
通过cc的“-KPIC”选项生成PIC中间文件,再通过cc的“-G”选项链接成动态库:
cc –O -KPIC -c d1.c d2.c /*编译以“.o”为扩展名的中间目标文件d1.o和d2.o */
cc -G -o d1.so d1.o /*根据中间目标文件d1.o创建动态库文件d1.so*/
cc -G -o d2.so d2.o /*根据中间目标文件d2.o创建动态库文件d2.so*/
或者直接一步到位,直接创建动态库文件d1.so和d2.so:
cc -O -KPIC -G -o d1.so d1.c /*创建动态库文件d1.so*/
cc -O -KPIC -G -o d2.so d2.c /*创建动态库文件d2.so*/
3. HP-UNIX系列
创建 PIC 中间文件的编译器选项是+z,创建动态库的链接器标志是-b:
cc +z -c d1.c d2.c /*编译以“.o”为扩展名的中间目标文件d1.o和d2.o */
ld -b -o d1.so d1.o /*根据中间目标文件d1.o创建动态库文件d1.so*/
ld -b -o d2.so d2.o /*根据中间目标文件d2.o创建动态库文件d2.so*/
与大部分系统不同的是,HP-UNIX缺省使用“.sl”做动态库的扩展名。
4. AIX系列
在AIX中,cc和xlc系列都是C语言编译器。
xlc_r4 -c d1.c d2.c /*编译以“.o”为扩展名的中间目标文件d1.o和d2.o */
ld -G -bnoentry -bexpall -lc d1.o -o d1.so /*创建动态库文件d1.so */
ld -G -bnoentry -bexpall -lc d2.o -o d2.so /*创建动态库文件d2.so */
ld是创建动态库的工具,它的常用选项与cc类似,比如“-l”标识链接库,“-o”标识目标文件名称等。本处“-G”选项等价于指定带有-b标记的 erok、rtl、nortllib、nosymbolic、noautoexp和 M:SRE选项,它将创建动态库对象。更多的ld信息请参考相关文档。
5. Linux和其他使用gcc编译器的Unix
gcc -fpic -c d1.c d2.c /*编译以“.o”为扩展名的中间目标文件d1.o和d2.o */
gcc -shared -o d1.so d1.o /*根据中间目标文件d1.o创建动态库文件d1.so*/
gcc -shared -o d2.so d2.o /*根据中间目标文件d2.o创建动态库文件d2.so*/
或者直接一步到位:
gcc -O -fpic -shared -o d1.so d1.c /*创建动态库文件d1.so*/
gcc -O -fpic -shared -o d2.so d2.c /*创建动态库文件d2.so*/
某些版本的gcc上也可以使用“-G”替换“-shared”选项。
Makefile示例
Linux-so
#此编译文件用于在linux下编译静态以及动态库
SSMDIR=.. INCLUDEPATH = $(SSMDIR)/include LIBPATH = $(SSMDIR)/lib WORKDIR = . #-fPIC表示编译为位置独立的代码 CFLAG = -fPIC
all: $(LIBPATH)/libswssm.a $(LIBPATH)/libsmapi.a libsmapi.so
$(LIBPATH)/libswssm.a: swqdes.o swstr.o swssm.o swtea.o swxchg20.o ar -v -r $(LIBPATH)/libswssm.a swqdes.o swstr.o swssm.o swtea.o swxchg20.o
$(LIBPATH)/libsmapi.a: AscBcd.o hsmbj.o sec.o swqdes.o swstr.o swssm.o swtea.o swxchg20.o smapi.o ar -v -r $(LIBPATH)/libsmapi.a AscBcd.o hsmbj.o sec.o swqdes.o swstr.o swssm.o swtea.o swxchg20.o smapi.o
swstr.o: swstr.c $(INCLUDEPATH)/stepwork.h cc -I $(INCLUDEPATH) -c swstr.c $(CFLAG)
swqdes.o: swqdes.c $(INCLUDEPATH)/stepwork.h cc -I $(INCLUDEPATH) -c swqdes.c $(CFLAG)
swtea.o: swtea.c $(INCLUDEPATH)/stepwork.h cc -I $(INCLUDEPATH) -c swtea.c $(CFLAG)
swssm.o: swssm.c $(INCLUDEPATH)/stepwork.h $(INCLUDEPATH)/swssm.h cc -I $(INCLUDEPATH) -c swssm.c $(CFLAG)
swxchg20.o: swxchg20.c $(INCLUDEPATH)/stepwork.h cc -I $(INCLUDEPATH) -c swxchg20.c $(CFLAG)
smapi.o: smapi.c $(INCLUDEPATH)/stepwork.h $(INCLUDEPATH)/smapi.h cc -I $(INCLUDEPATH) -c smapi.c $(CFLAG)
AscBcd.o: smapi.c $(INCLUDEPATH)/hsmbj.h $(INCLUDEPATH)/smapi.h cc -I $(INCLUDEPATH) -c AscBcd.c $(CFLAG)
hsmbj.o: smapi.c $(INCLUDEPATH)/hsmbj.h $(INCLUDEPATH)/smapi.h cc -I $(INCLUDEPATH) -c hsmbj.c $(CFLAG)
sec.o: smapi.c $(INCLUDEPATH)/sec.h $(INCLUDEPATH)/sec_api.h $(INCLUDEPATH)/smapi.h cc -I $(INCLUDEPATH) -c sec.c $(CFLAG)
rm: rm *.o
SOFLAG = -shared
OBJDYN = AscBcd.o hsmbj.o sec.o smapi.o swqdes.o swssm.o swstr.o swtea.o swxchg20.o
libsmapi.so: $(OBJDYN) cc $(SOFLAG) -o $@ $(OBJDYN) mv $@ ../lib rm *.o |
Aix-so
# # makefile for application preconsole program # # .SUFFIXES:.c .SUFFIXES:.cpre
SYBASEDIR=/sybase CICSDIR=/usr/lpp/cics PDTDIR=$(HOME)/appl/pdt APPLDIR=$(HOME)/appl DLLEXPDIR=../exp DLLLIBDIR=$(HOME)/bin
BSCDIR=$(APPLDIR)/sc/bsc SCDIR=$(APPLDIR)/sc/ ABSDIR=$(APPLDIR)/cs/abs APPDIR=$(APPLDIR)/cs/app BILDIR=$(APPLDIR)/cs/bil BUGDIR=$(APPLDIR)/cs/bug CHKDIR=$(APPLDIR)/cs/chk COMDIR=$(APPLDIR)/cs/com CTMDIR=$(APPLDIR)/cs/ctm ERRDIR=$(APPLDIR)/cs/err EUMDIR=$(APPLDIR)/cs/eum FMTDIR=$(APPLDIR)/cs/fmt PDGDIR=$(APPLDIR)/cs/pdg RPTDIR=$(APPLDIR)/cs/rpt SECDIR=$(APPLDIR)/cs/sec SYSDIR=$(APPLDIR)/cs/sys TABDIR=$(APPLDIR)/cs/tab XMLDIR=$(APPLDIR)/cs/xml BTCDIR=$(APPLDIR)/cs/btc CFGDIR=$(APPLDIR)/cfglist CSLIBDIR=$(APPLDIR)/cslib
INCDIRS=-I$(PDTDIR)/include -I$(SYBASEDIR)/include -I$(CICSDIR)/include \ -I../include -I$(APPLDIR)/include -I$(BSCDIR)/include\ -I$(SCDIR)/include -I$(SCDIR)/include \
LIBDIRS=-L$(PDTDIR)/lib -L$(SYBASEDIR)/lib -L$(CICSDIR)/lib \ -L/usr/lib/dce -L../lib -L$(APPLDIR)/lib -L$(BSCDIR)/lib \ -L$(SCDIR)/lib -L$(CSLIBDIR)/lib
LIBS= -s -lcs_r.so -lct_r.so -ltcl_dce.so -lcomn_dce.so \ -lintl_r.so -ldce -lcicsecico -ldcelibc_r -ldcepthreads -lc_r \ -lpthreads -lm -lld -lcicsrt -lcur -lcurses
SOFLAG=-bnoentry -bE:$(DLLEXPDIR)/$@.exp -bM:SRE -lc CICSREDO=$(PDTDIR)/bin/cicsline CICSLINE=$(PDTDIR)/bin/cicsline CC=xlc_r4 CICSTRAN=$(CICSDIR)/bin/cicstran CPRE=$(SYBASEDIR)/bin/cpre -y -V CS_VERSION_110 $(INCDIR) CFLAGS=-bI:/usr/lpp/cics/lib/cicsprC.exp -qmaxmem=8192 \ -O -DUNIX -DLIB -D__PATH__='"$(PWD)/"' -DDEBUG EDF=
help: @echo Please input filename that will be made @echo 'syntax: make <help|all|"filename">'
.c: $(CC) -c $? $(CFLAG) $(INCDIR) @if [ -f $(DLLLIBDIR)/$@.so ]; then rm $(DLLLIBDIR)/$@.so; fi @ld -o $(DLLLIBDIR)/$@.so $@.o $(SOFLAG) $(LIBDIR) $(LIB) @if [ -f $*.o ]; then rm $*.o; fi if [ -f $*.cpre ]; then rm $*.c; fi if [ -f $*.cpre.cp ]; then rm $*.cpre.cp; fi
.cpre.c: $(CICSREDO) $< < $< > $<.cp $(CPRE) $<.cp mv $<.c $@ |
SO调用示例
2、AIX下动态链接库的使用
2.1 重要的dlfcn.h头文件
AIX下使用动态链接库,源程序需要包含dlfcn.h头文件,此文件定义了调用动态链接库的函数的原型。下面详细说明一下这些函数。
2.1.1 dlerror
原型为:const char *dlerror(void);
当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。
2.1.2 dlopen
原型为:void *dlopen (const char *filename, int flag);
dlopen用于打开指定名字(filename)的动态链接库,并返回操作句柄。
filename: 如果名字不以/开头,则非绝对路径名,将按下列先后顺序查找该文件。
(1) 用户环境变量中的LD_LIBRARY值;
(2) 动态链接缓冲文件/etc/ld.so.cache
(3) 目录/lib,/usr/lib
flag表示在什么时候解决未定义的符号(调用)。取值有两个:
1) RTLD_LAZY : 表明在动态链接库的函数代码执行时解决。
2) RTLD_NOW : 表明在dlopen返回前就解决所有未定义的符号,一旦未解决,dlopen将返回错误。
dlopen调用失败时,将返回NULL值,否则返回的是操作句柄。
2.1.3 dlsym : 取函数执行地址
原型为: void *dlsym(void *handle, char*symbol);
dlsym根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的函数的执行代码地址。由此地址,可以带参数执行相应的函数。
如程序代码: void (*add)(int x,int y); /* 说明一下要调用的动态函数add */
add=dlsym("xxx.so","add"); /* 打开xxx.so共享库,取add函数地址 */
add(89,369); /* 带两个参数89和369调用add函数 */
2.1.4 dlclose : 关闭动态链接库
原型为: int dlclose (void *handle);
dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。
2.2 在程序中使用动态链接库函数
2.2.1 程序范例
下面的程序装载了动态链接库share_str_tools.so,并用allTrim格式化字符串。
文件名: newtest.c
文件存放地址 src/test/ 下
----------------------------------------------------------------------
- #include <stdio.h>
- #include <dlfcn.h>
- #include "../lib/share_str_tools.h"
- /*typedef int (*dll_func)();*/
- int main() {
- void *dp = NULL;
- /*dll_func allTrim;*/
- int (*allTrim)();
- char aa[50];
- char *error;
- strcpy(aa, " this is a test ");
- if (!(dp = dlopen("../lib/share_str_tools.so", RTLD_LAZY))) {
- fputs(dlerror(), stderr);
- return 1;
- }
- /*allTrim = (dll_func) dlsym(dp, "allTrim");*/
- allTrim = (int (*)()) dlsym(dp, "allTrim");
- error=dlerror(); /* 检测错误 */
- if (error) { /* 若出错则退出 */
- fputs(error,stderr);
- return 1;
- }
- printf("1==%s==\n", aa);
- allTrim(aa);
- printf("2==%d, %s==\n", allTrim(aa), aa);
- dlclose(dp);
- return 0;
- }
----------------------------------------------------------------------
2.2.2
- cc –c newtest.c
- cc newtest.o –c newtest
----------------------------------------------------------------------
当删除my.so文件时,将出现以下信息:
动态链接库应用示范
my.so: cannot open shared object file: 文件或目录不存在
3、小结
AIX创建与使用动态链接库并不是一件难事。
编译函数源程序时选用-shared选项即可创建动态链接库,注意应以.so后缀命名,最好放到公用库目录(如/lib,/usr/lib等)下面,并要写好用户接口文件,以便其它用户共享。
使用动态链接库,源程序中要包含dlfcn.h头文件,写程序时注意dlopen等函数的正确调用。编译文件
----------------------------------------------------------------------