如何解决何避免多个CC动态库函数同名

网站运营求职招聘微信群 http://www.prcgoogle.com/zonghe/guonei/2021/0615/5414.html
前言

现在的开发节奏越来越快,有大量现成的库来方便我们的开发者来使用,避免重复造轮子,而且有很多有生命力的开源社区。当然在使用过程中,你可能为这样的场景而头痛:

你的项目中用到了A库和B库,其中A库使用的v1版本的C库,B库使用的是V2版本的C库,v1和v2版本的C库还相差的比较大,因此你在编译此项目的时候链接了A和B库后,发现有些调用执行不了,或者崩溃,或是个异常的结果,总之我这里归结为多个C,C++动态库函数同名冲突。

案例分析

假设有动态库libFuncA.so和libFuncB.so,他们的内部实现分别为:

/*func_A.c*/#includestdio.h//内部函数intsayHi(){printf("Hi,thisisAAAAA\n");return0;}//外部调用函数intsayOut(){sayHi();printf("UsethistointroduceAAAAA\n");return0;}

/*func_B.c*/#includestdio.h//内部函数intsayHi(){printf("Hi,thisisBBBBB\n");return0;}//外部调用函数intsayOut(){sayHi();printf("UsethistointroduceBBBBB\n");return0;}

分别编译成动态库:gcc-fPIC-shared-olibFuncA.sofunc_A.cgcc-fPIC-shared-olibFuncB.sofunc_B.c

调用函数test.c:

#includestdio.hexternintsayOut();intmain(){sayOut();return0;}

则调用的时候,使用函数sayOut,实现的功能与编译时链接库顺序有关。

dlopen显示调用

可使用dlopen函数族,显式指定要调用的动态库。详细用法如:dlopen该函数族需设定打开模式,返回一个动态库的句柄,调用句柄和函数进行操作,完成后需要关闭。使用时,需引入头文件dlfcn.h,定义函数指针,编译时增加-rdynamic参数和链接-ldl

更改调用函数test.c

#includestdio.h#includedlfcn.htypedefint(*func_pt)();intmain(){void*handle=NULL;func_ptfunc=NULL;if((handle=dlopen("./libFuncA.so",RTLD_LAZY))==NULL){printf("dlopen%s\n",dlerror());return-1;}//定义函数指针,在动态库中查找符号func=dlsym(handle,"sayOut");func();dlclose(handle);printf("+++++++++++++++++++++++++++++++++++++\n");if((handle=dlopen("./libFuncB.so",RTLD_LAZY))==NULL){printf("dlopen%s\n",dlerror());return-1;}func=dlsym(handle,"sayOut");func();dlclose(handle);}

编译执行:gcc-g-oexectt1.cpp-ldl

可以发现,我们能显式执行指定动态库的外部函数sayOut了。

这个简单的例子可以说明我们通过dlopen显示加载动态库符号表来调用动态库的内容,从而避免了多个动态库的符号表冲突。

为何内部函数sayHi都调用了链接顺序第一个的实现?原因在于动态库中的内部函数没有设置限制,使得sayHi函数也暴露给外部,调用时自然选择第一个函数实现。用nm指令可以看出,两个函数都暴露出来了

再次搜索,可以用gcc编译器的特性来设置动态库函数的导出控制。

可在函数前增加__attribute__前缀来控制更改动态库函数如下:

/*func_A.c*/#includestdio.h#defineDLL_PUBLIC__attribute__((visibility("default")))#defineDLL_LOCAL__attribute__((visibility("hidden")))DLL_LOCALintsayHi(){printf("Hi,thisisAAAAA\n");return0;}DLL_PUBLICintsayOut(){sayHi();printf("UsethistointroduceAAAAA\n");return0;}

/*func_B.c*/#includestdio.h#defineDLL_PUBLIC__attribute__((visibility("default")))#defineDLL_LOCAL__attribute__((visibility("hidden")))DLL_LOCALintsayHi(){printf("Hi,thisisBBBBB\n");return0;}DLL_PUBLICintsayOut(){sayHi();printf("UsethistointroduceBBBBB\n");return0;}

重新编译动态库,用nm指令可以查看sayHi不再导出了

重新编译测试程序,也没问题了

