1. 程式人生 > >使用VS2010編寫Python的C擴充套件

使用VS2010編寫Python的C擴充套件

        Python語言的特性具有良好的開發效率,使其在諸多領域備受青睞。然而Python語言執行效率的缺陷,使得他在許多對工程效率要求嚴格的領域的應用受到了限制。但好在Python具有優秀的擴充套件效能(常常被人稱作膠水語言),能通過C\C++、Java等多種語言對其進行擴充套件。這樣便允許我們在工程中使用C語言擴充套件Python核心程式碼,同時也能在Python工程中如同呼叫普通模組一樣對C擴充套件進行呼叫。使得Python工程在保留其特性的前提下同時擁有C的執行效率,聽起來是不是十分美好。接下來簡單介紹使用C通過編寫動態連結庫dll的方法擴充套件Python。(例項環境:Windows7、Visual Studio 2010、Python 2.7.10)

       如何將C語言與Python語言結合在一起呢?那最重要的就是C擴充套件的中的三個部分:初始化函式、匯出函式和方法列表了。下面我們就來一一介紹。

       在VC2010中新建Win32工程PythonDemo,在嚮導中選擇dll,完成。

       首先在PythonDmeo.cpp中載入標頭檔案:

#include Python.h
  與Python相關的函式和巨集都在該標頭檔案中。若在編譯時出現無法找到該檔案或無法找到Python28_d.lib的錯誤,表示還未安裝Python或未設定環境變數,可以在工程中手動新增。具體方法:在專案——屬性中: 1、配置屬性——VC++目錄——包含目錄新增路徑:...\Python2.7.10\include
2、配置屬性——VC++目錄——庫目錄新增路徑:...\Python2.7.1\libs 3、配置屬性——連結器——輸入:加入Python27_d.lib 其他具體問題參見:http://blog.csdn.net/catalyst_zx/article/details/47131533

一、初始化函式:

      初始化函式的作用是對擴充套件模組進行初始化,例項程式碼如下:

//////////////////////////////////////////////////////////////////////////
//函式:擴充套件模組初始化函式
//功能:對擴充套件模組進行初始化
//返回:PyMODINT_FUNC
//////////////////////////////////////////////////////////////////////////
PyMODINIT_FUNC initPythonDemo(void)
{
	Py_InitModule("PythonDemo",PyPythonMethod);
}

        初始化函式的名稱是固定的,必須是init+模組名稱。在Python匯入模組時,首先會在模組中尋找呼叫該函式,對擴充套件模組進行初始化。該函式的返回值為PyMODINT_FUNC,在工程中我們可以看到其定義:

define PyMODINIT_FUNC extern "C" __declspec(dllexport) void

        表示該函式返回值相當於C中的void。

        在初始化函式內部,我們呼叫了Py_InitModule()函式,該函式的作用是將模組名字和方法列表結合起。第一個引數為模組的名稱,第二個引數為方法列表。Python通過方法列表確定在呼叫時呼叫C擴充套件中的哪個函式,具體有關方法列表我們將在後面介紹。Py_InitModule()函式返回值為一個指向該模組的指標,當返回值為NULL時表示初始化失敗。

二、方法列表

       方法列表是一個C的結構體陣列,Python在呼叫模組函式時,通過它找到模組中具體對應要呼叫的函式。

//////////////////////////////////////////////////////////////////////////
//方法列表
//////////////////////////////////////////////////////////////////////////
static PyMethodDef PyPythonMethod[]=
{
	{"PyTest",PyTest,METH_VARARGS,"This function is used for testing !"},
	{NULL,NULL,0,NULL}
};

        第一個欄位為在Python中呼叫時所使用的方法名稱;第二個為該方法的匯出函式,即實際呼叫的函式;第三個表示引數傳遞的模式,可選的兩種方式是METH_VARARGS和METH_KEYWORDS,其中METH_VARARGS是引數傳遞的標準形式,它通過Python的元組在Python直譯器和C函式之間傳遞引數,若採用METH_KEYWORD方式,則Python直譯器和C函式之間將通過Python的字典型別在兩者之間進行引數傳遞;第四個引數為對該函式的說明,在python裡來help這個函式,將顯示這個說明。

      方法列表始終以{NULL,NULL,0,NULL}結尾。

三、匯出函式:

          匯出函式也稱為包裹函式,是Python與C互動的關鍵。其作用是:1、將Python傳入的引數轉換為C\C++格式。2、將轉換好的引數傳給C擴充套件中的函式,並接受返回值。3、將返回值轉換為Python格式輸出。

          例如在C擴充套件模組中,我們有一個函式:

//////////////////////////////////////////////////////////////////////////
//擴充套件模組測試函式
//功能:測試擴充套件模組
//輸入:int i
//輸出:(int)i+1
//////////////////////////////////////////////////////////////////////////
int PyTest(int i)
{
	return i+1;
}

        則可以編寫匯出函式:

//////////////////////////////////////////////////////////////////////////
//測試函式匯出函式
//功能:匯出測試函式
//輸入:PyObject
//輸出:PyObject*
//////////////////////////////////////////////////////////////////////////
static PyObject *PyTest(PyObject *self,PyObject *args)
{
	int i;
	if(!PyArg_ParseTuple(args,"i",&i))
		return NULL;
	int k=PyTest(i);
	return Py_BuildValue("i",k);
}

        在C\C++中,Python中的物件統一都用PyObject類來表示。匯出函式有兩個引數第一個self一般為空,或為一個初始化的指向模組的指標(見Py_InitModule);第二冊引數是一個指向Python傳入引數元組\字典的指標。在匯出函式中我們使用PyArg_ParseTuple對Python傳入的引數元組args進行解析。

        PyArg_ParseTuple的第一個引數為我們要轉換的引數;第二個格式轉換符號,i表示int,s表示string,不同引數的轉化符號紙間用|隔開,入i|i表示兩個引數均為int型;第三個引數是轉換後引數實際存放的位置。

        在呼叫測試函式獲得返回值k後,我們在返回時通過Py_BuildValue函式將C格式的引數k轉換為Python格式返回。Py_BuildValue的第一個引數為格式轉換符號,第二個為要轉換的變數。

        如果對應的C函式沒有返回值(即返回值型別為void),則應返回一個全域性的None物件(Py_None),並將其引用計數增,如下所示:

Py_INCREF(Py_None);
returnPy_None;

四、完成並測試

        在release下編譯,將得到的dll複製到Python安裝目錄或工程目錄下,並將其副檔名改為.pyd。

        在Python IDLE中進行測試:

>>> import PythonDemo
>>> PythonDemo.PyTest (1)
2
>>>

五、疑難解決

       使用VS2010編寫的dll在Python呼叫總會莫名的出現”無初始化函式“的錯誤(實際上編寫時有初始化函式),百思不得其解,最後歷盡千辛萬苦終於在網上找到的解決方案http://m.blog.csdn.net/blog/kosl90/6618367

        即在VC2010連結器命令列中需要對初始化函式進行匯出,具體方法:配置屬性>>聯結器>>命令列>>其他選項中新增”/export:模組初始化方法名”。

       至於為何不匯出,會在Python匯入模組時出現模組為定義初始化方法的錯誤,具體原因仍不詳。

六、參考文獻: