天天看點

使用C語言擴充Python(一)

開發環境:Ubuntu9.10,python2.6,gcc4.4.1

1,ubuntu下的python運作包和開發包是分開的,是以需要在新利得裡面安裝python-all-dev,進而可以在代碼中引用python的頭檔案和庫。

2.下面是一個最簡單的可以供python調用的c擴充子產品,假設c程式檔案名為foo.c:

複制代碼

代碼

#include <Python.h>

static PyObject* foo_bar(PyObject* self, PyObject* args) {

    Py_RETURN_NONE;

}

static PyMethodDef foo_methods[] = {

    {"bar",(PyCFunction)foo_bar,METH_NOARGS,NULL},

    {NULL,NULL,0,NULL}

};

PyMODINIT_FUNC initfoo() {

    Py_InitModule3("foo", foo_methods, "My first extension module.");

     我們可以将上述子產品分成3個部分:1)c子產品想對外暴露的接口函數。2)提供給外部的python程式使用的一個c子產品函數名稱映射表。3)c子產品的初始化函數。子產品的第一行将Python.h引入到子產品中,這個檔案将使得你的子產品可以hook進python的解釋器,進而可以為外部的python程式所使用。

c子產品中的函數簽名一般有下列三種形式:

PyObject* MyFunction(PyObject* self, PyObject* args);

PyObject* MyFunctionWithKeywords(PyObject* self, PyObject* args, PyObject* kw);

PyObject* MyFunctionWithNoArgs(PyObject* self);

      一般我們使用的是第一種方式,函數的參數将會一個元組(tuple)的形式傳進來,是以我們在c子產品的函數中需要對其進行解析。Python中不能象c語言一樣聲明一個void類型的函數,如果你不想函數傳回一個值的話,那就傳回一個NONE,在這裡我們可以通過Python頭檔案中的一個宏Py_RETURN_NONE來實作。

C子產品中的函數名稱其實對外部來說是不可見的,是以可以随便你命名,一般我們可以使用static函數(這在C語言裡表示在目前檔案以外是不可見的)。本文函數命名方式采用子產品名加上函數名,例如foo_bar,這表示在子產品foo中會有一個bar函數。然後就是函數映射表了,它是一個PyMethodDef結構體數組,

struct PyMethodDef {

    char* ml_name;

    PyCFunction ml_meth;

    int ml_flags;

    char* ml_doc;

      第一個成員ml_name是函數名,當我們在外部的Python代碼中使用此子產品時利用這個名稱進行函數調用。ml_meth是函數位址。ml_flags告訴解釋器ml_meth将會使用上述三種方法簽名的哪一種,一般設定為METH_VARARGS,如果你想允許關鍵字參數,則可以将其與METH_KEYWORDS進行或運算。若不想接受任何參數,則可以将其設定為METH_NOARGS.最後,ml_doc字段是函數的注釋文檔資訊,最好還是寫幾句吧,不然會被鄙視的。。。另外,這個表必須以{NULL,NULL,0,NULL}這樣一條空記錄結尾。

    子產品的初始化函數是在子產品被加載時被Python解釋器所調用的,如果你的子產品名為foo,則要求命名為initfoo.Py_InitModule3函數一般用來定義一個子產品。

3,現在我們來将foo.c檔案編譯為一個擴充子產品,使用下述指令進行編譯: 

gcc -shared -I /usr/include/python2.6 foo.c -o foo.so

注意shared object的名稱必須和傳給Py_InitModule3函數的字元串一緻,另一種可選的方式是加上module字尾,是以上述foo子產品可以命名為foo.so或foomodule.so。

4,上面的編譯方式可以完成任務,但更好的生成擴充子產品的方法是使用distutils。首先寫一個setup.py腳本:

from distutils.core import setup, Extension

setup(name = 'foo', version = '1.0', ext_modules = [Extension('foo', ['foo.c'])])

然後執行下述指令進行build: 

python ./setup.py build

這會在目前目錄下生成一個build子目錄,其中包含了中間生成的foo.o以及最後生成出來的foo.so。當然,最簡單的方法是使用下述指令進行子產品的生成和安裝:

python ./setup.py install

注:由于需要獲得dist-packages的寫權限,最好先切換到root使用者,如果直接使用su切換出現下面的錯誤:

su: Authentication failure

則為root使用者設定一個新密碼:

sudo passwd root

再用新密碼切換到root使用者。檢視build時的詳細情況,我們可以發現這麼一句:

copying build/lib.linux-i686-2.6/foo.so -> /usr/local/lib/python2.6/dist-packages

 這是将生成的子產品拷貝到/usr/local/lib/python2.6/dist-packages下了,這樣就将我們的foo子產品安裝到系統中了,我們可以驗證如下,在python指令行中,

import foo

dir(foo)

 結果如下: 

['__doc__','__file__','__name__','__package__','bar']

呵呵,不錯吧,這個foo子產品現在已經和其他系統子產品一樣了,原因就在于dist-packages是在sys.path這個路徑中的,

5,現在我們手上已經有一個生成并安裝好的C擴充子產品了,剩下的就是在python代碼中引入這個新子產品,并調用它的方法

foo.bar()

當然,由于在c子產品中的bar函數裡,我們目前什麼都還沒做,是以現在啥都沒有,在下一篇中我們實作:1)從python腳本裡向C子產品中傳遞參數。2)從C子產品中傳回值給外部的Python腳本

夜已經深了,這個python和c/c++,java相結合系列的第一篇就暫時寫到這裡。。。

本文轉自Phinecos(洞庭散人)部落格園部落格,原文連結:http://www.cnblogs.com/phinecos/archive/2010/05/17/1737033.html,如需轉載請自行聯系原作者