如果两个动态库中相似函数很多,一个个加__attribute__前缀也是很大工作量。此时可以编译时设置默认函数不导出,只在需要导出的函数前面加前缀。以libFuncA.c为例:

/*funcA.c*/#includestdio.h#defineDLL_PUBLIC__attribute__((visibility("default")))intsayHi(){printf("Hi,thisisBBBBB\n");return0;}DLL_PUBLICintsayOut(){sayHi();printf("UsethistointroduceBBBBB\n");return0;}

编译时,增加-fvisibility=hidden参数,则未增加前缀的函数都不会导出

-fvisibility=hidden,默认改为隐藏属性。它与static的区别在于,它的边界范围是动态库,而static是文件,但两者都能做如上所述的优化(消除got,got.plt)。需注意,-fvisibility=hidden须在编译源码时传入,否则不会起作用。

so之间符号覆盖的解决方案

简单的说就是不允许so之间出现符号覆盖,如果有符号覆盖基本可以肯定是出问题了。

那么万一用到的两个不同功能的so,比如是两个不同的开源项目的代码,由于是各自开发,出现了函数或变量名字相同的情况,应该怎么办呢?

答案简单粗暴,也最可靠,那就是改名。

话说回来,没考虑到符号冲突的so,质量要打个问号,能不用还是不要用。。。

如果是我们自己开发的so库,要注意

(1)函数/变量/类加名字空间,如果是c函数就需要加前缀

(2)不导出不需要的函数/变量/类

相同so版本兼容问题

新旧版本的兼容问题

动态库可能有新旧多个版本,并且新旧版本也可能不兼容。

可能有多个app依赖于这些不同版本的so库。

因此当一个so库被覆盖的时候,就可能出问题。

(1)旧so覆盖新so,可能导致找不到新函数,

(2)新so覆盖旧so,可能导致找不到旧的函数,

(3)而更加隐蔽的问题是:新旧so里的同一个函数,语义已经不一样,即前置条件和效果不一样。

新旧版本的兼容关系

(1)新版本完全兼容旧版本,只是新增了函数。

这种情况只需要新版本即可。

(2)新版本删除了一些旧版函数,并且保持签名相同的语义相同(可能新增了函数)。

这种情况需要新旧版本同时存在。

(3)新旧两个版本有一些相同签名但是语义不一样的函数。

这种情况是不予许的。

因为可能出现一个app必须同时依赖新旧两个版本,由于同一签名函数只能有一个实现,也就说另一个实现会被覆盖,就会出错。

新旧版本兼容的解决方法

由此我们知道,有两个解决方案:

(1)新版本完全兼容旧版本,并保证新版本覆盖旧版本或者新旧版本共存。

这种方法太理想化。

实际情况下,新版本完全兼容旧版本比较难以做到,这要求函数一旦发布就不能改不能删,并且永远必须兼容。

(2)新版本可以删除一些旧版函数,需保持签名相同的函数语义相同,并保证新旧版本共存。

这是可行的解决方法。

Linux的版本兼容解决方法

首先加版本号保证新旧版本可以共存,不会互相覆盖。版本号形如openssl.so.1.0.0。

其次新版本需保持和旧版本签名相同的函数语义相同。

这样已经可以解决问题了,但是还可以优化。

因为版本号分的太细,导致有很多的版本同时存在,其实不需要这么多版本。

仔细考虑一下:

(1)如果新版本和旧版本的函数完全相同,只是fixbug:那么新版本需要替换掉旧版本,旧版本不需要保留。

(2)如果新版本新增了函数:那么新版本可以替换掉旧版本,旧版本不需要保留。

(3)如果新版本删除了函数:那么旧版本就需要保留。

如果linux系统下有新旧两个so,它怎么知道可不可以需不需要替换掉旧版本?

答案是通过版本号:

linux规定对于大版本号相同的一系列so,可以选出里面最新的so,用它替换掉其它的so。

这里所谓的替换,其实是建立了一个软链接,型如openssl.so.1,把它指向openssl.so.1.x.x.x系列so里面最新的那一个so。

预览时标签不可点收录于合集#个上一篇下一篇

转载请注明:http://www.aierlanlan.com/grrz/397.html

  • 上一篇文章:
  •   
  • 下一篇文章: