python 與 c/c++ 的交互實踐教程

專案需要用到 python 與 c/c++ 程序交互,網上有推薦swig但不符合專案需求,琢磨了 python 的 ctypes 模塊感覺不錯,這裏記錄下來做備用,同時也給廣大 python with c/c++ 派留給方便。

測試環境: win 8.1, Visual Studio 2010, Python 3.5

一、介紹
python 與 c/c++ 交互的主要目的一是爲了速度,二大概就是用做腳本了。
說是 python 與 c/c++ 交互,但實際上是 python 與 c 交互, 因爲 python 本身只支持 C API。但是我們可以通過調整達到 python 與 c++ 工程協作的目的。下面主要說明 python 使用 ctypes 模塊與 c 交互的要點和疑難點。

二、使用 ctypes 可以做到什麽?
python 可以通過使用 ctypes 模塊調用 c 函數,這其中必定包括可以定義 c 的變量類型(包括結構體類型、指針類型)。
官方給的定義是 “ctypes is a foreign function library for Python. It provides C compatible data types, and allows calling functions in DLLs or shared libraries. It can be used to wrap these libraries in pure Python.” —— 引自 Python 3.5 chm 文檔。其大意就是——ctypes 是一個爲 Python 准備的外部函數庫。它提供兼容C的數據類型,並允許調用DLL或共享庫中的函數。通過它,可以使用純粹的 Python 包裝這些函數庫(這樣你就可以直接 import xxx 來使用這些函數庫了)。

口說無憑,我們需要一個具體的例子,下面我們引入一個 cpp 文件來說明以下所有問題:
現有 test.cpp 文件如下:
#if 1
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
#include <stdio.h>
#include <stdlib.h>
// Point 結構體
struct Point
{
float x, y;
};
static Point* position = NULL;
extern "C" {
DLL_API int add(int a, int b)
{
return a + b;
}
DLL_API float addf(float a, float b)
{
return a + b;
}
DLL_API void print_point(Point* p)
{
if (p)
printf("position x %f y %f", p->x, p->y);
}
}
可以看見這裏有三個函數,包括一個形參帶指針的函數。學會用 Python 成功調用上面的三個函數就是我的本文的目標了。對于windows平台把他生成爲 dll 文件就行(其他平台爲 .so)。下面我們在解釋器中寫出出測試用的 Python 代碼。


三、ctypes 怎麽樣調用 c 的函數庫?
首先,需要 ctypes 載入需要被調用的函數庫(廢話)。
使用 ctypes.CDLL ,其定義如下(引自 Python 3.5 chm 文檔 )
ctypes.CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)
另外,在 windows 平台上會忽略 modes 參數。對于 windows 平台來說還可以調用 ctypes.WinDLL,與上面的 CDLL 幾乎一樣,唯一不同點是它假定庫中函數遵循 Windows stdcall 調用約定,其他參數的意義見官方文檔。
如果要調用 test.dll 中的 add 函數可以寫作 :
>>> from ctypes import *
>>> dll = CDLL(“test.dll”) # 調用 test.dll
>>> dll.add(10, 30) # 調用 add 函數
40
可以看見返回了 40,是不是很簡單?。這是就是我們預期的結果。下面我們再調用 addf 這是 add 的 float 版本,有些人可能會問爲什麽不直接寫 DLL_API float add(float a, float b) ? 用函數的重載就好了,爲什麽不這麽做?注意,我們使用了 extern“C”聲明函數,所以不支持函數的重載。
接下來我們調用 addf , 猜猜會發生什麽?
>>> dll.addf(10, 30)
9108284
哦,這是不是有點出乎你的意料?爲什麽會這樣?


