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", &degrees)) 
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", &degrees)) 
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水木清华站∶精华区