最近在使用linux下的共享库so的时候遇到一个奇怪的问题,做个记录,方便备查。
一般来说,如果用gcc编译的时候加上-shared和-fPIC选项,可以把源文件编译成一个so文件,可以在其他源程序连接阶段把这个链接上去,从而可以调用so文件提供的函数接口,这样可以多文件共用一个so文件提供的函数,即节省内存空间,也便于更新,所有的接口只需要更新so文件就行。
其实除了上面的方法,还有一个方法,那就是在运行时由程序自己动态加载so文件,使用一系列系统调用如dlopen,dlsym,dlclose等来进行动态加载,获取函数地址从而进行函数调用,关闭加载的so文件等。
一般以第一种方法用得多,但是第二种方法更灵活,结合配置文件,更具一般意义上的服务扩展性。 但是就是在用第二种方法的时候出现了一点问题。
这里把问题抽象一下,假设有一个源文件是要编译为so文件的,假设这个源文件只提供一个简单的函数,add,取两个整数为参数,返回它们的和,源文件为add.c,代码如下:
#include
int add(int a, int b)
{
return a+b;
}
编译
gcc -shared -fPIC add.c -o libadd.so
然后写个脚手架程序来测试,test.c,代码如下:
#include
#include
#include
int main(int argc, char *argv[])
{
void * handle;
int (*func)(int, int);
char *error;
handle = dlopen("libadd.so", RTLD_LAZY);
if(!handle)
{
fprintf(stderr, "%s\n", dlerror());
exit(1);
}
func = (int (*)(int,int))dlsym(handle, "add");
if((error = dlerror()) != NULL)
{
fprintf(stderr, "%s\n", error);
exit(1);
}
func(3, 4);
dlclose(handle);
return 0;
}
编译
gcc test.c -o test -ldl
然后修改LD_LIBRARY_PATH变量,加上库所在的目录,这里是当前目录
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
好的,这样是没有问题的,这样的方法是标准的动态加载so库的方法,但是问题在什么地方呢,如果把gcc换成g++,问题就出现了 整个编译完成后,执行test,它会告诉你
./libadd.so: undefined symbol: add
找不到符号add 然后用nm看一下libadd.so的符号名
$ nm libadd.so |grep add
00000000000005ec T _Z3addii
它娘的,这是c++编译器的符号命名法,用c++filt看一下
$ c++filt _Z3addii
add(int, int)
看吗,这才是我们很显而易见的函数名 所以,解决方案出来,把库源文件的函数当C符号来处理,这样
extern "C" {
// the function code
...
}
然后编译运行,这就是好的,编译出来后用nm看一下
$ nm libadd.so |grep add
00000000000005dc T add
哈哈,这就好了,然后在dlsym中写符号名的时候就直接写add就行了。
这里例子虽然是用g++编译纯C文件,其实是真正的cpp文件也是可以的。