天天看點

C/C++擴充Python與Swig工具

閑話python 48: C/C++擴充Python與Swig工具

python作為一種通用的程式設計語言,一般而言,是能夠滿足邏輯實作的需求的。隻是在日常使用過程中,除了實作一些邏輯之外,至少還有兩個方面的需求是可能需要尋求其他語言幫助的,第一個是提升運作效率,第二個是複用已有C/C++代碼。python比較接近自然語言這一特性确實對使用者而言很不錯,但是這帶來了一個不良後果--運作速度慢。有時還需要借助多程序的方式提升運算速度,但是無論如何,python本身速度慢這件事确實改變不了,也就意味着硬體資源的使用率低。而C/C++的運作速度上的優勢讓我們比較願意尋求某種方式在python中調用C/C++的程式。此外,如果并不是白手起家,而是已有大量C/C++代碼實作的核心功能,那麼使用python重新實作一遍是不劃算的。這時将這些C/C++代碼編譯成python可調用的庫将非常具有吸引力。本文将使用三種方式将一個簡單的C程式邏輯封裝成python中可調用的形式,以展示python使用C/C++擴充功能的一般方式。分别是:動态共享庫調用、C擴充以及swig工具。本文的所有示範在MacOS 10.14.6作業系統中完成,Linux作業系統下應該比較容易複現,Windows可能會比較麻煩一點。

1. 動态共享庫調用

将C/C++代碼編譯成動态共享庫然後在python中調用是一種比較直覺的方式。在MacOS和Linux中是生成字尾為so的檔案,在windows系統成是生成字尾為dll的檔案。

第一步:

首先展示一下這個簡單功能的C程式代碼。

C/C++擴充Python與Swig工具

核心功能函數

第二步:

這裡,我們使用gcc編譯器将源代碼編譯成動态共享庫。檢視編譯産出,發現按照預期生成了so檔案。

C/C++擴充Python與Swig工具

編譯動态共享庫

第三步:

接着,就可以在python中加載生成的so檔案,然後調用對應的功能函數。不過還是有一點需要說明,即資料類型。使用過C/C++的同學都知道,C/C++是強類型的靜态語言,也就是說在執行功能函數的時候,常常必須要保證傳參的資料類型完全一緻,否則程式容易崩潰。python提供了一個庫ctypes來解決調用C/C++程式時的類型問題。通常,我們需要使用ctypes中提供的資料類型和轉換接口,将python中的資料轉換成C/C++程式中的資料類型,然後調用對應的功能函數。複雜的函數參數對于這種調用方式而言是災難性的,是以這種方式一般适用于python與C/C++互動接口簡單的情形。

C/C++擴充Python與Swig工具

python調用動态共享庫

2. C擴充

為了解決上文中調用動态共享庫所面臨的問題,還可以使用C擴充的方式完成在python中調用C/C++程式的需求。這種方式使用了python本身提供的一組C/C++語言互動接口API,這樣所做成的擴充程式并不需要像動态共享庫那樣顯式加載檔案,而是像一般的python子產品那樣導入即可。程式設計風格更加pythonic,能夠實作的互動接口也更加複雜。

第一步:

下面就展示一下使用C擴充的方式需要提供的C程式源代碼。

C/C++擴充Python與Swig工具

C擴充源碼

第二步:

C代碼中除了本次示範功能的核心之外,最主要的就是定義調用接口。正是由于在C代碼中實作了一個複雜解析過程,python中調用過程就可以非常簡單,與一般的python程式調用毫無差别。 C代碼編寫完成後還需要編寫一個對應的setup.py檔案,以便于編譯。下面展示這個setup.py檔案。

C/C++擴充Python與Swig工具

setup.py編譯描述檔案

第三步:

C代碼和setup.py檔案準備完畢之後就可以進行編譯了。下面展示編譯的指令,并顯示編譯産出的檔案。

C/C++擴充Python與Swig工具

編譯C擴充

第四步:

可以看到,編譯産出順利生成,接下來就可以在python中調用了。由于C擴充的so檔案并不在目前的搜尋目錄下,是以需要修改一下sys.path這個變量。除了執行核心的功能函數之外,還可以檢視子產品的文檔,如果需要也可以設定對應的版本号。這樣就與一般的python子產品一緻了,用起來也比較順手。

C/C++擴充Python與Swig工具

python調用C擴充

3. Swig工具

從上文的C擴充可以看出,C/C++的源碼中需要包含python互動的解析,是以顯得非常複雜。而這個過程似乎是形式化的,那麼有沒有什麼工具可以輔助完成這個過程呢?這就是這裡提到的swig工具。當然,好可以用boost中的wrap接口,但是boost本身比較沉重,在釋出和共享時不太友善。

第一步:

下面我們先看一下實作所需功能的C/C++源代碼。

C/C++擴充Python與Swig工具

功能源碼檔案内容

第二步:

然後,需要定義一個字尾為i的描述檔案,用于swig工具處理源代碼。由于本文所示範的功能中需要使用int類型的數組,是以在描述檔案中也添加了一個相關的定義,就可以在python中定義int數組,便于調用。

C/C++擴充Python與Swig工具

swig描述檔案

第三步:

使用swig指令調用上面所編寫的描述檔案,就可以生成針對源碼的wrap接口代碼。從以下的示範可以看出,這條指令生成了swig_example.py和swig_example_wrap.c兩個檔案。

C/C++擴充Python與Swig工具

swig生成接口代碼

第四步:

接下來的步驟就跟上文中的C擴充步驟一緻了,編寫setup.py檔案,然後編譯。下面展示一個詳細一點的setup.py檔案,其中可以指定很多與子產品相關的資訊,便于形成标準的python第三方庫。

C/C++擴充Python與Swig工具

setup.py編譯描述檔案

第五步:

編譯生成對應的so檔案之後,就可以在python程式中調用了。

C/C++擴充Python與Swig工具

編譯swig産生的接口和源碼檔案

第六步:

同樣的,需要先設定以下搜尋路徑,以便于找到該子產品。這裡遇到的一個問題是需要保持python調用C/C++函數的傳入參數與程式中定義的參數類型一緻。由于本文所示範的例子中使用了int類型的指針,那麼在描述檔案中定義的intp就派上了用場。下面示範了參數轉換和調用的過程。從這個調用來看,似乎并不簡單,其原因是參數類型的轉換代碼較多,而且還是隻能服務于單一的資料類型轉換。但這并不是一個大問題。通常在設計C/C++程式時,會提供大量簡單易用的類型轉換的接口,在python中也會封裝一些接口,進而避免在實際調用過程中頻繁編寫代碼進行轉換。在封裝完類型轉換接口之後,其調用形式與C/C++源碼中定義的是一緻的,是以我們并不需要專門地閱讀swig所生成的代碼,隻需要掌握一點swig描述配置即可。