当前位置: 首页 > 科技观察

应用程序编程:如何在动态库中调用外部函数?

时间:2023-03-17 21:47:49 科技观察

大家好,我是动态链接库!相信你一定很早以前就听说过这个名字。在计算机早期,由于内存资源不足,我起了主要作用!无论是在Windows系统还是在Unix系列平台上,随处可见我的身影,因为我可以为大家节省很多资源,资源为人民币!玩的开心例如:我师父写了这么一段简单的代码:#file:lib.c#includeintfunc_in_lib(intk){printf("func_in_libiscalled\n");returnk+1;}只要我用下面命令编译,我会创建lib.so,它是一个动态链接库:$gcc-m32-fPIC--shared-olib.solib.c这时候师傅随便丢给我谁,我可以服侍他,只要他在我肚子里调用这个函数func_in_lib。虽然你可以看到我提供的功能很简单,但是原理是一样的。如果以后有机会,我会用这个函数来计算机器人的轨迹。我来给你展示!比如:张三今天写了一段代码需要调用我的函数。张三是一个喜欢表演的人。很明显,他在编译可执行程序的时候,可以动态链接我,像这样:$gcc-m32-omainmain.c./lib.so但是张三偏偏不这样做,为了炫耀,他选择了使用dlopen动态加载的方法从硬盘加载到我的进程中。我们来看看张三写的可执行程序代码:*pfunc)(int);intmain(intargc,char*agv[]){inta=1;intb;//打开动态库void*handle=dlopen("./lib.so",RTLD_NOW);if(handle){//在动态库中查找函数pfuncfunc=(pfunc)dlsym(handle,"func_in_lib");if(func){b=func(a);printf("b=%d\n",b);}else{printf("dlsymfailed!\n");}dlclose(handle);}else{printf("dlopenfailed!\n");}return0;}从代码可以看出,张三提前知道了什么在我肚子里这个函数的名字叫func_in_lib,所以他用的是系统函数dlsym(handle,"func_in_lib");在内存中找到这个函数的加载地址,然后直接调用这个函数。张三编译完可执行文件main后,执行结果完全正确,非常开心!但是有一天,我遇到了一件烦人的事,师父说:你的服务函数的计算过程太单调了,给你找点乐子,你在执行的时候,调用另一个外部模块中的函数。话音刚落,就扔了一个函数名:voidfunc_in_main(void);。换句话说,我需要在我的服务函数中调用其他模块中的函数,像这样:#include//Externalfunctiondeclarationvoidfunc_in_main(void);intfunc_in_lib(intk){printf("func_in_libiscalled\n");//调用外部函数func_in_main();return+1;}那么这个函数在哪里呢?天哪,我怎么知道这个函数是什么?如何找到它在内存中隐藏的角落(地址)?反正楼主修改代码后,顺利的编译了我:$gcc-m32-fPIC--shared-olib.solib.c编译指令一点都没变。因为我只是一个动态链接库,即使此时不知道func_in_main函数的地址,也能编译成功。只是我要给这家伙做标记:如果有人要用我,必须告诉我这家伙的地址在哪里!不然别怪我作弊。无辜的张三,师父对张三说:兄弟,我的动态链接库升级了,功能更强大了。你想试试吗?张三想:我用dlopen动态加载动态库文件。无需重新编译或链接可执行程序,直接运行即可!于是他二话不说就把我接过来,扔到他的可执行程序目录下,然后执行主程序。但这一次,他看到的结果是:dlopenfailed!为什么加载失败?上次正常执行!张三惊呆了!其实这也不能怪我!如果你要用我,你必须告诉我函数func_in_main的地址在哪里!但是在张三的进程中,到处都找不到这个函数的地址。既然你满足不了我,那我也满足不了你!技巧一:导出符号表张三现在不行了,求师傅算账:我的应用代码根本没变,为什么要改?你给的新动态链接库不行?大师慢条斯理地回答:疏忽,疏忽,忘了告诉你一件事:这个动态库,它还需要你做一件事:在你的程序中提供一个函数,叫做func_in_main,这样就可以了。张三义心想:这个好办,加个功能就行了。因为这个可执行程序只有一个main.c文件,所以他在里面加了一个新的函数:voidfunc_in_main(void){printf("func_in_main\n");}然后开始编译执行,运行很猛一只老虎:#gcc-m32-omainmain.c-ldl#./maindlopenfailed!嗯?为什么还是失败了?!你有没有按要求添加func_in_main函数?!笨张三,没错,你确实在main.c中添加了这个函数,但是你只是把它添加到你的可执行程序中,我却根本看不到这个函数!不信你看看编译后的可执行程序中是否包含了符号func_in_mainExported?如果没有导出,我怎么能看到呢?#查看导出的符号表$objdump-emain-T|grepfunc_in_main#这里输出为空因为输出为空,说明还没有导出!我不需要教这个你说得对吗?茴香豆的“茴”字有四种写法。..哦不对,导出符号有两种方法:方法一:导出所有符号$gcc-m32-rdynamic-omainmain.c-ldl当然也可以使用如下命令:gcc-m32-Wl,--export-dynamic-omainmain.c-ldl方法二:导出指定符号首先定义一个文件,列出所有需要导出的符号:File:exported.txt{extern"C"{func_in_main;};};然后,在编译选项中Specifythisexportfilein:gcc-m32-Wl,-dynamic-list=./exported.txt-omainmain.c-ldl使用以上两种方法之一。编译好后用objdump命令看exportSymbol:$objdump-emain-T|grepfunc_in_main080485bbgDF.text00000019Basefunc_in_main嗯,很好!张三很快就这样操作了起来,功能执行成功了!$./mainfunc_in_libisclalledfunc_in_mainb=2也就是说在我的动态库文件中,正确找到了其他外部模块中的函数地址,执行成功!提示二:虽然动态注册成功了,但是张三心里还是隐隐约约的有些不适。每次编译都要导出符号,真的很麻烦。你能优化它吗?于是他找到了我师傅,表达了他的不满。主人一看,很有个性!既然你不想提供,那我就满足你:首先,在动态库中提供一个默认的函数实现(func_in_main_def);然后,提供一个专门的注册函数(register_func),如果外部模块要提供func_in_main函数,调用注册函数进行注册;这时lib.c的最新代码变成了这样:#include//Tryvoidfunc_in_main_def(void){printf("themainislazybydefault),doNOTregisterme!\n");}//定义外部函数指针void(*func_in_main)()=func_in_main_def;voidregister_func(void(*pf)()){func_in_main=pf;}intfunc_in_lib(intk){printf("func_in_libiscalled\n");if(func_in_main)func_in_main();returnk+1;}然后编译,一个全新的lib.so又诞生了:gcc-m32-fPIC--shared-olib.solib.c你不需要提供func_in_main函数,当然你也不需要再导出符号了。但是,如果有一天你改变了主意,想要再次提供这个功能,那么你就不得不通过动态库中的register_func函数来注册你的功能。你明白了吗?快点再试一次!这时候张三再用我的时候,就不需要在他的main.c中导出函数func_in_main了。其实他可以把这个函数从代码里删掉!编译执行,张三又一次猛虎操作:$gcc-m32-omainmain.c-ldl$./mainfunc_in_libiscalledthemainislazy,doNOTregisterme!b=2嗯,结果看起来正确。嗯?为什么多了一行字:楼主懒,别注册!是在质疑我的技术能力吗?嗯,这样的话,我也满足你了,不就是注册一个函数吗,简单://File:main.c#include#include#include#includetypedefint(*pfunc)(int);typedefint(*preregister)(void(*)());//控制注册函数的宏定义#defineREG_FUNC#ifdefREG_FUNCvoidfunc_in_main(void){printf("func_in_main\n");}#endifintmain(intargc,char*agv[]){inta=1;intb;//打开动态库void*handle=dlopen("./lib.so",RTLD_NOW);if(handle){#ifdefREG_FUNC//在动态库中找到注册函数registerregister_func=(preregister)dlsym(handle,"register_func");if(register_func){register_func(func_in_main);}#endif//在动态库中查找函数pfuncfunc=(pfunc)dlsym(handle,"func_in_lib");if(func){b=func(a);printf("b=%d\n",b);}else{printf("dlsymfailed!\n");}dlclose(handle);}else{printf("dlopenfailed!\n");}return0;}然后编译执行:$gcc-m32-omainmain.c-ldl$./mainfunc_in_libiscallefunc_in_mainb=2完美收官!PS:很多平台级的代码,比如工控领域的一些运行时(Runtime)软件,大部分都是积分都是通过注册与平台代码和用户代码连接绑定的。