实现python调用C++的动态链接库
本文把在用python调用C++的动态链接库 中,搜集了不少资料,有些博客讲的非常好,所以就把这些内容进行了一定的汇总和总结。
1.本文不讲如何编译动态链接库, 这个问题要自己学习一下如何编译,如 cmake
2.本文只讲案例,这些都是本人自己借鉴过的。
1.如题,首先为什么不调用静态链接库?
因为不支持。
2.其次,总的流程是什么?
很简单,
1.将想要暴露的接口编译成动态链接库,然后将包含静态链接库(.so)以及对应的接口函数的头文件(.h)放到python调用的文件夹里即可。
2.python调用的时候,本文使用的是ctypes 包,能够很容易的调用 so文件,同时要注意文件解析。
3.先上几个参考的例子:
(1)我这里参考了这个例子的 python 将数组传递给C++
INPUT = c_int * 4
# 实例化一个长度为2的整型数组
input = INPUT()
# 为数组赋值(input这个数组是不支持迭代的)
input[0] = 11
input[1] = 2
input[2] = 3
input[3] = 4
dll.teststring.restype = c_char_p
# bytes(aaaa, encoding="utf-8")
a = dll.teststring(input,4)
MYLIBDLL char* teststring(int* plus1, int len);
char* teststring(int* plus1,int len) {
for (int i = 0; i < len; i++) {
printf("%d \n", plus1[i]);
}
Mat mat;
//加载图片
mat = imread("bgs.jpg", CV_LOAD_IMAGE_COLOR);
printf("a %d %d", mat.rows, mat.cols);
//if (!mat.empty()) {
int m, n;
n = mat.cols * 3;
m = mat.rows;
unsigned char *data = (unsigned char*)malloc(sizeof(unsigned char) * m * n);
int p = 0;
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
data[p] = mat.at<unsigned char>(i, j);
p++;
}
}
*plus1 = p;
return (char*)data;
}
其次,参考了怎么让数组传回,我目前没有直接用数组返回成功过。
(2)看了很多案例,都是用结构体struct 进行封装然后转换
c++ 部分:
#include <iostream>
using namespace std;
typedef unsigned char BYTE;
#define MAX_COUNT 20
struct tagOutCardResult_py
{
BYTE cbCardCount;
BYTE cbResultCard1;
BYTE cbResultCard2;
BYTE cbResultCard3;
BYTE cbResultCard4;
BYTE cbResultCard5;
BYTE cbResultCard6;
BYTE cbResultCard7;
BYTE cbResultCard8;
BYTE cbResultCard9;
BYTE cbResultCard10;
BYTE cbResultCard11;
BYTE cbResultCard12;
BYTE cbResultCard13;
BYTE cbResultCard14;
BYTE cbResultCard15;
BYTE cbResultCard16;
BYTE cbResultCard17;
BYTE cbResultCard18;
BYTE cbResultCard19;
BYTE cbResultCard20;
};
struct tagOutCardResult
{
BYTE cbCardCount;
BYTE cbResultCard[MAX_COUNT];
void clear()
{
cbCardCount = 0;
for (int nIdx = 0;nIdx < MAX_COUNT;++nIdx)
{
cbResultCard[nIdx] = 0;
}
}
void topy(tagOutCardResult_py* ppy)
{
cout<<"topy function begin"<<endl;
ppy->cbCardCount = cbCardCount;
cout<<"topy function 1"<<endl;
ppy->cbResultCard1 = cbResultCard[1 - 1];
cout<<"topy function 2"<<endl;
ppy->cbResultCard2 = cbResultCard[2 - 1];
ppy->cbResultCard3 = cbResultCard[3 - 1];
ppy->cbResultCard4 = cbResultCard[4 - 1];
ppy->cbResultCard5 = cbResultCard[5 - 1];
ppy->cbResultCard6 = cbResultCard[6 - 1];
ppy->cbResultCard7 = cbResultCard[7 - 1];
ppy->cbResultCard8 = cbResultCard[8 - 1];
ppy->cbResultCard9 = cbResultCard[9 - 1];
ppy->cbResultCard10 = cbResultCard[10 - 1];
ppy->cbResultCard11 = cbResultCard[11 - 1];
ppy->cbResultCard12 = cbResultCard[12 - 1];
ppy->cbResultCard13 = cbResultCard[13 - 1];
ppy->cbResultCard14 = cbResultCard[14 - 1];
ppy->cbResultCard15 = cbResultCard[15 - 1];
ppy->cbResultCard16 = cbResultCard[16 - 1];
ppy->cbResultCard17 = cbResultCard[17 - 1];
ppy->cbResultCard18 = cbResultCard[18 - 1];
ppy->cbResultCard19 = cbResultCard[19 - 1];
ppy->cbResultCard20 = cbResultCard[20 - 1];
cout<<"topy function end"<<endl;
}
};
class TestLib
{
public:
void display(tagOutCardResult& ret);
};
void TestLib::display(tagOutCardResult& ret) {
ret.cbCardCount = 3;
ret.cbResultCard[0] = 1;
ret.cbResultCard[1] = 50;
ret.cbResultCard[2] = 100;
cout<<"First display aaa ";
cout<<"hello ";
cout<<"world ";
}
extern "C" {
TestLib oGameLogic;
void display(tagOutCardResult_py* ret_py) {
tagOutCardResult oRet;
oGameLogic.display(oRet);
cout<<"before topy"<<endl;
oRet.topy(ret_py);
cout<<"after topy"<<endl;
cout<<"in cpp:ret_py->cbCardCount:"<<ret_py->cbCardCount<<endl;
cout<<"in cpp:ret_py->cbResultCard1:"<<ret_py->cbResultCard1<<endl;
cout<<" this:" << ret_py << endl;
}
}
python 部分:
import ctypes
class tagOutCardResult_py(ctypes.Structure):
_fields_ = [("cbCardCount", ctypes.c_ubyte), \
("cbResultCard1", ctypes.c_ubyte), \
("cbResultCard2", ctypes.c_ubyte), \
("cbResultCard3", ctypes.c_ubyte), \
("cbResultCard4", ctypes.c_ubyte), \
("cbResultCard5", ctypes.c_ubyte), \
("cbResultCard6", ctypes.c_ubyte), \
("cbResultCard7", ctypes.c_ubyte), \
("cbResultCard8", ctypes.c_ubyte), \
("cbResultCard9", ctypes.c_ubyte), \
("cbResultCard10", ctypes.c_ubyte), \
("cbResultCard11", ctypes.c_ubyte), \
("cbResultCard12", ctypes.c_ubyte), \
("cbResultCard13", ctypes.c_ubyte), \
("cbResultCard14", ctypes.c_ubyte), \
("cbResultCard15", ctypes.c_ubyte), \
("cbResultCard16", ctypes.c_ubyte), \
("cbResultCard17", ctypes.c_ubyte), \
("cbResultCard18", ctypes.c_ubyte), \
("cbResultCard19", ctypes.c_ubyte), \
("cbResultCard20", ctypes.c_ubyte)]
import ctypes
so = ctypes.cdll.LoadLibrary
lib = so("./libpycallclass.so")
ERROR_MSG('display(\)')
ret = tagOutCardResult_py(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
ERROR_MSG("before lib.display(ctypes.byref(ret))")
lib.display(ctypes.byref(ret))
ERROR_MSG("after lib.display(ctypes.byref(ret))")
ERROR_MSG('#######################################################################################')
ERROR_MSG(ret)
ERROR_MSG(ret.cbCardCount)
ERROR_MSG(ret.cbResultCard1)
ERROR_MSG(ret.cbResultCard2)
ERROR_MSG(ret.cbResultCard3)
ERROR_MSG(type(ret))
实际上,有上面可以看到,可以利用结构体的引用,实现数据的传递,这样的做法好处在于可以避免内存泄漏。
(3)当然也有用指针进行传递的,但是也是基于结构体包装:
python 部分:
# 返回结构体
import ctypes
path = r'E:\01_Lab\VisualStudioLab\cpp_dll\cpp_dll\Debug\cpp_dll.dll'
dll = ctypes.WinDLL(path)
class StructPointer(ctypes.Structure):
_fields_ = [("name", ctypes.c_char * 20),
("age", ctypes.c_int),
("arr", ctypes.c_int * 3)]
dll.test.restype = ctypes.POINTER(StructPointer)
p = dll.test()
print(p.contents.name)
print(p.contents.age)
print(p.contents.arr[0])
print(p.contents.arr[1])
print(p.contents.arr[2])
c++部分代码: 可以看到这里是返回结构体的指针的,我看到有同学表示这里有内存泄漏,自己没试过,感兴趣的同学可以试试
typedef struct StructPointerTest
{
char name[20];
int age;
int arr[3];
}StructPointerTest, *StructPointer;
DLLEXPORT StructPointer __stdcall test() // 返回结构体指针
{
StructPointer p = (StructPointer)malloc(sizeof(StructPointerTest));
strcpy(p->name, "Joe");
p->age = 20;
p->arr[0] = 3;
p->arr[1] = 5;
p->arr[2] = 10;
return p;
}
(4)另外一个问题在于写C++代码的时候,由于C++有重载功能,所以编译器在编译的时候是会把函数改名的,这个时候就需要强调需要被python调用的函数是以c的方式编译。需要用如下方式:
extern "C"
{
float test()
{
printf("hello cpp");
return 1;
}
}
也就是说extern "C" 这句非常重要 因为在编译的时候被改名了,导致我们最终会找不到这个函数。
这边的例子是如何传opencv 图片 的例子:
c++ 代码, 就是把图片数组传入,然后重新赋值
float test(int height, int width, uchar* frame_data)
{
cv::Mat image(height, width, CV_8UC3);
uchar* pxvec =image.ptr<uchar>(0);
int count = 0;
for (int row = 0; row < height; row++)
{
pxvec = image.ptr<uchar>(row);
for(int col = 0; col < width; col++)
{
for(int c = 0; c < 3; c++)
{
pxvec[col*3+c] = frame_data[count];
count++;
}
}
}
float value = 0.2;
return value;
}
python 代码,对应的 你需要了解 ctypes 的c和python的转换格式,网上很多
import ctypes
import cv2
#load
ll = ctypes.cdll.LoadLibrary
# 动态链接库
lib = ll("./lib/libtest.so")
#设置这个函数的返回类型
lib.test.restype = ctypes.c_float
#读图片
frame = cv2.imread('test.jpg')
#将图片转化成array 并让c++ 接受的格式
frame_data = np.asarray(frame, dtype=np.uint8)
frame_data = frame_data.ctypes.data_as(ctypes.c_char_p)
#返回结果
value = lib.test(frame.shape[0], frame.shape[1], frame_data)
print value
当然注意,以上这段可以借鉴的是传图片数组。
欢迎批评指正,谢谢!
感谢一下博文,他们都写的非常好。
参考:https://blog.csdn.net/u011021773/article/details/83188012
https://www.jb51.net/article/156159.htm