linux中使用boost.python调用c++动态库的方法
前言
最近开始使用robotframework测试c++的动态库,robotframework是跑在windows上面,c++动态库是跑在远程linux主机上面。测试办法是让robotframework通过SSHLIbrary库执行远程机器上面的python脚本,python脚本调用C++动态库。所以现在要解决的是如何让python调用c++动态库。
python调用c++动态库的两种办法
在上网查资料和咨询同事之后,得到两种办法:第一种将C++动态库封装成C接口,让python调用C语言接口。由于python只能调用C接口,无法直接调用C++接口,所以需要一层封装。封装办法:使用extern“C”声明方式,在C++的接口之上,封装一层C语言接口。这种办法经过尝试,发现纯C调用可行,但是python调用不可行,下面会具体讲解原因。第二种办法是使用c++的boost库,生成供python调用的接口,经测试可行,但是过程很曲折,下文会具体讲解遇到的问题和解决办法。
理解extern“C”的本质
在讲述第一种办法之前,先简单介绍一下extern“C”方式的作用。具体讲解可以参考这篇博客,讲的很详细,推荐阅读。举一个例子,在c语言中,有一个函数
intadd(inta,intb);
如果使用gcc编译器,那么编译生成的名字就叫add,但是如果使用g++编译器,可能编译生成的名字就叫类似于ABaddCD这样的,这里面包含了函数名、入参个数、类型、返回值。所以,如果c++中还定义了一个重载的
floatadd(floata,floatb);
可能编译生成的名字就叫类似于EFaddGH这样的,同样包含了函数名、入参、返回值等信息,所以c++可以重载。试想一下,如果使用gcc编译器,那么都叫add,就无法区分哪个函数了,所以无法重载。那么,extern“C”的作用,就是告诉g++编译器,将intadd(inta,intb)编译成add,而不是编译成ABaddCD,因为add才能被C语言识别,ABaddCD无法被C语言识别,C语言会认为add是'undefinedsymbol'。所以,从这里我们也可以看出,extern“C”只能用于c++代码,另外,对于存在重载的c++函数,需要写两个不一样的函数分别调用,保证名字不重复。
python使用extern“C”方式调用c++动态库
知道了extern“C”的本质之后,我们就按照这个方法进行封装。我是直接拿着c++动态库的源码,在源码之上封装一层C接口,然后生成动态库。假设将add函数封装成addc,C++动态库叫做A,封装一层C接口之后生成的动态库叫做B。如果写一个test.c的测试代码,使用纯C代码检验动态库B,调用addc函数,结果是可行的,成功的。但是使用python检验动态库B,调用addc函数,发现会报这样的错误:
AttributeError:B.so:undefinedsymbol:add
就是说依然不识别add函数。使用
nmB.so|grepadd
能够得到
addc
ABaddCD
这样的结果,第一个addc肯定是可以被python识别的,第二个ABaddCD,是g++编译生成的名字,无法被python调用。我这只是举一个自己的例子,我自己的C++动态库的源码可能写的比较复杂,无法被python成功调用,网上还有很多例子,说的是可以被成功调用。所以读者可以自行试验,如果能够成功调用,自然是最好的。因为接下来要介绍的使用boost.python的方式比较曲折。
python使用boost.python调用c++动态库
解决c++动态库依赖的其他的第三方库
由于我的动态库依赖了其他第三方的库文件,比如openssl,uuid,libevent,pthread,所以,不管是使用哪种办法调用c++动态库,都需要python加载这些动态库,具体python代码如下:
fromctypesimport* ctypes.CDLL("libssl.so",mode=ctypes.RTLD_GLOBAL) ctypes.CDLL("libcrypto.so",mode=ctypes.RTLD_GLOBAL) ctypes.CDLL("libuuid.so",mode=ctypes.RTLD_GLOBAL) ctypes.CDLL("/usr/lib64/libevent.so",mode=ctypes.RTLD_GLOBAL) #ctypes.CDLL("/usr/lib64/libpthread.so.0",mode=ctypes.RTLD_GLOBAL)
有一些可以默认加载,比如libpthread.so,我们不需要加载,其他的则需要手动加载,像libssl.so,libuuid.so,都在/usr/lib64/目录下,可以不加路径,但是libevent库也是/usr/lib64目录下,且在/usr/lib/目录下也有,又必须加路径。所以,如果编译不通过,就使用whereislibevent.so查看在哪个目录,然后加上绝对路径。有时候加上绝对路径依然不对,比如libpthread.so,加上绝对路径之后还是报错
'OSError:/usr/lib64/libpthread.so:invalidELFheader'
这意味着版本号不对,找到libpthread.so链接的版本号,加上.0版本号,则不会报错。
c++代码配置boost环境
在c++动态库所在的centos6.6机器上面,我参考:ubuntu下python调用C/C++方法之动态链接库配置和试验boost。参考:利用Boost.Python实现PythonC/C++混合编程实现python定义c++的函数重载。配置环境时,我使用的命令是:yuminstallboost*,yuminstallpython-devel,参考这两篇文章实现boost,基本上都能通过,遇到的问题,里面也有。另外我也遇到其他问题,在StackOverflow上面找到解决办法,我下面就直接贴一下结果:
新建一个test.cpp,在这个cpp里面我们要定义python可用的函数。
在test.cpp代码中,包含以下代码:
//需要包含boost的头文件 #include#include #include //重载函数的实现,在我的c++代码中,LOGIN函数、Synchronize_Request函数、Notify函数都有三个重载函数,下面我只用到了其中一个LOGIN函数,一个Synchronize_Request函数,2个Notify函数,比如下面的fun3和fun4,就是两个不同的notify。 //只有存在重载的函数才需要像这样定义fun1,fun2,fun3,fun4,不存在重载的函数,可以直接写名字 int(*fun1)(constintserver_type,constintrequest_no,std::string&login_result)=&LOGIN; int(*fun2)(constintserver_type,constintrequest_no,std::string&recv_answer)=&Synchronize_Request; int(*fun3)(constintserver_type,unsignedinttimeout_ms,unsignedintsesscare)=&Notify; int(*fun4)(void)=&Notify; //add函数重载举例 int(*fun5)(inta,intb)=&add; BOOST_PYTHON_MODULE(libB)//python模块,libB的名字要与.so的名字一致 { usingnamespaceboost::python; //Initialize函数没有重载,直接使用即可,不需要像上面一样定义出fun1 def("Initialize",Initialize); //Uninitialize函数没有重载,直接使用即可 def("Uninitialize",Uninitialize); def("LOGIN",fun1); def("Synchronize_Request",fun2); def("Notify",fun3); def("Notify2",fun4); def("add",fun5); //python可以调用以上def定义的函数 }
Makefile使用的命令是:
%.o:%.cpp g++-g-lssl-fPIC-levent-lcrypto-luuid-lpthread-lrt-lboost\_filesystem-lboost\_system-lboost_python-lpython-I/usr/include/python2.7-o$@-c$<
生成B.so的命令是:
g++-shared-Wl,-soname,libB.so-olibB.so*.o-lpython-lboost_python
python脚本中则需要引入该动态库
importlibB printlibB.add(10,20)
按照上面的命令进行编写、编译,就能规避我踩过的坑。注意-lpython的位置,不要放在前面。如果没有实现重载的定义,而是直接使用def("LOGIN",LOGIN);则会报如下的错误error:nomatchingfunctionforcallto‘def(constchar[15],
补充:当采用boost.python的方式调用c++动态库的时候,我无法处理引用类型,比如string&recv_answer用来接收返回结果,被识别为string{lvalue},而我的python传入的是string类型,无法匹配。所以我就手动将string&recv_answer的string类型的引用,改写成char*recv_answer_c格式,就是改成C语言的风格,然后用下面的方式传入recv_answer_c这个参数用来接收结果。
#采用bytes的方式,为变量预先分配空间,保证不会段错误 temp=bytearray(1000) recv_answer_c=bytes(temp)
总结:
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。