使用ctypes來擴充套件Python

felix021發表於2019-05-11

為了擴充套件Python,我們可以用C/C++編寫模組,但是這要求對Python的底層有足夠的瞭解,包括Python物件模型、常用模組、引用計數等,門檻較高,且不方便利用現有的C庫。而 ctypes 則另闢蹊徑,通過封裝dlopen/dlsym之類的函式,並提供對C中資料結構的包裝/解包,讓Python能夠載入動態庫、匯出其中的函式直接加以利用。

快速上手

最簡單的,我們可以從 libc 開始:

felix021@ubserver:~/code$ python
Python 2.7.3 (default, Jul  5 2013, 08:39:51) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes
>>> libc = ctypes.CDLL("libc.so.6")
>>> libc.printf("hello, %s! %d + %d = %d
", "world", 1, 2, 3)
hello, world! 1 + 2 = 3
25

由於ctypes庫能夠直接處理 None, integers, longs, byte strings 和 unicode strings 型別的轉換,因此在這裡不需要任何額外的操作就能完成任務。(注意,最後一行的25是printf的返回值,標識輸出了25個字元)

自己動手

這裡有一個最簡單的例子——實現一個 int sum(int a, int b)

//file foo.c
#ifdef __WIN32
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT
#endif

DLLEXPORT int sum(int a, int b)
{
    return a + b;
}

編譯之得到 foo.so:

$ gcc -fPIC -shared -o foo.so foo.c

然後在Python裡頭:

>>> foo = ctypes.CDLL("./foo.so")
>>> foo.sum(5, 3)
8

真是超級簡單。

豐衣足食

下面來個稍微複雜點的例子:反轉一個字串。儘管ctypes可以直接處理string的轉換,但是你不能直接修改string裡的內容,所以這裡需要變通一下。可能的解決方案有兩個:

  1. 在foo.c裡面malloc一點空間然後返回。這樣可以不修改原string,但是卻要考慮釋放該空間的問題,不太好處理。
  2. 讓Python分配好空間,將原字串拷貝進去,再讓foo裡面的函式來修改它。ctypes為這種方案提供了create_string_buffer這個函式,正適合。

那就讓我們用方案2來實現它:

//file foo.c
DLLEXPORT int reverse(char *str)
{
    int i, j, t;
    for (i = 0, j = strlen(str) - 1; i < j; i++, j--)
        t = str[i], str[i] = str[j], str[j] = t;
    return 0;
}

然後在Python裡頭:

>>> s = ctypes.create_string_buffer("hello world!")
>>> foo.reverse(s)
0
>>> print s.value
!dlrow olleh

這樣就OK啦。

補充說明

以上的操作都是在Linux下完成的,在Windows下基本上一致,除了windows不能直接load libc.so.6,而是應該找 msvcrt :

>>> libc = ctypes.cdll.msvcrt

或者直接匯入檔案

>>> libc = ctypes.CDLL("msvcr90.dll")

小結

前面演示了ctypes庫的簡單使用,已經能夠完成許多工了;但是它可以完成更多的任務,包括操作更復雜的例如結構體、陣列等C資料結構,具體的這裡不細說了,可以參考詳細的官方文件

好了,就到這裡。

相關文章