【简介】
linux环境下的动态库一般名为libxxx.so, 用ldd命令分析某个可执行程序,可以看到该程序依赖哪些动态库,以及路径。 如 ldd ./test
linux-vdso.so.1 => (0x00007fffaab52000)
libc.so.6 => /lib64/libc.so.6 (0x0000003c4c800000)
/lib64/ld-linux-x86-64.so.2 (0x0000003c4c000000)
如果有依赖库找不到,程序会无法正常运行。
【创建一个动态库】
util.cpp
extern "C" int InsertSubStr(char* pszBuf, int nPos, char* pValue)
{
return 1;
}
代码为cpp时,函数可以重载,在symbol里生成的函数名会带上修饰,如_ZwqInsertSubStrBstl. 加上extern "C"之后在symbol里就是原名,当然也就不能有同名函数了。如果函数体使用了该修饰后,头文件定义也必须加上,否则调用者编译时会找不到函数名。
makefile
libmyutil.so : util.o
g++ -shared -o libmyutil.so util.o
util.o : util.cpp
g++ -fPIC -c util.cpp -o util.o
如果没有编译错误,则会生成libmyutil.so文件。因为使用了-shared编译选项,如果在代码里引用了其他库libabc.so,在编译时不会报错(不像windows下那样会报unresolve external,或者我还没找到设置方法)。如果调用者也没有引用该库libabc.so,则加载或运行时会报错。所以知道引用了哪些库,最好在makefile里加上,如g++ -shared -labc -o libmyutil.so util.o
QT创建动态库时,会生成libxx.so.1.0.0等几个带版本号的符号链接,可以加上 CONFIG += plugin 这样就不带版本号了。
【使用动态库】
像windows一样,也有直接编译链接和动态加载两种方式
1.直接编译链接
include头文件的函数定义后,在程序中调用函数,再在makefile中加上-lmyutil即可。用ldd命令分析执行程序,可以看到引用了libmyutil.so QT编译时,需要在pro文件里指定路径 LIB += -L../lib -lmymodule
2.动态加载
#include <dlfcn.h>
typedef int (*fnTestFoo)(char* pszBuf, int nPos, char* pValue);
void *hso = dlopen("./libmyutil.so", RTLD_NOW);//RTLD_LAZY);//
if(!hso)
{
printf("dlopen failed:%s\n", dlerror());
return;
}
fnTestFoo fnTest = (fnTestFoo)dlsym(hso, "InsertSubStr");
char *perr = dlerror();
if(perr)
{
printf("load symbol failed:%s\n", perr);
return;
}
char szText[256]="abcdef";
fnTestFoo(szText, 2, "xyz");
printf("%s\n", szText);
dlclose(hso);
可以看出与windows函数的对应:dlopen=LoadLibrary,dlsym=GetProcAddress. 这几个函数在libdl.so中,编译时在makefile中加上-ldl dlopen
可以有选项RTLD_NOW/RTLD_LAZY,前者为加载时解析依赖关系,失败则报错停止运行;后者为先不解析,运行到相应的代码时,如果有依赖关系错误再报错。
动态加载C++类
如果想要使用动态库中的C++类,直接使用肯定不行,因为编译时会找不到构造函数。其实只要在动态库中输出一个函数,创建类对象
CTest *GetClass_DL(void)
{
return (new CTest());
}
这样就可以了,析构函数也一样。
【依赖关系和路径】
在windows下可以用depends来查看库的依赖关系,在Linux下有好几个工具可以达到类似效果,如上面提到的文件依赖关系的ldd. 如果要查看函数依赖关系,可以用nm命令,如nm test
0000000000601170 d _DYNAMIC
w _Jv_RegisterClasses
00000000004008e4 T _Z8callFunciPPcPFiS_zE
U dlclose@@GLIBC_2.2.5
U dlerror@@GLIBC_2.2.5
U dlopen@@GLIBC_2.2.5
U dlsym@@GLIBC_2.2.5
0000000000400da2 T main
其中T(text)前面是在本程序中的地址;U(unresolved)表示引用的外部库函数,以及对应的库名称。另外还有w(weak),A(absolute)不了解。 另外还有objdump命令,更详细的显示程序内的内容。
动态加载dlopen时,最好是指定全路径,否则当前路径可能发生变化。当编译链接库文件时,有时运行程序会找不到库文件报错:error while loading shared libraries: libxxx.so.1: cannot open shared object file: No such file or directory.很多时候是未指定路径。
与windows不同,把dll放在exe一起就行了,加载时会先从当前路径查找。linux下的库文件的路径配置在/etc/ld.so.conf 中,内容如下一行
include ld.so.conf.d/*.conf
在conf.d目录,可以看到其他软件所需的库文件路径,如mysql-x86_64.conf 里面也只有一行:/usr/lib64/mysql
所以如果自己的库文件不放在系统的库路径如/usr/lib64下的话,也可以照此方法增加myapp.conf文件,写上/mylib, 放在这里即可。
sudo ldconfig更新配置(注意较慢),用ldconfig -p|grep libmyxxx可以查看自己的库是否在系统的路径里。当在路径里新增so文件时,配置不会自动更新。
如果不想更改系统的库文件路径,也可以在运行时修改环境变量LD_LIBRARY_PATH,如 $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/my_library_path $ ./my_app