本文章所用环境为:
对于C++程序,本文章通过g++编译指令,使用参数 -shared 来生成动态链接库文件(.so)
这里,我创建了一个cpp文件,然后使用下面的编译指令来生成动态链接库:
g++ ctypes_stu.cpp -shared -fPIC -o ctypes_stu.so
ctypes_stu.cpp是我们的C++程序,用于生成动态链接库
-shared是告诉编译器,我们生成的是动态链接库(.so文件)
-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
-o ctypes_stu.so来指定输出文件的名称
运行上面的编译指令后,会在cpp文件的目录下生成一个名为ctypes_stu.so的动态链接库文件,该文件可以被python程序调用:
from ctypes import *
mydll = cdll.LoadLibrary("./ctypes_stu.so") #加载动态链接库给mydll
上面代码的
mydll = cdll.LoadLibrary("./ctypes_stu.so")
可以使用
mydll = CDLL("./ctypes_stu.so")
进行替换。
有时会使用windll和WINDLL来替代cdll和CDLL,这是取决于导出函数的调用规范(_cdecl或_stdcall),这里不作详细解释。
在加载动态链接库后,我们就可以使用
mydll.func_name()来调用C++中的函数(func_name表示的是C++中的导出函数名称)
注意:我们使用C++来编译程序时,对于C++程序中的导出函数,要添加 extern “C” 来进行修饰:
extern "C" void functest()
{
cout << "_____以下为c++输出______" << endl;
cout << "调用函数" << endl;
}
编译之后我们使用python进行调用:
from ctypes import *
dll = CDLL("./ctypes_stu.so")
dll.functest()
ctypes支持的原生数据类型如下:
ctypes类型 | C 类型 | Python 类型 |
---|---|---|
c_char | char | 1-character string |
c_wchar | wchar_t | 1-character unicode string |
c_byte | char | int/long |
c_ubyte | unsigned char | int/long |
c_bool | bool | bool |
c_short | short | int/long |
c_ushort | unsigned short | int/long |
c_int | int | int/long |
c_uint | unsigned int | int/long |
c_long | long | int/long |
c_ulong | unsigned long | int/long |
c_longlong | __int64 or longlong | int/long |
c_ulonglong | unsigned __int64 or unsigned long long | int/long |
c_float | float | float |
c_double | double | float |
c_longdouble | long double float | float |
c_char_p | char * | string or None |
c_wchar_p | wchar_t * | unicode or None |
c_void_p | void * | int/long or None |
如果我们传递的参数是上面的类型,就可以对导出函数使用argtypes和restype来定义传递参数类型和返回值类型。
例如:
extern "C" int functest(int a, int b, bool add)
{
cout << "_____以下为c++输出______" << endl;
cout << "a:" << a << "b:" << b << endl;
if(add)
return a+b;
else
return a-b;
}
我们使用python进行调用:
from ctypes import *
#dll = cdll.LoadLibrary("./ctypes_stu.so")
dll = CDLL("./ctypes_stu.so")
dll.functest.restype = c_int;
dll.functest.argtypes = (c_int,c_int, c_bool);
retval = dll.functest(1,2,False)
print("_____以下为python端输出______")
print(retval)
得到结果为:
这里定义functest函数的返回值为c_int类型,传递的参数值类型分别为(c_int,c_int, c_bool),定义之后我们就可以使用
retval = dll.functest(1,2,False)
调用functest函数并将返回值存储在retval中。
自定义的结构体和联合体必须继承自ctypes的Structure和Union,这两个类都在ctypes模块中定义。每一个子类必须定义"_fields_“属性,”_fields_"是一个二维的tuples列表,用于描述类的每个数据成员的字段名和字段类型,这里的字段类型必须是一个ctypes类型,如c_int,或者任何其他的继承ctypes的类型,如Structure, Union, Array, 指针等。
POINTER 返回类型对象,用来给 restype 和 argtypes 指定函数的参数和返回值的类型用。
那么使用结构体指针就要使用POINTER来生成指针类型。
我们这里使用参数中的结构体指针来将C++函数中的数据返回给python。
C++程序:
#include
using namespace std;
typedef struct mystruct
{
int x;
int y;
};
extern "C" int functest(mystruct* cppstruct, int c)
{
int a=1;
int b=2;
cout << "_____以下为c++输出______" << endl;
cout << "a:" << a << "b:" << b << endl;
cppstruct->x = a-b+c;
cppstruct->y = a+b+c;
return 0;
}
int main()
{
cout << "hello world!" << endl;
return 0;
}
在C++程序中主要定义了一个结构体类型mystruct,该类型里面有两个值x和y。
然后定义了一个导出函数functest,其参数为一个结构体数组指针和一个整型。
python程序:
from ctypes import *
class mystruct(Structure):
_fields_ = [('x',c_int),('y',c_int)]
p_buff = create_string_buffer(sizeof(mystruct) * 1)
p_struct = POINTER(mystruct)(p_buff)
#dll = cdll.LoadLibrary("./ctypes_stu.so")
dll = CDLL("./ctypes_stu.so")
dll.functest.restype = c_int
dll.functest.argtypes = ( POINTER(mystruct), c_int)
success = dll.functest( p_struct, 3)
print("_____以下为python端输出______")
print(p_struct[0].x,p_struct[0].y)
主要详细说明python程序:
p_buff是一个内存空间,表示的是创建buffer的类型和长度,这里创建的是一个结构体mystruct类型,长度为1的数组空间,然后使用该空间存储一个结构体指针p_struct。
这里的POINTER(mystruct)表示的是将一个mystruct指针类型,通过该类型定义p_struct。
所以这里的p_struct就是一个结构体数组指针对象,可以作为参数传入C++导出函数。
然后下面两行:
dll.functest.restype = c_int
dll.functest.argtypes = ( POINTER(mystruct), c_int)
是定义导出函数functest的返回值类型和参数类型,从这里可以看出,POINTER(mystruct)表示的是一种类型–结构体数组指针。
然后调用导出函数functest,将p_struct作为参数,通过在python端打印其值可以看出,我们实现了python和C++的数据传递。
注意:
这里的结构体定义:
class mystruct(Structure):
_fields_ = [('x',c_int),('y',c_int)]
要保证python结构体名称(继承了Structure的类)以及结构体中的成员(x和y)和C++中保持一致。
python和C++之间不能通过string直接传递字符串类型,所以我们需要通过char*指针来传递:
python定义字符串前要加b修饰,表示使用bytes类型来定义字符串;或者我们可以使用encode()来进行转换:
mystring = b"abcdefg"
等价于:
prestring = "abcdefg"
mystring = prestring.encode()
C++导出函数如下:
extern "C" int functest(mystruct* cppstruct, int c, char* p_char)
{
int a=1;
int b=2;
string mystring = p_char;
cout << "_____以下为c++输出______" << endl;
cout << "a:" << a << "b:" << b << endl;
cout << "收到的字符串为:"<<mystring<<endl;
cppstruct->x = a-b+c;
cppstruct->y = a+b+c;
p_char[5] = 'F';
return 0;
}
这里第三个参数p_char是char*格式的,我们把p_char转换为string类型并输出,然后改变p_char的第6个字符为大写F(原本是小写f)。
python代码如下:
from ctypes import *
class mystruct(Structure):
_fields_ = [('x',c_int),('y',c_int)]
p_buff = create_string_buffer(sizeof(mystruct) * 1)
p_struct = POINTER(mystruct)(p_buff)
pystring = b"abcdefg"
#codestring = pystring.encode()
#dll = cdll.LoadLibrary("./ctypes_stu.so")
dll = CDLL("./ctypes_stu.so")
dll.functest.restype = c_int
dll.functest.argtypes = ( POINTER(mystruct), c_int, c_char_p)#注意这里的类型,第三个为c_char_p,对应的就是char*
success = dll.functest( p_struct, 3, pystring)
print("_____以下为python端输出______")
print(p_struct[0].x,p_struct[0].y)
print(pystring)
然后我们运行该python程序得到如下结果:
这样我们就实现了字符串的传递
如果我们想在python和C++之间传递图像类型,我们可以使用Mat类型进行传递,当然,我们不能直接传递Mat类型,需要在传递时进行转换(利用uchar和Mat转换,传递时使用uchar类型,传递后转换回Mat类型)。
在C++中定义如下函数:
extern "C" Mat get_mat(uchar* mat_data, int rows, int cols, int channels)
{
Mat dst = Mat(Size(cols, rows), CV_8UC3, Scalar(255,255,255));
dst.data = mat_data;
imshow("dst", dst);
waitKey(0);
return dst;
}
我们需要得到Mat对象的rows、cols、channels以及对应的data数据。
这里我们使用一个函数进行测试:
extern "C" int mat_test(uchar* matrix, int rows, int cols, int channels)
{
Mat frame;
frame = get_mat(matrix, rows, cols, channels);
cout<<"success!!!"<<endl;
return 0;
}
在python程序中,我们调用函数mat_test()来进行测试:
from ctypes import *
import cv2 as cv
#dll = cdll.LoadLibrary("./ctypes_stu.so")
dll = CDLL("./ctypes_stu.so")
videoCapture = cv.VideoCapture(0)
success, frame = videoCapture.read()
cols = frame.shape[1]
rows= frame.shape[0]
channels = 3
pubyIm = frame.ctypes.data_as(POINTER(c_ubyte))
ret = dll.mat_test(pubyIm, rows, cols, channels)
这里需要说明的一点是: 如果我们需要编译使用C++程序时使用了opencv,我们需要在g++编译时添加下面的参数:
`pkg-config --cflags --libs opencv`
那么完整的g++编译指令就是:
g++ ctypes_stu.cpp -shared -fPIC `pkg-config --cflags --libs opencv` -o ctypes_stu.so
否则会出现一些其他错误,比如:
OSError: ./ctypes_stu.so: undefined symbol: _ZN2cv3Mat10deallocateEv
然后我们运行程序得到结果为:
这里的手图片是在python中获取,然后传递到C++程序中进行显示的!
其他需要的类型可以参考上面的方式来进行传递!