目录
前言
C/C++动态共享库编译
ctype模块
ctype数据类型
使用案例
float数据
指针
输出数组
结构体及结构体指针
numpy图像当作指针传入
参考资料
前言
在项目开发中,有时会使用到多种编程语言,比如部分功能是C/C++代码实现的,而另一部分是Python代码实现的,这样就可能需要使用多种编程语言。当然,也可以把C/C++代码转成Python,但这样可能费时;也有可能某个模块用Python来实现,速度很慢,但用C/C++速度却很快,这就会使用到用Python来调用C/C++代码。
本文主要说明如何使用Python来调用C/C++代码,且是基于Linux平台,Linux上编译生成动态共享库so文件,然后Python使用ctype模块来调用so文件里的函数。如果不同平台,比如Windows或者mac,编译的动态共享库文件是不一样的。
C/C++动态共享库编译
首先要把C/C++代码编译成动态共享库,一般是通过extern C来实现对外接口。下面给出一个例子。test.h文件进行函数声明,test.cpp是函数实现。
/******c动态库接口文件test.h*************/
#ifndef TEST_H
#define TEST_H
#ifdef __cplusplus
extern "C" {
#endif
int hello();
#ifdef __cplusplus
}
#endif
#endif /* TEST_H */
/*****c动态库函数实现test.cpp*****************/
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include "test.h"
int hello()
{
printf("hello world\n");
return 0;
}
编译命令为:g++ -std=c++11 -o libtest.so -shared -fPIC test.cpp。编译完成后,就会在当前路径下生成libtest.so文件。
ctype模块
通过ctype模块,可以加载动态库到进程中并返回其实例对象,该方法每次都会返回一个新实例,使用方法如下:
import ctypes
mylib = ctypes.cdll.LoadLibrary("libtest.so") #此次可能要加入绝对路径
ctype数据类型
因为ctypes只能调用C编译成的库,因此不支持重载,需要在程序中显示定义函数类型和返回值类型。ctypes基本数据类型如下:
在定义函数的参数和返回值时,必须使用ctypes的数据类型,若没有显示定义参数类型和返回类型,python默认为int型。参数类型用关键字argtypes定义,返回类型用restype定义,其中argtypes必须是一个序列,如tuple或者list,否则会报错,好像不使用参数类型关键字和返回类型也行。
使用案例
float数据
C/C++代码
float addfloat(float a, float b)
{
printf("a = %f, b = %f \n", a, b);
return a + b;
}
Python端代码
def c_basic_test():
mylib.addfloat.argtypes = [ctypes.c_float, ctypes.c_float]
mylib.addfloat.restype = ctypes.c_float # 必须否则c会是整型
a = ctypes.c_float(2.1)
b = ctypes.c_float(3.5)
c = mylib.addfloat(a, b) # ctypes.c_float(mylib.addfloat(a, b))没设置restype时
print(c)
指针
从上面的表格中,我们可以看到char*和void*已经有专用类型了,直接使用即可,对于其他类型的指针,ctypes提供了两种定义方式pointer和POINTER。POINTER必须传入ctypes类型,创建出新的ctypes指针类型(pointer type),而pointer传入一个对象,创建出一个新的指针实例。(POINTER创建出了pointer)。
传输地址,ctypes提供了byref函数,ctypes.byref(obj[, offset]),该函数返回一个指向ctypes实例对象的轻量级指针,函数中还可以通过参数(必须是int)来设置偏移地址,这个返回的对象只能用于外部函数调用的参数。
C/C++代码
void pointerTest(int* pInt, float* pFloat)
{
*pInt = 10;
*pFloat = 12.34;
}
Python代码
def c_pointer_test():
# 好像可有可无
#library.pointerTest.argtypes = [POINTER(c_int), POINTER(c_float)]
#library.pointerTest.restype = c_void_p
a = 0
b = 0
int_a = ctypes.c_int(a)
float_b = ctypes.c_float(b)
# byref()函数用于传输地址
mylib.pointerTest(ctypes.byref(int_a), ctypes.byref(float_b))
print("out_a:", int_a.value) # a的值还是0,没变
print("out_b", float_b.value)
外部传输字符空间,在函数内部进行赋值
C/C++代码
void mallocTest(char* pszStr)
{
strcpy(pszStr, "Happay Children's day");
}
Python代码
def c_malloc_test():
mylib.mallocTest.argtypes = [ctypes.c_char_p]
mylib.mallocTest.restype = ctypes.c_void_p
word = (ctypes.c_char * 32)()
mylib.mallocTest(word)
print("out_word:", word.value)
输出数组
有时会返回一个数组。
C/C++代码
void OutputArr(float *arr, int len)
{
for (int i = 0; i < len; i++) {
arr[i] = i + 0.5;
}
}
Python代码
def TestOutputArr():
arrf = (ctypes.c_float*10)()
mylib.OutputArr(arrf, ctypes.c_int(10))
for i in range(10):
print(arrf[i])
结构体及结构体指针
结构体是C/C++中常用类型,使用前要先定义其成员类型,在python中也是同样处理,python中的结构体必须继承Structure类,定义其成员必须使用_field_属性。该属性是一个list,其成员都是2个值的tuple,分别是每个结构体成员的类型和长度,而且定义类型必须使用ctypes类型或者由ctype组合而成的新类型。而且结构体还存在嵌套的情况,这里也嵌套的结构体为例。
C/C++代码
struct sub_struct{
char* test_char;
int test_int;
};
struct struct_def {
char* stru_string;
int stru_int;
char stru_arr_num[4];
sub_struct son_struct;
};
int test(struct_def struct_mystruct, struct_def* struct_test_p)
{
//输出结构体指针的数据
cout<<"ouput struct char*:";
cout << struct_mystruct.stru_string << endl;
cout<<"output struct int:";
cout << struct_mystruct.stru_int <<endl;
cout <<"output struct char arr:";
for(int x = 0;x< 4;x++) {
cout << struct_mystruct.stru_arr_num[x]<<" ";
}
cout<< endl;
cout<<"output struct char*:";
cout << struct_mystruct.son_struct.test_char<<endl;
cout<<"ouput struct int:";
cout<<struct_mystruct.son_struct.test_int<<endl;
cout<<endl;
cout<<endl;
cout<<"pointer char*:";
cout << struct_test_p->stru_string << endl;
cout<<"pointer int:";
cout << struct_test_p->stru_int <<endl;
cout <<"pointer char arr:";
for(int x = 0;x< 4;x++) {
cout << struct_test_p->stru_arr_num[x]<<" ";
}
cout<< endl;
cout<<"sub struct string:";
cout<<struct_test_p->son_struct.test_char;
cout << endl;
cout<<"sub struct pointer int";
cout<<struct_test_p->son_struct.test_int<<endl;
}
Python代码
class sub_struct(ctypes.Structure):
_fields_ = [
("test_char_p",ctypes.c_char_p),
("test_int",ctypes.c_int)
]
class struct_def(ctypes.Structure):
_fields_ = [
("stru_string",ctypes.c_char_p),
("stru_int", ctypes.c_int),
("stru_arr_num", ctypes.c_char*4),
("son_struct", sub_struct)
]
struct_mystruct = struct_def()
struct_mystruct.stru_string = b"string in the struct"
struct_mystruct.stru_int = 99
struct_mystruct.stru_arr_num = b"ABCD"
struct_mystruct.son_struct.test_char_p =b"sub struct of the string"
struct_mystruct.son_struct.test_int = 10
mylib.test(struct_mystruct,ctypes.byref(struct_mystruct))
numpy图像当作指针传入
在处理图像时,经常是传入一张图像,输出另外一张图像,图像处理部分是C/C++代码处理,这里也把彩色图像转出灰度图像为例。
C/C++代码
void GetGrayImage(unsigned char *rgb, unsigned char *gray, int width, int height)
{
for(int j =0; j < height; j++) {
for(int i = 0; i < width; i++) {
int r = rgb[j * width + i];
int g = rgb[j * width + i + width * height];
int b = rgb[j * width + i + 2 * width * height];
int val = 0.299 * r + 0.587 * g + 0.114 * b;
gray[j * width + i] = val;
}
}
}
Python代码
img = cv2.imread('1.jpg', 1)
h, w, c = img.shape
imgrgb = img[:, :, ::-1] # BGR2RGB
imgrgb = np.transpose(imgrgb, (2, 0, 1)) # RGBRGB格式转成RRR GGG BBB
imgrgb = imgrgb.reshape(1, -1)
grayimg = np.zeros((h,w), np.uint8)
grayimg = grayimg.reshape(1, -1)
rgbdata = imgrgb.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte))
graydata = grayimg.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte))
width = ctypes.c_int(w)
height = ctypes.c_int(h)
mylib.GetGrayImage(rgbdata, graydata, width, height)
grayimg = grayimg.reshape(h, w)
cv2.imwrite('gray.jpg', grayimg)
暂且列了这些应用,一些没涉及到的,可能根据这些用例可以类似处理,之后如果遇到新的使用案例再新增。
参考资料:
python调用c++模块.so库, 互相回传数据(ctypes、pybind11) - 知乎