.o 、 .a 、 .so 文件都是 Linux 下的程序函数库,即编译好的可以供其他程序使用的代 码和数据。
优 点:程序模块化,容易重新编译,方便升级。
分类:静态函数库(对应 .a 文件)、共享函数库(对应 .so 文件,类似于 Windows 的 dll 文件)、动态加载函数库(对应 .o 文件, 相 当于 Windows 里的 .obj 文件 )
静态函数库
特点: 实 际上是简单的普通目标文件的集合,在程序执行前就加入到目标程序中。
优点: 可 以用以前某些程序兼容;描述简单;允许程序员把程序 link 起 来而不用重新编译代码,节省了重新编译代码的时间(该优势目前已不明显);开发者可以对源代码保密;理论上使用 ELF 格式的静态库函数生成的代码可以比使用共享或动态函数库 的程序运行速度快(大概 1%-5% )
生成 : 使用 ar 程序( archiver 的缩写)。 ar rcs my_lib.a f1.o f2.o 是把目 标代码 f1.o 和 f2.o 加入到 my_lib.a 这个函数库文件中(如果 my_lib.a 不存在则创建)
使用: 用 gcc 生成可执行代码时,使用 -l 参数指定要加入的库函数。也可以用 ld 命令的 -l 和 -L 参数。
共享函数库
共享函数库在可执行程序启动的时候加载,所有程序重新运行时都可自动加载共享函数库中的函数。 .so 文件感觉很复杂,光是命名规则就已经看得我很晕了 ~ 整理一下,共享库需要: soname 、 real name ,另外编译的时候名字也有说法。依次解释 下:
soname : 必须的格式: lib+ 函数库名 +.so+ 版本号信息(但是记住,非常底层的 C 库函数都不是以 lib 开头命名的)。例子: /usr/lib/libreadline.so.3
real name : 顾名思义是真正的名字啦,有主版本号和发行版本号。但是没找到实例……
编 译器编译的时候需要的函数库的名字就是不包含版本号信息的 soname , 例如上面的例子把最后的 .3 去 掉就可以了。
位置: 共 享函数库文件必须放在特定目录,对于开放源码来说, GNU 标 准建议所有的函数库文件都放在 /usr/local/lib 目录下,而且建议命令、可执行程序都放在 /usr/local/bin 目 录下。不过这个只是习惯啦,可以改变,具体的位置信息可以看 /etc/ld.so.conf 里面的配置信息。当然,也可以修改这个文件,加入自己的一些特殊的路径要求。
创建: 在 网上找到了 gcc 方式 和 easyeclipse 环 境下两种创建方式。
gcc 方式:
首先创建 object 文 件,这个文件将加入通过 gcc –fPIC 参数命令加入到共享函数库里面,标准格式: gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list (说 实话这个标准格式看起来好复杂,我找了个实例,但是好像和那个标准格式稍有不同: gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so )
在 easyeclipse 环境下生成 .so 文件:
1. 选择新建工程,建立一个 c++ 工程
2. 在工程类型选项里选择 Shared Library ,然后填入工程名字 PXXX 点击完成即可。
3. 编写程序,然后编译就会在 debug 或者 release 里生成一个 libPXXX.so 文件,如果不要 lib 的起头标记点击 project 菜单的 Properties 选项,然后在弹出的界面的右边点击 Build artifact 页面,将 Output prefix 选项的内容清空即可。
4. 如果是 C++ 程序,注意在接口函数的前面加上 extern "C" 标记,在头文件加上如下标记:
#ifdef __cplusplus
#extern "C"{
#endif
头 文件主体
#ifdef __cplusplus
}
#endif
如果不加以上标记,经过编译后, so 里 的函数名并非你编写程序时设定的函数名,在开发环境左侧的工程文件列表中点开 debug 项里的 PXXX.o 可以看到 so 文件里的函数名都是在你设定的函数名后面加了一个 __Fi 标记,比如你用的设定的函数名称是 Func(), 而 so 里的函数名则为 Func__Fi() 或者其他的名称。
安装: 拷贝共享库文件到指定的标准的目录,然后运行 ldconfig 。如果没有权限这样做,那么就只好通过修改环境变量来实现这些函数库的使用了。方法不再说了,很复杂。
查看: 可以通过运行 ldd 来看某个程序使用的 共享函数库。例如 ldd /bin/ls 。查看 .so 文件使用 nm 命令,如 nm libXXX.so 。(注意, nm 对于静态的函数库和共享的 函数库都起作用)
关于覆盖: 如果想用自己的函数覆盖某个库中的一些函数,同时保留该库中其他的函数的话,可以在 /etc/ld.so.preload 中加入要替换的库( .o 结尾的文件),这些 preloading 的库函数将有优先加载的权利。
关于更新: 每次新增加动态加载的函数库、删除某个函数库或者修改某个函数库的路径时, 都要重新运行 ldconfig 来更新 /etc/ld.so.cache
动 态加载函数库
特点: 可以在程序运行过程中的任何时间加载。特别适合在函数中加载一些模块和 plugin 扩 展模块的场合。动态加载函数库通过 API 来打开函数库,寻找符号表,处理错误和关闭函数库。
使 用:
打开 —— Linux 使 用 dlopen 函数打开函数库,原型: void * dlopen(const char *filename, int flag); dlopen ()函数的返回值是一个句柄,然后后面的函数就通过使用这个句柄来做进一步的操作。如果打开失败 dlopen() 就返回一个 NULL 。如果一个函数库被多次打开,它会返回同样的句柄。
使用 —— dlsym() 函 数在一个已经打开的函数库里面查找给定的符号。定义: void * dlsym(void *handle, char *symbol); 参数 handle 就是由 dlopen 打开后返回的句柄, symbol 是一个以 NIL 结 尾的字符串。没有找到需要查找的 symbol ,则返回 NULL (注意,有些 symbol 可能值就是 NULL, 这时可配合 dlerror() 函数检查是否出错。
关闭 —— dlclose () 函数用于关闭一个动态加载函数库。函数库维持一个资源利用的计数器,当调用 dlclose 的时候,就把这个计 数器的计数减一,如果计数器为 0 ,则真正的释放掉。真正释放的时候,如果函数库里面有 _fini() 这 个函 数,则自动调用 _fini ()这个函数,做一些必要的处理。 dlclose ()返回 0 表 示成功,其他非 0 值表示错误。
查看出错信息 ——dlerror() 函数 获得最后一次调用 dlopen() , dlsym() ,或者 dlclose ()的错误信息,返回 NULL 时表示没有错误。使用 dlerror(); 可以清除以前的错误。