昨天看了一下使用C语言对python进行扩展,但是死活都不成功,后来发现原来那个方法是Linux平台的方法,直接郁闷到了。然后今天在网上查了一下在Windows下怎么扩展python,但是也是始终无法成功,这让我相当的不爽啊,难道就因为我用的是VC++2010吗?后来实在没办法,只能去看官方的英文文档了,但是官方的扩展方法需要使用源代码,然后我就下载了源代码,转换了格式(因为官方使用的也不是VC++2010),最后确实成功了。我想这大概是属性配置的问题,于是我就粗略的在看了看官方文档,就转过头来看配置。将我自己的配置属性和源代码中例子的配置属性对比了一下,发现在连接器的命令行选项中多出了一项:/export:initexample。这个时候我意识到了很有可能就是这里的问题,经过我的验证,虽然其他的地方也有些许不同,但是没什么影响,可以肯定确实是这里的问题。激动之余,把我的发现与和我一样在使用VC2010并且想要在Windows下扩展python却始终无法实现的同志们分享一下。
我们就来实现一个简单的扩展,定义一个叫做testMod的模块,在这个模块中包含一个对两个整数相加求和的函数和一个test函数。那么先来用C语言来实现这个函数并测试一下吧。
#include <stdio.h>我们使用纯C语言来完成确实是轻而易举,那么现在就来讲这个C语言程序进行一番改造使之成为python的扩展吧。为了能够是C语言和python之间形成交互,我必须在C语言的源文件中加上Python.h这个头文件。接下来我们进行第二步,将函数改造成python可以调用的形式。那么怎么改造呢?我们需要使用PyObject* Module_function(PyObject *self, PyObject *args)函数将我们所需要的函数包装起来。这看着是相当的纠结啊,我们还是直接来看对sum函数的改造吧:
int sum(int a, int b);
int main() {
printf("2 + 3 is %d\n", sum(2, 3));
printf("23 + 177 is %d\n", sum(23, 177));
return 0;
}
int sum(int a, int b) {
return a + b;
}
int sum(int a, int b) {
return a + b;
}
static PyObject *testMod_sum(PyObject *self, PyObject *args) {
int a, b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return (PyObject *)Py_BuildValue("i", sum(a, b));
}
我们现在来解释一下这段代码,首先,在C语言和python间的交互必须使用PyObject类型。其次,这个函数是返回PyObject*类型,并且以模块名加上下划线加上函数名来命名的一个函数,即:testMod_sum。在这个函数中有两个参数,第一个是self,第二个是args。虽然书上没有说明这两个参数是什么,但是大概也能猜测得到,第一个参数类似于C++中的this指针之类的东西,与python中定义一个类方法类似;第二个参数则是python传入的参数。好,继续往下看,PyArg_ParseTuple函数是干什么的?还有,"ii"又是什么?先来看PyArg_ParseTuple函数把,这个函数是用来解析从python中传入的参数并将参数的值以对应的类型传个C语言中定义的变量的。因此我们可以从这段代码中知道,args就是python中传入的参数,后面的"ii"就是对应的值的类型了,i表示整型,而ii表示有两个整型,没错,这与C语言中sscanf函数非常相似,只不过我们使用的不是%d而是使用的i,而后面则是两个整型变量的地址了。如果解析成功了,那么这个函数将会返回1,也就是真,我们将进行下一步;如果解析失败则会返回0,也就是假,这时我们则返回一个空指针。
PyArg_ParseTuple函数很像sscanf函数,那么有没有发现Py_BuildValue函数很想prinf函数呢?没错,我们只需要这样去理解就行了。只不过Py_BuildValue函数返回的不是输出的字符个数,而是PyObject类型的指针。这个指针所指向的变量包含了我们所要传递给python的数据,在这段代码中就是a与b的和。如法炮制,我们可以将main函数改造成一个test函数。
void test() {
printf("2 + 3 is %d\n", sum(2, 3));
printf("23 + 177 is %d\n", sum(23, 177));
}
static PyObject* testMod_test(PyObject *self, PyObject *args) {
test();
return Py_None;
}
在这段代码中PyNone表示的是空对象,也可以使用(PyObject *)Py_BuildValue("")。
函数改造完了是不是就完了呢?不是的,我们还有其他的工作要做,我们要为模块创建一个函数列表:
static PyMethodDef testModMethods[] = {
{"sum", testMod_sum, METH_VARARGS},
{"test", testMod_test, METH_VARARGS},
{NULL, NULL}.
};
记得在最后加上分号,因为这是创建一个数组,不然会出错的。在这个列表中最后的{NULL, NULL}是用来表示列表已经结束了。不要急,还有最后一步了——为模块创建初始化函数,以init加上模块名作为函数名:
void inittestMod() {
Py_InitModule("testMod", testModMethods);
}
在初始化函数中,我们调用Py_InitModule函数来完成初始化,该函数的第一个参数是模块的名字,第二个参数是给模块的函数列表。好了,这样这个C语言源代码就改造完成了,来看一看完整的代码吧:
#include <stdio.h>
int sum(int a, int b) {
return a + b;
}
void test() {
printf("2 + 3 is %d\n", sum(2, 3));
printf("23 + 177 is %d\n", sum(23, 177));
}
#include "Python.h"
static PyObject* testMod_sum(PyObject *self, PyObject *args) {
int a, b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return (PyObject*)Py_BuildValue("i", sum(a, b));
}
static PyObject* testMod_test(PyObject *self, PyObject *args) {
test();
return Py_None;
}
static PyMethodDef testModMethods[]= {
{"sum", testMod_sum, METH_VARARGS},
{"test", testMod_test, METH_VARARGS},
{NULL, NULL},
};
// 感谢@lilongduzhi的提醒,PyMODINIT_FUNC的具体定义在pyport.h文件中可以查看
PyMODINIT_FUNC inittestMod() {
Py_InitModule("testMod", testModMethods);
}
好了,源文件已经处理完了,值得注意的是除了模块的初始化方法不是static以外被改造的函数都必须是static。
说明一下,我使用的是python2.7.2,安装在D:\Python27目录中,在下文中我会使用py_install_dir来替代安装目录。那么我们现在就来看看怎么创建吧。打开VC++2010,新建项目>>Win32项目>>输入testMod作为项目名>>点击确定>>下一步>>选择DLL>>点击完成。然后我们把之前的源代码粘贴到testMod.cpp中,保存。接下来就是关键了,我们要设置该项目的属性了。
首先,将项目设置为release。然后,在项目名上点击右键并选择属性>>配置属性>>常规>>目标文件扩展名,将其改为.pyd。接着我们需要添加Python.h头文件的目录,这个文件在python的安装目录的include目录中有,因此我们只需要添加该目录即可。添加这个目录有两种方法。
方法一:
配置属性>>VC++目录>>包含目录,点击下拉菜单选择编辑,添加py_install_dir\include目录。记得吗?我将使用py_install_dir来代替python的安装目录。
方法二:
配置属性>>C/C++>>命令行,在其他选项中输入/Ipy_install_dir\include
之前说过,我使用的python是2.7.2版本,因此我还需要向项目中加入python27.lib库,这个库位于py_install_dir\libs目录中,因此我们又有两种方法可以添加这个库。
方法一:
配置属性>>连接器>>常规>>附加库目录,我们只需要将py_install_dir\libs这个目录添加景区就行了。
方法二:
配置属性>>连接器>>命令行,在其他选项中直接添加py_install_dir\libs\python27.lib。
最后,也是最重要的,配置属性>>连接器>>命令行>>其他选项中添加/export:模块初始化方法名,在我们所要完成的这个模块中需要添加:/export:inittestMod,否则在导入模块时出现模块为定义初始化方法的错误。
这样项目属性就配置完成了。现在编译吧。然后找到这个项目的Release目录,将testMod.pyd文件复制或剪切到你的py_install_dir\DLLs目录之类的地方,现在打开python的解释器测试吧:
或者通过命令行进入该项目的Release目录,打开python解释器,测试模块。