1. Python如何使用C编写的模块

  • 本文档通过对一个例子的演化,描述了如何用Python调用C语言编写的模块。例子?你猜对了,还是老套——helloworld!:编写一个简单功能的C语言程序helloworld.c,并实现python调用helloworld.c的函数。

1.1. 纯手工打造

1.1.1. 编写你的C语言模块

  • helloworld.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int fac(int n)
    {
        if (n < 2) return (1);
        return( (n) * fac(n - 1));
    }
    
    int main()
    {
        printf("9! == %d\n", fac(9));
    }
    
    为了保证后续的步骤不受到前面步骤某些错误的影响,我们添加main这样的自我测试函数,以保证我们要包裹的函数已经正确书写。这是编译、运行的结果:
    gcc -o helloworld helloworld.c
    ./helloworld
    9! == 362880
    
    好了,下面我们就为fac函数添加“包裹”代码,让Python能访问它。

1.1.2. 为模块添加Python包裹代码

  • 添加了包裹代码的helloworld2.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int fac(int n)
    {
        if (n < 2) return (1);
        return( (n) * fac(n - 1));
    }
    
    int main()
    {
        printf("9! == %d\n", fac(9));
    }
    
    /* 添加的包裹代码 */
    #include "Python.h"
    static PyObject *hello_fac(PyObject *self, PyObject *args)
    {
            int num;
            if (!PyArg_ParseTuple(args, "i", &num))
                    return NULL;
            return (PyObject *)Py_BuildValue("i", fac(num));
    }
    
    static PyMethodDef helloMethods[] =
    {
            {"fac", hello_fac, METH_VARARGS},
            {NULL, NULL}
    };
    
    void inithello()
    {
            Py_InitModule("hello", helloMethods);
    }
    
    有点晕?其实很简单,一般为c语言模块添加“包裹”代码,分为以下四步:
    1. 加上Python.h头文件
    2. 给模块中的每个函数加上"PyObject * Moudle_func()" Python打包器,每个函数都有一个打包器。

    3. 把每个函数“注册”到"PyMethodDef ModuleMethods[]" 函数名/函数指针的映射表中。

    4. 加上"void initModule()" 模块初始化函数。

    而Python的调用次序正好相反:首先调用一次initMoulde得到函数名/函数指针映射表的函数,然后用这个函数解析函数名返回实际函数指针,最后调用经过“包裹”的函数,而包裹函数在调用相应函数前做Python->C的数据转换,然后调用c函数,最后进行C->Python的数据类型转换,返回给Python。

1.1.3. 编译为Python可用的动态链接库

  • 在2.0版本以前,Python使用一个Makefile.pre.in的脚本+固定格式的Setup文件来编译“包裹”过的C语言模块,从2.0版本以后,就采用了一种新的方式,编写一个py文件(一般命名为setup.py),然后执行python setup.py build就可以生成Python所需的动态链接文件.so
  • setup.py文件
    from distutils.core import setup, Extension^M
    
    moduleHello = Extension('hello',
                        sources = ['helloworld2.c'])
    
    setup (name = 'hello',
           version = '1.0',
           description = 'This is a hello world package',
           ext_modules = [moduleHello])
    
    生成的.so文件会放在build/lib.your_flatform_uname中,只需把它拷贝到Python可以找到的目录下即可,这里我们把它拷到当前目录下。
    fxh@~/simpleshm$python setup2.py build
    running build
    running build_ext
    building 'hello' extension
    cc -fno-strict-aliasing -DNDEBUG -O -pipe -D_THREAD_SAFE -DTHREAD_STACK_SIZE=0x100000 -fPIC -I/usr/local/include/python2.3 -c helloworld2.c -o build/temp.freebsd-4.10-STABLE-i386-2.3/helloworld2.o
    cc -shared -pthread build/temp.freebsd-4.10-STABLE-i386-2.3/helloworld2.o -o build/lib.freebsd-4.10-STABLE-i386-2.3/hello.so
    fxh@~/simpleshm$cp build/lib.freebsd-4.10-STABLE-i386-2.3/hello.so .
    

1.1.4. 试试看吧

  • fxh@~/simpleshm$python
    Python 2.3.4 (#2, Aug  4 2004, 19:17:31)
    [GCC 2.95.4 20020320 [FreeBSD]] on freebsd4
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import hello
    >>> hello.fac(10)
    3628800
    

1.2. 更简单些 - 使用SWIG

  • SWIG是一个代码生成工具:为脚本语言使用C/C++编写的模块生成“包裹”代码。它支持的脚本语言范围相当广泛:CHICKEN C# Guile Java Mzscheme Ocaml Perl PHP Pike Python Ruby Lisp S-Expressions Tcl XML(还有XML?没搞清楚啥概念),嘿嘿,Python位列其中,让我们来试试吧。使用SWIG的5个基本步骤:
  • 根据你要包裹的文件:your_module.c编写SWIG模块定义文件:your_module.i
  • 用SWIG对模块定义文件your_module.i进行预处理,生成your_moudle_wrap.c
  • 编译your_module.c文件和your_module_wrap.c文件
  • 链接上述生成的目标文件,生成动态链接库。
  • 在python即可import该模块,并调用。 我们仍然使用上面的helloworld.c作为例子,这是我们编写的SWIG模块定义文件:helloworld.i
    %module hello
    %{
    %}
    
    extern int fac(int);
    
    很简单,对吗?让我们继续——对helloworld.i进行预处理——SWIG为我们生成了一个可怕的、长达762行的程序:helloworld_wrap.c。
    fxh@~/simpleshm$swig -python helloworld.i
    fxh@~/simpleshm$wc helloworld_wrap.c
         762    2372   20881 helloworld_wrap.c
    
    好了,现在我们只需要编译、链接这两个文件:helloworld.c和helloworld_wrap.c就可以了:
    gcc -c -fpic helloworld.c helloworld_wrap.c -I/usr/local/include/python2.3
    gcc -shared helloworld.o helloworld_wrap.o -o _hello.so
    
    现在我们就可以用hello模块了:
    fxh@~/simpleshm$python
    Python 2.3.4 (#2, Aug  4 2004, 19:17:31)
    [GCC 2.95.4 20020320 [FreeBSD]] on freebsd4
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import hello
    >>> hello.fac(10)
    3628800
    

1.3. 更进一步 - 更复杂的数据类型

  • 从上面的描述看来,“包裹”问题的关键在于Python和C语言的数据类型转换,上面我们演示的例子只演示了单个的、简单的数据类型,对于多个、复杂的数据类型,将随着项目的深入,不断补充。

1.4. 资源


-- samhoo 2004-08-16 18:00:17