python调用C++动态链接库之ctypes(共享结构体、字符串、Mat类型)

说明

本文章所用环境为:

  • linux系统:ubuntu18.04版本
  • opencv版本:
    • python3.4.5.20
    • C++3.4.5

生成和调用动态链接库

对于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()

得到下面的结果:
在这里插入图片描述

python和C++参数的传递

传递简单数据

ctypes支持的原生数据类型如下:

ctypes类型C 类型Python 类型
c_charchar1-character string
c_wcharwchar_t1-character unicode string
c_bytecharint/long
c_ubyteunsigned charint/long
c_boolboolbool
c_shortshortint/long
c_ushortunsigned shortint/long
c_intintint/long
c_uintunsigned intint/long
c_longlongint/long
c_ulongunsigned longint/long
c_longlong__int64 or longlongint/long
c_ulonglongunsigned __int64 or unsigned long longint/long
c_floatfloatfloat
c_doubledoublefloat
c_longdoublelong double floatfloat
c_char_pchar *string or None
c_wchar_pwchar_t *unicode or None
c_void_pvoid *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 <iostream>

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程序得到如下结果:
在这里插入图片描述这样我们就实现了字符串的传递

传递Mat类型

如果我们想在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++程序中进行显示的!

其他需要的类型可以参考上面的方式来进行传递!

  • 6
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值