四、c 類型與 Python 類型, 參數類型、返回類型
之所以會調用 addf 函數“失敗”倒不是 Python 出了問題。原因是你沒有“告訴” Python 這個函數的“容貌”(更正式的說法是“描述”)——函數的形參類型和返回類型。那麽爲什麽我們調用 add 成功了呢?因爲 Python 默認函數的參數類型和返回類型爲 int 型。理所當然地 Python 以爲 addf 返回了一個 int 類型的值。
也就是說,在 ctypes 讀取 dll 時只知道存在這個函數,但是並不知到函數的形參類型和返回值的類型。你可能會疑惑爲什麽 Python 這麽麻煩,還要告訴它共享庫中函數的“容貌”。這就不能怪它了,事實上,就是 Microsoft 自己開發的 C# 語言在調用 dll 的時候都需要告訴 C# 這個函數是什麽樣子的。這解釋起來有點煩,還是來專注于我們對 ctypes 用法的研究吧。
那麽,對于 Python 來說 c 的類型都有哪些呢?下面就是一張 Python 中的類型對應 c 類型的表(截圖自 Python 3.5 chm 文檔)
python 與 c/c++ 的交互實踐教程

然後,怎麽告訴 Python 一個外來函數的形參類型和返回的值的類型呢?
這就要需要給函數的兩個屬性 restype 和 argtypes 賦值了。它們分別對應返回類型和參數類型。對于 addf 它的返回值類型是 float, 對應到 Python 裏就是 c_float。下面我們進行賦值:
>>> dll.addf.restype = c_float # addf 返回值的類型是 flaot
如果函數的返回值是 void 那麽你可以賦值爲 None。另外,在不是太低的版本中,可以使用 Python 內置類型(上表中最右邊的一列)“描述”庫函數的返回類型,但是,不可以用 Python 內置類型來描述庫函數的參數。
由于函數的參數不是固定的數量,所以需要使用列表或者是元組來說明:
>>> dll.addf.argtypes = (c_float, c_float) # addf 有兩個形參,都是 float 類型
或者是下面這樣,但是,你知道的,查找元組的效率略高:)
>>> dll.addf.argtypes = [c_float, c_float] # addf 有兩個形參,都是 float 類型
該做的都做完了,現在再來調用 addf:
>>> dll.addf(8, 3)
11.0
>>> dll.addf(8.3, 3.1)
11.399999618530273
這就是我們想要的結果。


五、更多地關于 ctypes 類型的創建和使用
我們也可以創建一個 ctypes 的類型(c_int、c_float、c_char……)並給他賦值,例子如下:
>>> i = c_int(45) # 定義一個 int 型變量,值爲 45
>>> i.value # 列印變量的值
45
>>> i.value = 56 # 改變該變量的值爲 56
>>> i.value # 列印變量的新值
56
沒錯,你要通過 ctypes 的 value 屬性給一個 ctypes 類型賦值——賦一個 Python 內置類型的值。
其他的 ctypes 的函數,如 sizeof(i)(是不是感覺很貼心就像 c 一樣),就不一一介紹了。自行參見文獻第三條和官方文檔吧。


六、結構體、共用體

這是調用 print_point 庫函數的必要成分之一。
如果要在 Python 中定義一個 c 類型的結構體,需要定義一個類,例如 Structu Point 就這麽做:
>>> class Point(Structure):
... _fields_ = [("x", c_float), ("y", c_float)]
...
>>>
這就定義好了。其中有兩個要點:
1. 類必須繼承自 ctypes.Structure
2. 描述這個結構體的“容貌”
第一點很簡單, class XXX(Structure) 就 OK。
要做到第二點,則必須在自定義的 c 結構體類中定義一個名爲 _fields_ 的屬性,並賦值給如上的一個列表。
然後就可以這樣使用了:
>>> p = Point(2,5) # 定義一個 Point 類型的變量,初始值爲 x=2, y=5 也可以直接寫 p = Point()
>>> p.y = 3 # 修改值
>>> print (p.x, p.y) # 列印變量
2 3
而對于共用體只要類繼承自 ctypes.Union 就成,其他與上面相同。


