linux下使用 boost.python 调用c++动态库

    最近开始使用 robot framework 测试c++的动态库,robot framework 是跑在 windows 上面,c++动态库是跑在远程linux主机上面。测试办法是让 robot framework 经过 SSHLIbrary 库执行远程机器上面的 python 脚本,python 脚本调用 C++ 动态库。因此如今要解决的是如何让python调用c++动态库。html

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

    在上网查资料和咨询同事以后,获得两种办法:第一种将C++动态库封装成C接口,让python调用C语言接口。因为python只能调用C接口,没法直接调用C++接口,因此须要一层封装。封装办法:使用extern “C”声明方式,在C++的接口之上,封装一层C语言接口。这种办法通过尝试,发现纯C调用可行,可是python调用不可行,下面会具体讲解缘由。第二种办法是使用c++的boost库,生成供python调用的接口,经测试可行,可是过程很曲折,下文会具体讲解遇到的问题和解决办法。python

理解extern “C”的本质

    在讲述第一种办法以前,先简单介绍一下extern “C”方式的做用。具体讲解能够参考这篇博客,讲的很详细,推荐阅读。举一个例子,在c语言中,有一个函数linux

int add(int a,int b);

若是使用gcc编译器,那么编译生成的名字就叫 add,可是若是使用 g++编译器,可能编译生成的名字就叫相似于ABaddCD这样的,这里面包含了函数名、入参个数、类型、返回值。因此,若是c++中还定义了一个重载的c++

float add(float a,float b);

可能编译生成的名字就叫相似于 EFaddGH这样的,一样包含了函数名、入参、返回值等信息,因此c++能够重载。试想一下,若是使用gcc编译器,那么都叫add,就没法区分哪一个函数了,因此没法重载。那么,extern “C”的做用,就是告诉g++编译器,将 int add(int a,int b)编译成 add,而不是编译成 ABaddCD,由于 add 才能被 C 语言识别,ABaddCD没法被C语言识别,C语言会认为 add是'undefined symbol'。因此,从这里咱们也能够看出,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函数,发现会报这样的错误:ubuntu

AttributeError: B.so: undefined symbol: add

就是说依然不识别add函数。使用windows

nm B.so | grep add

可以获得centos

addc 
ABaddCD

这样的结果,第一个 addc 确定是能够被 python 识别的,第二个 ABaddCD,是g++编译生成的名字,没法被python调用。我这只是举一个本身的例子,我本身的C++动态库的源码可能写的比较复杂,没法被python 成功调用,网上还有不少例子,说的是能够被成功调用。因此读者能够自行试验,若是可以成功调用,天然是最好的。由于接下来要介绍的使用 boost.python的方式比较曲折。python2.7

python使用 boost.python 调用c++动态库

   

解决c++动态库依赖的其余的第三方库

    因为个人动态库依赖了其余第三方的库文件,好比openssl,uuid,libevent,pthread,因此,不论是使用哪一种办法调用c++动态库,都须要python加载这些动态库,具体python代码以下:函数

from ctypes import *
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/目录下也有,又必须加路径。因此,若是编译不经过,就使用 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 <boost/python.hpp>  
#include <boost/python/module.hpp>  
#include <boost/python/def.hpp>

//重载函数的实现,在个人 c++代码中,LOGIN 函数、Synchronize_Request函数、Notify函数都有三个重载函数,下面我只用到了其中一个LOGIN函数,一个Synchronize_Request函数,2个Notify函数,好比下面的fun3和fun4,就是两个不一样的notify。

//只有存在重载的函数才须要像这样定义 fun1,fun2,fun3,fun4,不存在重载的函数,能够直接写名字

int (*fun1)(const int server_type, const int request_no, std::string& login_result) = &LOGIN;

int (*fun2)(const int server_type, const int request_no,std::string& recv_answer) = &Synchronize_Request;  
int (*fun3)(const int server_type, unsigned int timeout_ms, unsigned int sesscare ) = &Notify;

int (*fun4)(void) = &Notify;

// add 函数重载举例
int (*fun5)(int a,int b) = &add;

  
BOOST_PYTHON_MODULE( libB )  //python模块,libB的名字要与 .so 的名字一致  
{  
    using namespace boost::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 -o libB.so  *.o -lpython -lboost_python

python脚本中则须要引入该动态库

import libB

print libB.add(10,20)

按照上面的命令进行编写、编译,就能规避我踩过的坑。注意 -lpython 的位置,不要放在前面。 若是没有实现重载的定义,而是直接使用 def("LOGIN",LOGIN); 则会报以下的错误 error: no matching function for call to ‘def(const char [15], <unresolved overloaded function type>)’ 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)
相关文章
相关标签/搜索