BBS水木清华站∶精华区
发信人: kafa (staring at the sun), 信区: Linux
标 题: 用python扩展模块来做海龟画图
发信站: BBS 水木清华站 (Wed Jan 24 01:26:06 2001) WWW-POST
我们常常希望把某个程序内部的功能通过脚本的形式暴露出来,使用户可以跟据自己的需
要进行二次开发,例如,我们可以用Zmud的脚本语言来编机器人自动练功完成任务,Quake里
可以用脚本描述和设计关卡,这样关卡设计就可以交给专门的人来作,减轻了程序员的工作
量.但是,要自己开发一个成熟的脚本语言本身并不是一件简单的事,所以我们可以利用已
有的各种脚本语言,例如Tcl/Tk, Perl, Python, Guile, Ruby等.它们都提供了把自己的
解释器嵌入应用程序的C语言的API,我们可以直接利用它们的解释器来作语法分析等"脏活
累活",而自己只需把需要暴露的功能作成脚本语言的扩展模块.这种方法简单灵活,正变的
越来越流行,微软也推出了script engine提供类似的功能.
python是一种解释型的,面向对象的编程语言.由于它的C API清楚简单,所以作为嵌入式
的脚本语言非常合适.不知道大家有没有用过Logo?用过的人肯定记得那个海龟吧?呵呵,今
天我们就用python来作一个简单的海龟画图程序.为了简单,我们把它做成一个python的扩
展模块.我先解释一下,利用python的CAPI可以作两件事:扩展(extend)和嵌入(embed).扩
展是用C编写python的模块使你在python里可以调用,简单说就是把C嵌入python;而嵌入是
把python解释器植入到C写的应用程序里面,使程序可以解释执行python的脚本程序.扩展
和嵌入都是在C语言和脚本语言之间作数据转换的工作,但是把python嵌入应用程序的方式
很多,用起来非常灵活,而且往往也要涉及到扩展,所以说嵌入要比扩展复杂一些,不过原理
是一样的.
好,我们先写一个没有python的简单程序,实现必需的功能.程序如下:
/*------------------------------------------------------------------------*/
/* tortoise1.c */
#include <unistd.h>
#include <X11/Xlib.h>
#define WINDOW_SIZE 500
Display *theDisplay;
Window theWindow;
Screen *theScreen;
GC theGC;
double currentX;
double currentY;
double currentDirection;
int penDown;
#include <math.h>
#define DEGREES_TO_RADIANS (3.1415926535897932384626433832795029L/180.0)
/* 复位. 海龟回到窗口中心,落笔,面向北方 */
void tortoise_reset()
{
currentX = currentY = WINDOW_SIZE/2;
currentDirection = 0.0;
penDown = 1;
}
/* 落笔 */
void tortoise_pendown()
{
penDown = 1;
}
/* 抬笔 */
void tortoise_penup()
{
penDown = 0;
}
/* 转弯 */
void tortoise_turn(int degrees)
{
currentDirection += (double)degrees;
}
/* 前进 */
void tortoise_move(int steps)
{
double newX, newY;
/* first work out the new endpoint */
newX = currentX + sin(currentDirection*DEGREES_TO_RADIANS)*(double)steps;
newY = currentY - cos(currentDirection*DEGREES_TO_RADIANS)*(double)steps;
/* if the pen is down, draw a line */
if (penDown) XDrawLine(theDisplay, theWindow, theGC,
(int)currentX, (int)currentY, (int)newX,
(int)newY);
/* in either case, move the tortoise */
currentX = newX;
currentY = newY;
}
int main(int argc, char *argv[])
{
theDisplay = XOpenDisplay(NULL);
XSynchronize(theDisplay, True);
theScreen = DefaultScreenOfDisplay(theDisplay);
theWindow = XCreateSimpleWindow(theDisplay, RootWindowOfScreen(theScreen),
0, 0,
WINDOW_SIZE, WINDOW_SIZE, 0,
BlackPixelOfScreen(theScreen),
WhitePixelOfScreen(theScreen));
theGC = XCreateGC(theDisplay, theWindow, 0L, NULL);
XSetForeground(theDisplay, theGC, BlackPixelOfScreen(theScreen));
XMapWindow(theDisplay,theWindow);
/* 画个简单的正方形测试一下 */
tortoise_reset();
{
int ii;
tortoise_pendown();
for (ii=0; ii<4; ii++) {
tortoise_move(100);
tortoise_turn(90.0);
}
/* sleep for a bit so the window stays visible */
sleep(10);
}
return 0;
}
/*------------------------------------------------------------------------*/
你可以用
gcc -o t1 tortoise1.c -I/usr/X11R6/include -lX11 -L/usr/X11R6/lib -lm
编译它,运行一下看看.
在上面这个程序里,如果要画个什么图形就要修改程序,然后重新编译调试运行,这太麻烦
了!下面我们把它做成python的扩展模块.给python写扩展模块其实是一件很简单很模式化
的任务,所以Python提供了一个模块框架生成器modulator,可以替你生成基本的扩展模块
的程序框架,你只需在这个基础上面把自己的具体实现代码填上就可以了.它一般放在
python源程序的Tools/modulator目录下面.如果你没有找到,就去下一个python的源代码
然后编译一下就有了.
好,我们先运行modulator,程序的界面很简单.我们先在Module输入条里填上扩展模块的
名字--tortoise,然后在Add method输入条里加入以下几个方法名: reset, pendown,
penup, turn, move. 最后按一下Generate code...按钮生成c代码,生成文件名最好叫做
tortoisemodule.c以符合python扩展模块的命名习惯.退出modulator,可以看到在当前目
录下已经多了一个叫tortoisemodule.c的文件,内容如下(中文是我加的注释):
/*------------------------------------------------------------------------*/
/* 所有的python CAPI需要的常量,宏和函数原型都包含在这个头文件里 */
#include "Python.h"
/* 定义一个本模块用的例外对象 */
static PyObject *ErrorObject;
/* reset函数的doc */
static char tortoise_reset__doc__[] =
""
;
/* 返回PyObject类型的指针,指向具体的python返回对象 */
static PyObject *
tortoise_reset(self, args)
PyObject *self; /* 这个参数只有在做python扩展类型(extend type)
的时候才会用到,这里我们不用 */
PyObject *args; /* 传入的是python的tuple类型的对象,里面包含需要的参数 */
{
/* 从tuple里提取出真正的C的参数 */
if (!PyArg_ParseTuple(args, ""))
return NULL;
/* 返回Py_None类型的对象表示函数正常返回,没有错误发生 */
Py_INCREF(Py_None);
return Py_None;
}
static char tortoise_pendown__doc__[] =
""
;
static PyObject *
tortoise_pendown(self, args)
PyObject *self; /* Not used */
PyObject *args;
{
if (!PyArg_ParseTuple(args, ""))
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static char tortoise_penup__doc__[] =
""
;
static PyObject *
tortoise_penup(self, args)
PyObject *self; /* Not used */
PyObject *args;
{
if (!PyArg_ParseTuple(args, ""))
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static char tortoise_turn__doc__[] =
""
;
static PyObject *
tortoise_turn(self, args)
PyObject *self; /* Not used */
PyObject *args;
{
if (!PyArg_ParseTuple(args, ""))
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static char tortoise_move__doc__[] =
""
;
static PyObject *
tortoise_move(self, args)
PyObject *self; /* Not used */
PyObject *args;
{
if (!PyArg_ParseTuple(args, ""))
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
/* List of methods defined in the module */
/* 模块中所有方法的列表,这是一个扩展模块的关键数据结构
例如当你在python里调用tortoise.pendown()方法时,
python解释器会来查这个表,找到对应的函数tortoise_pendown()
*/
static struct PyMethodDef tortoise_methods[] = {
{"reset", (PyCFunction)tortoise_reset, METH_VARARGS, tortoise_reset__doc__},
{"pendown", (PyCFunction)tortoise_pendown, METH_VARARGS, tortoise_pendown__do
c__},
{"penup", (PyCFunction)tortoise_penup, METH_VARARGS, tortoise_penup__doc__},
{"turn", (PyCFunction)tortoise_turn, METH_VARARGS, tortoise_turn__doc__},
{"move", (PyCFunction)tortoise_move, METH_VARARGS, tortoise_move__doc__},
{NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */
};
/* Initialization function for the module (*must* be called inittortoise) */
static char tortoise_module_documentation[] =
""
;
/* 每个模块都要有这样一个初始化函数,
在import这个模块的时候python会自动执行这个函数.
函数命名必须是 init<modulename>
*/
void
inittortoise()
{
PyObject *m, *d;
/* Create the module and add the functions */
/* 增加tortoise这个模块 */
m = Py_InitModule4("tortoise", tortoise_methods,
tortoise_module_documentation,
(PyObject*)NULL,PYTHON_API_VERSION);
/* Add some symbolic constants to the module */
/* 得到此模块的名字空间 */
d = PyModule_GetDict(m);
/* 添加一个错误对象的名字 */
ErrorObject = PyString_FromString("tortoise.error");
PyDict_SetItemString(d, "error", ErrorObject);
/* XXXX Add constants here */
/* 在这里可以加入模块的常量 */
/* Check for errors */
if (PyErr_Occurred())
Py_FatalError("can't initialize module tortoise");
}
/*------------------------------------------------------------------------*/
这个文件其实就可以编译了,只不过没有任何功能罢了,呵呵.我们去Python源程序的
Demo/extend目录,把make_shared拷贝一份叫my_make_shared,然后把my_make_shared里面
的xxmodule都改成我们的tortoisemodule.如果上面那个tortoisemodule.c不在
Tools/modulator目录下,还需要修改第10行的路径指向tortoisemodule.c的目录.最后,再
加上编译X程序需要的包含库.呵呵,说起来挺罗嗦的,看看我的my_make_shared文件吧:
#! /bin/sh
# This script tests and demonstrates the mechanism for building a
# shared library for an additional extension module using the
# generic Makefile.pre.in from the Misc directory.
./make_clean
cp ../../Misc/Makefile.pre.in .
cp /home/kafa/guile/tortoisemodule.c .
echo '*shared*' >Setup.in
echo tortoise tortoisemodule.c -I/usr/X11R6/include -lX11 -L/usr/X11R6/lib
-lm >>Setup.in
make -f Makefile.pre.in boot
make Makefile
make
######################################################################
然后我们运行my_make_shared就可以在此目录下生成一个叫tortoisemodule.so的动态联
接库文件,这就是我们需要的python扩展模块.现在我们就可以在此目录下试试这个扩展模
块了,运行命令'python'起动python的交互式解释器,然后执行'import tortoise'导入
tortoise模块.如果没有错误发生的话,现在你可以用'dir(tortoise)'看看tortoise模块
里是不是包含了我们要的那些函数,也可以用'tortoise.pendown()'命令调用一下这些方
法看看有什么结果.
OK,现在tortoise的基本程序和扩展模块的骨架都已经有了,让我们把它们合在一起,就在
那个tortoisemodule.c上面改吧,先把tortoise1.c里的头文件和定义的宏copy进来,然后
把main()函数里面初始化X和工作窗口的代码放到一个函数里,就叫init_workspace()吧,
然后就是真正有意义的工作--把模块函数加入真正的代码实现我们需要的功能,就以
tortoise_turn()为例:
static PyObject *
tortoise_turn(self, args)
PyObject *self; /* Not used */
PyObject *args;
{
/* 定义一个整型变量用来放真正传进来的degrees参数 */
int degrees;
/* 从python对象里提取出一个整型参数付给degrees
PyArg_ParseTuple()函数里的"i"表示int类型 */
if (!PyArg_ParseTuple(args, "i", °rees))
return NULL;
/* 真正的功能代码 */
currentDirection += (double)degrees;
/* 没有错误发生,正常返回 */
Py_INCREF(Py_None);
return Py_None;
}
很简单吧?呵呵,依样画葫芦修改其它几个函数,这个扩展模块就完成了.我的改过的
tortoisemodule.c文件内容如下:
/* ------------------------------------------------------- */
/* tortoisemodule.c */
#include <unistd.h>
#include <math.h>
#include <X11/Xlib.h>
#define WINDOW_SIZE 500
#define DEGREES_TO_RADIANS (3.1415926535897932384626433832795029L/180.0)
Display *theDisplay;
Window theWindow;
Screen *theScreen;
GC theGC;
double currentX;
double currentY;
double currentDirection;
int penDown;
#include "Python.h"
static PyObject *ErrorObject;
/* ----------------------------------------------------- */
static void
init_workspace(void)
{
theDisplay = XOpenDisplay(NULL);
XSynchronize(theDisplay, True);
theScreen = DefaultScreenOfDisplay(theDisplay);
theWindow = XCreateSimpleWindow(theDisplay, RootWindowOfScreen(theScreen),
0, 0,
WINDOW_SIZE, WINDOW_SIZE, 0,
BlackPixelOfScreen(theScreen),
WhitePixelOfScreen(theScreen));
theGC = XCreateGC(theDisplay, theWindow, 0L, NULL);
XSetForeground(theDisplay, theGC, BlackPixelOfScreen(theScreen));
XMapWindow(theDisplay,theWindow);
}
static char tortoise_reset__doc__[] =
""
;
static PyObject *
tortoise_reset(self, args)
PyObject *self; /* Not used */
PyObject *args;
{
if (!PyArg_ParseTuple(args, ""))
return NULL;
currentX = currentY = WINDOW_SIZE/2;
currentDirection = 0.0;
penDown = 1;
Py_INCREF(Py_None);
return Py_None;
}
static char tortoise_pendown__doc__[] =
""
;
static PyObject *
tortoise_pendown(self, args)
PyObject *self; /* Not used */
PyObject *args;
{
if (!PyArg_ParseTuple(args, ""))
return NULL;
penDown = 1;
Py_INCREF(Py_None);
return Py_None;
}
static char tortoise_penup__doc__[] =
""
;
static PyObject *
tortoise_penup(self, args)
PyObject *self; /* Not used */
PyObject *args;
{
if (!PyArg_ParseTuple(args, ""))
return NULL;
penDown = 0;
Py_INCREF(Py_None);
return Py_None;
}
static char tortoise_turn__doc__[] =
""
;
static PyObject *
tortoise_turn(self, args)
PyObject *self; /* Not used */
PyObject *args;
{
int degrees;
if (!PyArg_ParseTuple(args, "i", °rees))
return NULL;
currentDirection += (double)degrees;
Py_INCREF(Py_None);
return Py_None;
}
static char tortoise_move__doc__[] =
""
;
static PyObject *
tortoise_move(self, args)
PyObject *self; /* Not used */
PyObject *args;
{
int steps;
double newX, newY;
if (!PyArg_ParseTuple(args, "i", &steps))
return NULL;
/* first work out the new endpoint */
newX = currentX + sin(currentDirection*DEGREES_TO_RADIANS)*(double)steps;
newY = currentY - cos(currentDirection*DEGREES_TO_RADIANS)*(double)steps;
/* if the pen is down, draw a line */
if (penDown) XDrawLine(theDisplay, theWindow, theGC,
(int)currentX, (int)currentY, (int)newX, (int)newY);
/* in either case, move the tortoise */
currentX = newX;
currentY = newY;
Py_INCREF(Py_None);
return Py_None;
}
/* List of methods defined in the module */
static struct PyMethodDef tortoise_methods[] = {
{"reset", (PyCFunction)tortoise_reset, METH_VARARGS, tortoise_reset__doc__},
{"pendown", (PyCFunction)tortoise_pendown, METH_VARARGS, tortoise_pendown__do
c__},
{"penup", (PyCFunction)tortoise_penup, METH_VARARGS, tortoise_penup__doc__},
{"turn", (PyCFunction)tortoise_turn, METH_VARARGS, tortoise_turn__doc__},
{"move", (PyCFunction)tortoise_move, METH_VARARGS, tortoise_move__doc__},
{NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */
};
/* Initialization function for the module (*must* be called inittortoise) */
static char tortoise_module_documentation[] =
""
;
void
inittortoise()
{
PyObject *m, *d;
/* Init X and working window */
init_workspace();
/* Create the module and add the functions */
m = Py_InitModule4("tortoise", tortoise_methods,
tortoise_module_documentation,
(PyObject*)NULL,PYTHON_API_VERSION);
/* Add some symbolic constants to the module */
d = PyModule_GetDict(m);
ErrorObject = PyString_FromString("tortoise.error");
PyDict_SetItemString(d, "error", ErrorObject);
/* XXXX Add constants here */
/* Check for errors */
if (PyErr_Occurred())
Py_FatalError("can't initialize module tortoise");
}
/* -------------------------------------------------------------- */
我们在运行一编my_make_shared,好,一切搞定!来试验一下吧?
[root@zhouj extend]# python
Python 1.5.2 (#1, Nov 25 1999, 14:59:15) [GCC 2.95.2 19991024 (release)] on
linux-i386
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> from tortoise import *
>>> dir()
['__builtins__', '__doc__', '__name__', 'error', 'move', 'pendown', 'penup',
'reset', 'turn']
>>> reset()
>>> move(100)
>>> for i in range(4):
... move(100)
... turn(90)
...
>>>
上面的命令在一个窗口里画了条线和一个正方型.而且是你敲入命令它就执行,很听话的
海龟,就是Logo的感觉,嘿嘿.搞定!
我们总结一下写一个python扩展模块的步骤吧:1,用modulator生成程序框架.2,修改框架
,加入真正的功能代码.3,用make_shared或自己写makefile编译成.so动态库文件.4.没了
∶)
上面的程序实在很简单,不过它向我们展示了用python作扩展模块是多么的容易,其实把
python的嵌入就比扩展多一点点东西,如果你感性趣,去看Python自带文档的<<
Extending and Embedding the Python Interpreter >>和python源程序的Demo/embed目
录里的例子吧.其实python自己带的那么多模块也是用相同的方法作出来的,都放在
Modules目录下面,也是非常好的例子.相信我,这些时间的付出将使你获得一个强大,灵活
的工具.
注:
几个月前看了MoneyMaker大虾一片介绍python的文章,从此倘徉在python的奇妙世界,收
益非浅.python是个好东西,一直觉的好东西要大家一起分享:),所以边看无聊的春节晚会
边写了上面这个东东,就作为我送给linux版各位的春节礼物吧,祝各位蛇年顺利,天天开心
! And the same to myself ;-)
--
※ 来源:·BBS 水木清华站 smth.org·[FROM: 166.111.172.246]
BBS水木清华站∶精华区