雖然python開發效率很高,但做爲腳本語言,其性能不高,因此爲了兼顧開發效率和性能,一般把性能要求高的模塊用c或c++來實現或者在c或c++中運行python腳原本處理邏輯,前者一般是python中一些模塊的實現方式,後者服務端程序(實現業務擴展或是Plugin功能)和遊戲開發(腳本只處理邏輯)中比較常見。本文主要介紹經過在c中運行python腳原本實現python與c的相互調用,並經過c和python腳本設置同一段內存區域爲例子來說解。html
準備工做python
爲了在c中運行python腳本,須要在程序連接的時候將python虛擬機庫連接進去,python虛擬機庫是python安裝目錄下libs中的python27.lib文件,至於怎樣將庫連接進程序中能夠本身google下。因爲在c中使用了python的一些方法和數據結構,因此須要將python安裝目錄下的include目錄添加到項目include目錄中。好了,須要準備的就是這些,而後就能夠開始實現一個設置內存區域的例子了。c++
內嵌python虛擬機git
在c中內嵌python虛擬機很簡單,只須要在程序開頭include Python.h頭文件,而後調用下面兩段來初始化python虛擬機實例就好了。github
1 Py_SetPythonHome("D:\Python27"); 2 Py_Initialize();
Py_SetPythonHome函數是用來設置python的庫路徑,也就是python安裝路徑,Py_Initialize函數真正實例化一個python虛擬機,這樣就把一個python虛擬機內嵌到c中了。api
調用python腳本數據結構
將python虛擬機初始化後,其實就能夠調用python腳本了。c中調用腳本模塊中的方法分下面幾個步驟:ide
一、使用PyImport_ImportModule導入腳步模塊;
函數
二、使用PyObject_GetAttrString獲取模塊特定方法信息;性能
三、使用Py_VaBuildValue轉換輸入參數;
四、使用PyObject_CallObject調用特定方法;
五、使用PyArg_Parse轉換方法的返回結果。
因爲上面流程在調用模塊中的方法都是必須的,因此能夠寫個函數來封裝上面的5個步驟,具體代碼以下:
1 int PyModuleRunFunction(const char *module, const char *function, 2 const char *result_format, void *result, const char *args_format, ...) 3 { 4 5 PyObject *pmodule, *pfunction, *args, *presult; 6 7 pmodule = PyImport_ImportModule(const_cast<char *>(module)); 8 if (!pmodule) 9 { 10 PyObject *type = PyErr_Occurred(); 11 if (type == PyExc_NameError) 12 { 13 PyErr_Clear(); 14 return 0; 15 } 16 17 PyError("PyModuleRunFunction"); 18 return -1; 19 } 20 21 pfunction = PyObject_GetAttrString(pmodule, const_cast<char *>(function)); 22 Py_DECREF(pmodule); 23 if (!pfunction) 24 { 25 PyObject *type = PyErr_Occurred(); 26 if (type == PyExc_AttributeError) 27 { 28 PyErr_Clear(); 29 return 0; 30 } 31 32 PyError("PyModuleRunFunction"); 33 return -2; 34 } 35 36 if (pfunction == Py_None) 37 { 38 return 0; 39 } 40 41 va_list args_list; 42 va_start(args_list, args_format); 43 args = Py_VaBuildValue(const_cast<char *>(args_format), args_list); 44 va_end(args_list); 45 46 if (!args) 47 { 48 Py_DECREF(pfunction); 49 return -3; 50 } 51 52 presult = PyObject_CallObject(pfunction, args); 53 if (presult == 0) 54 { 55 PyError("PyModuleRunFunction"); 56 Py_XDECREF(pfunction); 57 Py_XDECREF(args); 58 return -1; 59 } 60 61 Py_XDECREF(pfunction); 62 Py_XDECREF(args); 63 64 return ConvertResult(presult, result_format, result); 65 }
有了上面的調用python模塊內方法的通用函數,咱們就能夠直接調用python腳本中的方法了,具體以下:
1 PyModuleRunFunction("hello", "test", "", 0, "()");
這樣咱們就實現了再c中調用python的方法。下面咱們再來開心python怎麼調用c中的方法。
初始化c實現的python模塊
爲了能在python腳本中調用到c中定義的方法,須要先在c中定義一個python模塊,而後在腳本中import這個模塊,最後經過這個模塊來間接調用c中定義的方法。例如,咱們經過c定義了一塊內存區域data和對這個內存區域操做的函數SetData與GetData(代碼以下),怎樣在腳本中調用SetData與GetData函數來操做data呢?其實關鍵問題是怎麼樣在腳本中調用SetData和GetData函數,若是能在腳本中調用這兩個函數,天然就能操做data了。python中經過模塊的方式來解決這個問題。
1 #define min(a,b) (((a) < (b)) ? (a) : (b)) 2 3 char data[1024]; 4 5 void SetData(const char *str) 6 { 7 strncpy(data, str, min(strlen(str) + 1, 1024)); 8 } 9 10 const char *GetData() 11 { 12 return data; 13 }
在c中定義一個python模塊有特定的步驟,具體代碼以下:
1 PyDoc_STRVAR(PySetData_doc__, "\ 2 測試\n\ 3 \n\ 4 PySetData(str)\n\ 5 str: 出入的字符串\n\ 6 返回: \n\ 7 null \n\ 8 "); 9 static PyObject* PySetData(PyObject *self, PyObject *args) 10 { 11 const char* str = NULL; 12 if ( !PyArg_ParseTuple(args, "s", &str) ) 13 { 14 return 0; 15 } 16 SetData(str); 17 Py_RETURN_NONE; 18 } 19 20 PyDoc_STRVAR(PyGetData_doc__, "\ 21 打印數據\n\ 22 \n\ 23 PyGetData()\n\ 24 返回: \n\ 25 data \n\ 26 "); 27 static PyObject* PyGetData(PyObject *self, PyObject *args) 28 { 29 const char* str = NULL; 30 return PyString_FromString(GetData()); 31 } 32 33 static PyMethodDef module_methods[] = { 34 {"py_set_data", PySetData, METH_VARARGS, PySetData_doc__}, 35 {"py_get_data", PyGetData, METH_VARARGS, PyGetData_doc__}, 36 {NULL} 37 }; 38 void InitCCallPy() 39 { 40 PyObject *module = Py_InitModule3("pycallc", module_methods, 41 "python call c"); 42 }
Py_InitModule3用來定義一個python模塊,第一個參數是模塊的名字,第二個參數是模塊中的方法描述集合,第三個參數是模塊的描述信息。上面代碼中咱們定義了一個叫pycallc的模塊,方法描述集合module_methods描述了兩個方法py_set_data和py_get_data,這兩個方法對應的函數地址是PySetData和PyGetData,這兩個函數最終會分別調用前面定義的SetData和GetData。這樣咱們在python腳本中經過pycallc模塊的py_set_data和py_get_data方法就能夠設置和獲取data數據了。看了上面的實現,其實這個python模塊的主要做用就是把c中定義的函數再封裝一次,封裝的函數可以被python識別。
在python腳本中調用c實現的python模塊
因爲前面已經經過c代碼初始化了一個python模塊pycallc,那麼在腳本中咱們就能夠經過import導入這個模塊,並調用這個模塊中的函數。具體代碼以下:
1 # -*- coding: utf-8 -*- 2 3 import pycallc 4 5 def test(): 6 print 'in python : ', pycallc.py_get_data() 7 pycallc.py_set_data("change hello world!")
這樣咱們就實現了在python腳本中調用c中的方法。
上面完整的代碼demo的連接:https://github.com/morningstatus/python/tree/master/ccallpy
總結
從上面c調用python,python調用c,其實都是一些固定的步驟,知道就會用了,沒有會不會的問題,只有想不想知道的問題。沒有接觸這個技術前可能以爲它很高深,但其實只要稍微花點心思去了解它,它也其實沒有這麼難。計算機不少技術不外乎都是這樣,只有你想不想的問題,沒有你會不會的問題,多問,多思考,多學習,總有一天你也能成爲技術大牛。
參考