七、指針
這就是最後一節了,雖然是指針,不過別緊張,且聽我娓娓道來。
如何創建一個 ctypes 的指針呢?這裏有三個跟指針有個的 ctypes 裏的函數,掌握了他們你自然就會了(可能 pointer POINTER 會有點繞,仔細看看就好)。
函數 說明
byref(x [, offset]) 返回 x 的地址,x 必須爲 ctypes 類型的一個實例。相當于 c 的 &x 。 offset 表示偏移量。
pointer(x) 創建並返回一個指向 x 的指針實例, x 是一個實例對象。
POINTER(type) 返回一個類型,這個類型是指向 type 類型的指針類型, type 是 ctypes 的一個類型。
byref 很好理解,傳遞參數的時候就用這個,用 pointer 創建一個指針變量也行,不過 byref 更快。
而 pointer 和 POINTER 的區別是,pointer 返回一個實例,POINTER 返回一個類型。甚至你可以用 POINTER 來做 pointer 的工作:
>>> a = c_int(66) # 創建一個 c_int 實例
>>> b = pointer(a) # 創建指針
>>> c = POINTER(c_int)(a) # 創建指針
>>> b
<__main__.LP_c_long object at 0x00E12AD0>
>>> c
<__main__.LP_c_long object at 0x00E12B20>
>>> b.contents # 輸出 a 的值
c_long(66)
>>> c.contents # 輸出 a 的值
c_long(66)
pointer 創建的指針貌似沒方法修改指向的 ctypes 類型值。
該說的都說了,接下來就要調用 print_point 函數了:
>>> dll.print_point.argtypes = (POINTER(Point),) # 指明函數的參數類型
>>> dll.print_point.restype = None # 指明函數的返回類型
>>>
>>> p = Point(32.4, -92.1) # 實例化一個 Point
>>> dll.print_point(byref(p)) # 調用函數
position x 32.400002 y -92.099998>>>
當然你非要用慢一點的 pointer 也行:
>>> dll.print_point(pointer(p)) # 調用函數
position x 32.400002 y -92.099998>>>
得到的結果就是我們想要的 :)
至于爲什麽輸出的後面出現了畸形 “y -92.099998>>>” ,去翻一翻上面的 c 代碼你就知道了。


參考文獻
更多關于 ctypes 類型的用法可以參加下面的書籍、文檔和網頁:
1. 《Python參考手冊》
2. Python 3.5 官方文檔 “python350.chm”
3. http://www.ibm.com/developerworks/cn/linux/l-cn-pythonandc/

