最近在使用Python编写一些控制程序,由于需要控制一些CCD相机之类的外设,而这些外设通常又只提供c的SDK,因此需要自己编写一些c/c++的扩展程序,方便用python来控制这些设备。下面介绍一下我的编写经验。
使用工具:VS2017;python 3.6;操作系统:win10,x64。
首先,安装VS2017:
社区版的VS2017可以在官网上免费下载和安装使用。当然,在免费使用一个月后会要求你登录账号,免费注册一个账号然后乖乖登录就可以了。如果实在不想登录或电脑没有联网,还有一个办法就是修改电脑的本地日期,不超过安装日期一个月就行。
VS2017的安装很简单,需要选择安装的模块如下图所示,注意一定要勾选Python开发模块,这是我们主要使用的模块。
创建Python扩展模块:
VS2017安装完成后,运行,进入新建项目窗口,如下图所示,选择Python扩展模块,设置好模块的名称和文件位置。这里我设置的模块名称是Extention,位置在D盘Python\c extention文件夹下。设置完成后单击确定即可。
创建好的Python扩展模块如下图所示。通常需要先在左侧解决方案资源管理器界面右键单击项目名称,选择属性,进入项目属性设置界面。在属性设置界面,需要设置你的编译平台和Windows SDK版本,如下图所示,如果是64位的系统平台就选x64,SDK选择最新的版本。
属性设置完成后,扩展模块程序应该就可以正确编译了。
接下来,打开项目Source Files文件夹下的Extenstion.c文件,开始编写扩展程序。
首先,来看一下我们的扩展程序,
第一行
#include <Python.h>
,需要包含Python.h头文件。如果你的python安装后没有添加系统路径,VS2017可能找不到这个头文件而报错,可以手动把该文件的路径加上去,这个文件位于…\Python36\include文件夹下。
接下来,是
PyDoc_STRVAR
函数,这个里面可以写一些本扩展模块的说明和备注,可以先不管它。
再往下是一个
PyObject *Extention_example()
函数。我们需要在这里添加我们的程序,等下我们来详细介绍这个函数。
再往下是
static PyMethodDef Extention_functions[] =...
函数,这里是我们自己定义的函数的一个列表。我们自己写的所有python可调用的函数都要在这里进行登记。
再接下来,就是
int exec_Extention()
函数,点开这个函数,可以发现里面是一些模块作者、版本、创建时间之类的信息,我们也先不用管这个函数。
后面的几个函数我们也都不用管,放在那里就行了。
总得来说,我们需要做的就是编写自己的
PyObject *Extention_yourFunction()
函数,然后在
static PyMethodDef Extention_functions[] =...
里面登记好就可以了。所以最主要的工作还是
PyObject *Extention_yourFunction()
函数的编写。
关于函数
PyObject *Extention_example(PyObject *self, PyObject *args, PyObject *kwargs)
,需要说明的是,Python和c模块之间的数据传递都是通过PyObject来实现的。即Python中所有类型的数据在c看来都是一个PyObject,而c要把自己产生的数据传递给Python也需要先把它包装成一个PyObject才行。因此,我们可以看到
PyObject *Extention_yourFunction()
函数的输入参数和返回值都是PyObject类型的。
我们先来看下面的一个例子:
PyObject *Extention_add(PyObject *self, PyObject *args) {
/* Shared references that do not need Py_DECREF before returning. */
PyObject *obj = NULL;
int a,b, result;
/* Parse positional and keyword arguments */
//static char* keywords[] = { "obj", "number", NULL };
if (!PyArg_ParseTuple(args, "ii", &a,&b)) {
return NULL;
}
/* Function implementation starts here */
result = a+b;
return Py_BuildValue("i",result);
}
函数很简单,在python中调用本函数后返回两个数之和。这里面主要用到
PyArg_ParseTuple()
和
Py_BuildValue()
两个函数。
其中
PyArg_ParseTuple()
函数用于从输入变量args中取出数据并赋给本地变量,其使用方式与print函数类似:int PyArg_ParseTuple(PyObject *arg, char *format, …)。在上面的例子中,
PyArg_ParseTuple(args, "ii", &a,&b)
意思是,从args中取出两个int类型的数并分别赋给a和b,并返回操作的结果,操作成功则返回1,否则0。不同数据类型的格式字符可以参考
链接
。
函数
Py_BuildValue()
用于将c中的数据打包成PyObject类型,其定义为:
PyObject *Py_BuildValue(char *format, ...)
。本例中,
Py_BuildValue("i",result);
代码的意思是将int类型的数据result打包成PyObject。
函数定义好后,需要在
static PyMethodDef Extention_functions[] =...
中登记才能调用。登记方式如下所示,第一项为在python中调用该函数时使用的函数名;第二项为本文件中定义该函数时使用的函数名;第三项可以选择
METH_NOARGS
或
METH_VARARGS
,前者表示无参数,后者表示有参数;第四项是该函数的描述,在python中通过help函数可以看到该信息。
static PyMethodDef Extention_functions[] = {
{ "add", (PyCFunction)Extention_add, METH_VARARGS, "Return an array." },
{ NULL, NULL, 0, NULL } /* marks end of array */
};
至此,就完成扩展函数的编写啦。保存后,右键单击项目名称,选择生成,就会生成一个“Extention.pyd”文件,然后将该pyd文件放在…\Python36\DLLs目录下即可。然后在python中像使用其它模块一样导入我们的扩展模块
import Extention as ex
,然后,调用函数
ex.add(1,2)
即可得到预期的结果了。