如题,最近遇到c++与python模块混合协作的任务。在python端调用c++模块编译好的.so库,c++中得到的string结果,需要返回给python端。咋一看是一个混合编程问题,或者说,需要进行c++与python通信。哇其实我的需求好像没那么高大上,就简单的捣鼓了下python中的ctypes库,大概可以满足需求。
数据交流嘛,有进就要有出啊。我的上一篇笔记记录了怎么把python中的数据送进c++,.npy或者直接从内存传输都可以,感兴趣的小伙伴可以看看~.~ ChenJ:huffman 霍夫曼无损编解码 c++ 压缩
那么对于c++中的结果怎么输出,下面简单记录下,两种方法:
1.在python端,传入一个,可修改指针指向内容的指针。
Py端:
import ctypes
from ctypes import *
ll = ctypes.cdll.LoadLibrary
lib = ll("good.so")
lib.fun.restype = c_char_p # restype定义返回类型, 可被修改.
lib.fun.argtypes = [c_char_p] # argtypes定义输入参数的类型,是个char型指针
# create_string_buffer() 注意此函数的使用..
str_buf = create_string_buffer("test".encode('utf-8')) # 没改变前的str_buf内容是:”test“
s = lib.fun(str_buf); # str_buf是传进来是一个char型指针. 可供c++中改变string内容. 调用c++中的fun()函数
print("here is python: ", s) # s为被修改后的string值
C++端:
extern "C"
const char *fun(char* tmp){
static string res = "something"; // s为在c++端生成的string结果.
// 将s与tmp"叠加",从而在py端通过tmp的指针,访问到改变内容后的string结果 res += tmp;
return res.c_str(); // .c_str()获取string的指针}
2.在python端送入一个string变量,让它在c++中直接被修改,就不用再另外做返回后的解析工作。这样做需要注意的地方是,python中的string,一个元素占4byte(python3)。所以要注意下差别处理。
Py端:
import ctypes
from ctypes import *
ll = ctypes.cdll.LoadLibrary
lib = ll("good.so")
str_res = "goodjob"
p=c_wchar_p(str_res) # c_wchar_p: ctypes中指向string的指针
lib.fun(p,len(str_res)) # p直接传入,其指向的string将在c++中被修改
print(p.value)
C++端:
void fun(char* p, int len){
/* your code */
string tmp_res = "something"; // tmp_res是即将要赋值给python端的,在c++中的计算结果 for(int i=0;i
p[i*4]=tmp_res[i]; // py中,string一个元素占4btye,所以p[4*i]
}
以上两种方法都亲测有效的,小伙伴们可以放心"服用"。
但...是,如果c++模块得到的string结果中,包含一些"特殊"char,在py端就会有奇怪的东西返回。比如第一种方法, .c_str()函数可能无法正确获取到修改后的 string 的指针。第二种方法的话,return的string可能被截断或者为空 (可能c++的结果string中包含一些提前结束的"信号"???)
我就遇到了上述问题,解决方式是:”曲线救国“吧,在含有"特殊"char的上一步,把中间结果传到py端先,再在py端做进一步处理。这个法子不一定适合所有场景的,或许你还可以试试把char转个ascll码再传出???
----------------------------------------- 20200915 更新 ---------------------------------
以上提到的两种方法我都试过了,繁琐且低效。这里给大家推荐一个神器:pybind11!
pybind11 直接支持: 在python端传入list或numpy数据,c++中计算得到的vector或string结果也可以便捷传出,忒棒了!
给个示例:
c++:
class ContainerTest{
public:
ContainerTest(){}
void Set(vector input_s ){ // input_s接收python传进来的数据,支持npy或list等 s = input_s; // 把python端传进来的vector赋值给mv }
/*************** your fun **************/
vector Fun(){
/** some code **/
return result; // vector类型的哈,在python端就直接变成list了}
private:
vector s; // 这里“声明”下s};
PYBIND11_MODULE(py2cpp, m){
m.doc() = "pybind11 example";
pybind11::class_(m, "CT" )
.def( pybind11::init() )
.def( "set", &ContainerTest::Set ) // ""内的是暴露给python的函数名 .def( "fun", &ContainerTest::Fun );
}
编译cpp :
g++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` your.cpp -o py2cpp`python3-config --extension-suffix` -I /usr/include/python3.6m
py:
import py2cpp # 编译上面的cpp文件,即可得到这个.so库
c=py2cpp.CT()
c.set(npy_data) # pybind11可支持npy
strat = time.time()
res = c.fun()
print(res)
希望遇到类似问题的小伙伴们也一样可以愉快解决! 看都看完了,点个赞再走呗~.~