更多相關文章
  • Celery是Python開發的分布式任務調度模塊,本文我們深入學習一下 celery 模塊的配置,感興趣的朋友可以參考一下.celery的官方文檔其實相對還是寫的很不錯的.但是在一些深層次的使用上面卻顯得雜亂甚至就
  • 本文分享一個js與mvc數據的交互實例教程,js向後台提交數據的方法一般是get傳值和post傳值,數據格式兼容比較強的是json串.在做考試系統中,遇到最頭疼的問題,就是前台與後台的交互.對于使用easyui來說,剛接觸不久,有點陌生.查著文檔,看著Demo.做起來著實有點頭疼.首先,前台使用的是 ...
  • BCP導出導入 SQL SERVER 大容量數據實踐教程
    BCP是SYBASE公司提供專門用于數據庫表一級數據備份的工具.本文我們講講用BCP導出導入大容量SQL SERVER數據的實踐教程本教程我們介紹大容量數據導出導入的利器--BCP實用工具.同時在後面也介紹BULK INSERT導入大容量數據,以及BCP結合BULK INSERT做數據接口的實踐(在
  • 因爲ezdpl不是開箱即用的,得根據自己的應用環境定制,所以對初次使用困難很多,本文的重點是將 ezdpl Linux自動化部署步驟分享出來.申明:ezdpl不是開箱即用的,需要根據自己的應用環境定制.對初學者來說使用起來反倒困難更多.風險更大.它不是一個通用的專案,更多的是提供一種思路,將繁瑣的操
  • 本文我們來講講iOS系統上通用的本地Objective-C代碼與Javascript互操作的基本方法,本文還涉及到html5的應用,這裏把實現方法簡單介紹.專案中要用到html5來實現,涉及到Objective-C調用JS,以及JS調用Objective-C的方法,這裏把遇到的問題以及實現方法介紹一 ...
  • Lua 是一個小巧的腳本語言,Lua腳本可以很容易的被C/C++ 代碼調用,也可以反過來調用C/C++的函數,本文我們講講爲 lua 配一個合適的記憶體分配器的一些想法及實踐教程.爲 lua 配一個合適的記憶體分配器
  • FLEX與JAVA交互實例代碼與詳細說明下載FLEX的插件step 1.下載 flex 3.0 plugin 插件 裝在 我的myeclips教程e 6.0 上 1) 下載地址:http://trials.adobe.com/Applications/Flex/FlexBuilder/3/FB3_W ...
  • Python的異常處理能力是很強大的,可向用戶准確反饋出錯信息.在Python中,異常也是對象,可對它進行操作.所有異常都是基類Exception的成員.Python 異常處理 python提供了兩個非常重要的功能來處理python程序在運行中出現的異常和錯誤.你可以使用該功能來調試python程序 ...
一周排行
  • 在使用php curl獲取網頁內容有些網站提示405 method not allowed狀態碼了,這個問題我分析了出問題的網站是因爲ipv6而導致了,所以我們只要簡單的調整一下就可以解決這個問題了,下面一起來看看吧
  • 本文章來給大家介紹cURL函數庫錯誤碼說明之PHP curl_errno函數,有需要了解的朋友不防進入參考.背景概述:遊戲接口是使用PHP cURL擴展進行請求操作.但是,被請求的伺服器經常會無故的不回應或者超時.總 ...
  • apache rewrite_module 僞靜態與 order allow,deny 封ip一 打開 apache 的配置文件 httpd.conf .二 將#loadmodule rewrite_module m
  • 班主任是一個班集體的組織者.教育者,是班級各項工作的協調者,是連接學校的紐帶,是培養學生成爲有用人才的引路人.一年級的小學生,經過半學期的教育,雖說行爲習慣和學習習慣都有一些基礎,但養成教育依然是教育管理的重中之重. ...
  • 免費500m/100g/php教程/mysql教程/ftp/subdomain/domain國外空間免費空間 PHP與MySQL的免費虛擬主機印度尼西亞 磁盤空間:200 MB 每月帶寬:500 MB 網站托管:1網
  • 在mysql中鎖表是一個比較有學問的東西了,下面我來給各位介紹一下MySQL的lock tables和unlock tables使用方法.lock tables 命令是爲當前線程鎖定表.這裏有2種類型的鎖定,一種是讀 ...
  • jquery中(function($){...})(jQuery)是什麽大家可參考本文章.這裏實際上是匿名函數function(arg){...}這就定義了一個匿名函數,參數爲arg而調用函數 時,是在函數後面寫上括 ...
  • 文章簡單的介紹了關于oracle 的instr函數的用法介紹,這個相當于sql中的查找函數,有需要的朋友可以參考一下.語法 代碼如下 instr( string1, string2 [, start_position
  • c/s模式遠程有點像伺服器與客戶端一樣的,我們下面利用python來做一個簡單的例子,有興趣的和小編來學學.實現目標:通過控制端,可以實現N台主機執行同一操作.具體代碼如下:1.控制端代碼[[email protected] t
  • 解決U盾進行轉賬、B2C等交易時頁面無回應或不彈出證書密碼輸入框問題
    本文章爲各位介紹在使用U盾進行轉賬.B2C等交易時頁面無回應或不彈出證書密碼輸入框問題解決 ...