python extension_Python C Extension (ctypes)

本文介绍了在Python中封装C库的三种方法,重点讨论了ctypes的使用,包括数据类型的转换、指针操作和结构体定义。通过ctypes,可以在Python中调用C语言的.dll文件,并处理复杂数据类型如句柄。文章还展示了如何处理C异常,提高数据转换的安全性,并探讨了性能影响。
摘要由CSDN通过智能技术生成

几个月前项目中需要用到一个相机,工业厂商给的SDK很全而且文档和demo都有,但是只给了cpp的接口,但主程序的图像处理算法都用python+opencv写好了。因此,开始鼓捣将相机的SDK包裹成python的模块,用了几种方案,重构了几次,总结一下各方案的优劣。

该项目经历了这么三个技术方案:在py中包裹厂商给的.dll文件,用ctypes在py中生成c风格的数据结构,直接用CDLL调用给出的SDK(.dll文件),传入生成的数据类型。

在C中包裹.dll文件,直接用C中的数据类型,打包成.dll文件,再在py中通过ctypes调用并转换成py的数据结构。

用CPython,引入Python.h将cpp文件包裹成py的模块,直接在py中import,获得的也是py的数据类型。

所以C和python之间互相通信最基本的问题就是c中的数据类型和py中的数据类型互相交换的问题,如何转换的更有效率、更安全、可复用性更强,是python的c扩展的焦点。这三个方案对于这个问题的处理如下:方案一:所有的数据类型都在py中生成,ctypes生成c风格的数据类型传给c。

方案二:c中用c的数据类型,处理成几个简单的数据类型传给py,ctypes处理部分数据类型。

方案三:在c中生成py的数据类型,传给py。

第一个方案是最开始的方案,毕竟自己python用的熟练度比c要高很多,能在py域解决的自然会用py解决。甚至为了生成一堆SDK需要的structure,专门写了一个解析器将定义那堆structure的.h文件转换的ctypes定义的.py文件。但随着SDK开发进入到一定的程度,出现了很多ctypes也没法转换的数据类型。比如厂商的某个SDK必须传一个句柄进去,句柄这个东西已经超过了ctypes的能力范围,但你也不能怪厂商写的东西没解耦,毕竟人家写这个的时候可能只打算用到MFC上面去的。

第二个方案是方案一开发不下去的产物,也是用到项目里的方案,优势是可以利用c里面的数据类型,直接include厂商给的SDK中的.h文件就行,不用自己用ctypes构建一堆struct,直接在给的demo里修改出传给py的接口就行了。这个方案凑合着跑了很久,但是这个方案仍然不够pythonic。

第三个方案是现在的使用着的,include了Python.h和numpy提供的C-API。这一方案可以将c域里程序执行的异常raise到py域中,由py的try-except捕获到。同时,在数据转换的时候也更安全。不过,运行的时候界面比之前要卡一点,不知道为什么。

ctypes使用要点

ctypes的使用没什么难的,一句 from ctypes import * 就行了。平时很少看到import * 这一用法,因为容易出现命名冲突,但这里用起来到是很符合c语言的编程风格,把import用的像include似的。

ctypes中基本数据类型是在前面加个前缀“c_”,比如int是c_int,unsigned int是c_uint,不同的是这些在c里面是数据类型,在python里都是类,因此将类实例化成对象就行了。

ctypes中有指针操作,对一个c_int对象取指针用pointer(),然后.contents就是指向的内容,如果要创造指针类型,就用POINTER(c_int),这样实例化出来的就是指针类型。

cytpes中的数组格式是“数据类型*数组长度”,比如c_char*32就是生成了一个长度为32的char数组。

最后是structure,在ctypes中是继承Structure类的一个子类,在类属性__fields__中赋值一个定义结构体成员的列表,举例如下:

from ctypes import *

class MyStructure(Structure):

_fields_ = [("aint", c_int),

("puint", POINTER(c_uint)),

("charArray", c_char*32)]

生成的c文件需要将接口函数写到extern "C"里面,一些关键语句举例如下:

#ifdef EXPORT_MINDPY_DLL#define MINDPY_API __declspec(dllexport)#else#define MINDPY_API __declspec(dllimport)#endif

#include

#ifdef _WIN64//是否导入X64的SDK#pragma comment(lib, "..\\SDK_X64.lib")#else#pragma comment(lib, "..\\SDK.lib")#endif

extern "C"

{

MINDPY_API int GetArray();

}

编译为dll的命令为 cl /LD Mydll.cpp 其中/不要写成\,LD区分大小写,还有cl.exe需要添加到环境变量中。

在生成numpy的数组时可以直接将ctypes生成的数组用numpy初始化成一个ndarray,具体代码如下:

mydll = CDLL('Mydll.dll')#调用生成的dll

ctypeArray = c_byte * arrayLen#定义一个arrayLen长度的c_byte类型数组

arget = ctypeArray()#实例化该数组

md = mydll.GetArray(arget, arrayLen)#这里的arget就是数组的头指针了

npArray = np.array(arget, dtype=np.uint8)#生成ndarray

感觉内容有点多,剩下的和Python.h和numpy有关的内容分到下一节更新吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值