linux中怎么使用boost.python调用c++动态库
创始人
2024-12-21 17:01:28
0次

python调用c++动态库的两种办法

在网上搜了资料,咨询了同事,得到了两种方法:第一种方法是把C动态库打包成C接口,让python调用C语言接口。Python只能调用C接口,不能直接调用C接口,所以需要一层封装。封装方法:使用extern  "c  "声明模式,在c接口之上封装一层c语言接口。尝试这种方法后发现纯C调用可行,python调用不可行。原因将在下面详细解释。第二种方法是利用C的boost库生成一个接口,让python调用。测试是可行的,但过程是曲折的。下面将详细说明问题和解决方法。

理解extern  “C”的本质

在讲第一种方法之前,先简单介绍一下extern“c”方法的作用。具体解释可以参考这个博客,非常详细,推荐阅读。比如C语言中,有一个函数

intad(inta,intb);如果用gcc编译器,编译生成的名字叫add,但是如果用g编译器,编译生成的名字可能和ABaddCD差不多,包括函数名,参数个数,类型,返回值。所以,如果一个超载

float  DD(float,float  b);可能编译出来的名字有点像EFaddGH,里面也包含了函数名、输入参数、返回值等信息,所以c可以重载。试想一下,如果用gcc编译器,那就叫add,所以分不清哪个函数,所以不能重载。那么extern“c”的作用就是告诉g编译器把int  add(int  a,int  b)编译成add而不是ABaddCD,因为add可以被c语言识别,ABaddCD不能被c语言识别,c语言会认为add是‘未定义符号’。因此,我们也可以从这里看出,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函数,发现会报错这个错误:

属性错误: B.so:未定义符号:添加

也就是说,add函数仍然没有被识别。使用

可以获得NmB.so|grepadd

数据流向自动控制

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/li

bpthread.so.0",mode=ctypes.RTLD_GLOBAL)

有一些可以默认加载,比如 libpthread.so,我们不需要加载,其他的则需要手动加载,像 libssl.so,libuuid.so,都在 /usr/lib64/目录下,可以不加路径,但是libevent库也是/usr/lib64目录下,且在 /usr/lib/目录下也有,又必须加路径。所以,如果编译不通过,就使用 whereis libevent.so 查看在哪个目录,然后加上绝对路径。有时候加上绝对路径依然不对,比如libpthread.so,加上绝对路径之后还是报错

'OSError: /usr/lib64/libpthread.so: invalid ELF header'

这意味着版本号不对,找到 libpthread.so 链接的版本号,加上 .0 版本号,则不会报错。

c++代码配置boost环境

在c++动态库所在的centos6.6机器上面,我参考: ubuntu下python调用C/C++方法之动态链接库配置和试验boost。参考:利用Boost.Python实现Python C/C++混合编程实现python定义c++的函数重载。配置环境时,我使用的命令是:yum install boost*, yum install python-devel,参考这两篇文章实现boost,基本上都能通过,遇到的问题,里面也有。另外我也遇到其他问题,在Stack Overflow上面找到解决办法,我下面就直接贴一下结果:

新建一个 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: no matching function for call to ‘def(const char [15], )' def("LOGIN",LOGIN); 综上是我花了一整天时间研究的成果,如有错漏,还请读者指出,谢谢。

补充:当采用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)

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...