最近的项目频繁的从matlab,c++,python相互转换,因此接口的书写变得格外重要。
最初对于python调用c++工程使用的swig的方式。swig存在着它的弊端,首先是数值维度,它最高只支持四维数组,碰巧当时的项目需要的是五维数组,因此在每一次生成.i文件时,都需要二次修改(其实展开变为1维数组也是可以的,但是当时懒得写转换函数),其次swig是将c++工程打包成python的一个包,然后在使用的时候进行import。对于生成py文件是十分依赖python的版本的,因此一旦环境改变便需要重新生成,对于环境迁移是极其不方便的。
因此选择使用ctypes进行接口调用。
ctypes主要针对生成dll文件进行调用。对于需要外部调用的函数,在原始代码中需要标注。
extern "C" _declspec(dllexport)
网上相关的教程有很多,其实它的核心语句就是这一句。为了方便调用常常使用宏定义进行替代。
1.dll文件载入
对于已经成功生成的dll,首先是进行调用。这里记录一下我之前踩过的坑。我的python版本是3.8,在载入的时候如果不给winmode赋值的话会报错。
from ctypes import *
dll = CDLL(path)
dll = CDLL(path, winmode=0)
如果需要赋值,winmode设为0。我之前将其设为1,在调试过程中发现一段运行系统内置的头文件中的函数便会发生访问错误。我暂时没有查到原因,不过设为0是没有问题的。
2.调用函数的输入参数
- 数组指针
确保传入参数正确,常常对函数的输入参数类型进行声明。
参数的基本类型在ctypes --- Python 的外部函数库 — Python 3.10.4 文档中均可以找到
这里主要介绍一下指针参数的使用。
import numpy.ctypeslib as ctl
dll.argtypes = [
ctl.ndpointer(np.float32), # data
ctl.ndpointer(np.int32), # shared
c_int32,
POINTER(c_float)
]
ctl.ndpointer(np.float32), POINTER(c_float) 均可以表示float类型的指针
在实际使用当中numpy数组使用的其实更多,极少使用list类型作为数组进行传参。这里主要介绍的是利用numpy数组作为数组指针进行传参。
ctl.ndpointer(np.float32) 作为输入参数声明,参数输入时直接使用numpy.array()即可进行传参。
POINTER(c_float) 作为输入参数声明,使用numpy数组传参需要进行类型的强制转换 array.ctypes.data_as(POINTER(c_float))
- 结构体
由于一些函数需要,我们需要传入结构体。而在原始c++代码中,是包含这部分结构体的定义,在python中调用时我们需要再次声明该结构体。
class sysParas_(Structure):
_fields_ =[
('num_', POINTER(c_float)),
('num0_', c_int)
]
需要注意的是,结构体名称要与c++中结构体名称不同,其中变量名称也是不同的。但是整个变量顺序要完全相同,即每个变量所占用的空间顺序要与原结构体相同。
- 变量顺序
与matlab相同在处理输入参数时要注意输入参数是顺序
3.联合调试
对于dll文件与python联合调试,方法十分简单。这里还是以vs作为例子。
- 确保vs处于debug状态,设置断点。同样在python程序设置断点。
- 运行python程序
- vs调试附加到进程
而且相比matlab来说,python的附加进程更加稳定。
在c++中的输出可以直接在python的控制台显示,更加方便。