雖然python開發效率很高,但作為腳本語言,其性能不高,所以為了兼顧開發效率和性能,通常把性能要求高的模塊用c或c++來實現或者在c或c++中運行python腳本來處理邏輯,前者通常是python中一些模塊的實現方式,後者服務端程序(實現業務擴展或是Plugin功能)和游戲開發(腳本只處理邏輯)中比較常見。本文主要介紹通過在c中運行python腳本來實現python與c的相互調用,並通過c和python腳本設置同一段內存區域為例子來講解。
准備工作
為了在c中運行python腳本,需要在程序鏈接的時候將python虛擬機庫鏈接進去,python虛擬機庫是python安裝目錄下libs中的python27.lib文件,至於怎樣將庫鏈接進程序中可以自己google下。由於在c中使用了python的一些方法和數據結構,所以需要將python安裝目錄下的include目錄添加到項目include目錄中。好了,需要准備的就是這些,然後就可以開始實現一個設置內存區域的例子了。
內嵌python虛擬機
在c中內嵌python虛擬機很簡單,只需要在程序開頭include Python.h頭文件,然後調用下面兩段來初始化python虛擬機實例就行了。
Py_SetPythonHome("D:\Python27"); Py_Initialize();
Py_SetPythonHome函數是用來設置python的庫路徑,也就是python安裝路徑,Py_Initialize函數真正實例化一個python虛擬機,這樣就把一個python虛擬機內嵌到c中了。
調用python腳本
將python虛擬機初始化後,其實就可以調用python腳本了。c中調用腳本模塊中的方法分下面幾個步驟:
1、使用PyImport_ImportModule導入腳步模塊;
2、使用PyObject_GetAttrString獲取模塊特定方法信息;
3、使用Py_VaBuildValue轉換輸入參數;
4、使用PyObject_CallObject調用特定方法;
5、使用PyArg_Parse轉換方法的返回結果。
由於上面流程在調用模塊中的方法都是必須的,所以可以寫個函數來封裝上面的5個步驟,具體代碼如下:
int PyModuleRunFunction(const char *module, const char *function, const char *result_format, void *result, const char *args_format, ...) { PyObject *pmodule, *pfunction, *args, *presult; pmodule = PyImport_ImportModule(const_cast<char *>(module)); if (!pmodule) { PyObject *type = PyErr_Occurred(); if (type == PyExc_NameError) { PyErr_Clear(); return 0; } PyError("PyModuleRunFunction"); return -1; } pfunction = PyObject_GetAttrString(pmodule, const_cast<char *>(function)); Py_DECREF(pmodule); if (!pfunction) { PyObject *type = PyErr_Occurred(); if (type == PyExc_AttributeError) { PyErr_Clear(); return 0; } PyError("PyModuleRunFunction"); return -2; } if (pfunction == Py_None) { return 0; } va_list args_list; va_start(args_list, args_format); args = Py_VaBuildValue(const_cast<char *>(args_format), args_list); va_end(args_list); if (!args) { Py_DECREF(pfunction); return -3; } presult = PyObject_CallObject(pfunction, args); if (presult == 0) { PyError("PyModuleRunFunction"); Py_XDECREF(pfunction); Py_XDECREF(args); return -1; } Py_XDECREF(pfunction); Py_XDECREF(args); return ConvertResult(presult, result_format, result); }View Code
有了上面的調用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中通過模塊的方式來解決這個問題。
#define min(a,b) (((a) < (b)) ? (a) : (b)) char data[1024]; void SetData(const char *str) { strncpy(data, str, min(strlen(str) + 1, 1024)); } const char *GetData() { return data; }
在c中定義一個python模塊有特定的步驟,具體代碼如下:
PyDoc_STRVAR(PySetData_doc__, "\ 測試\n \n PySetData(str)\n str: 出入的字符串\n 返回: \n null \n "); static PyObject* PySetData(PyObject *self, PyObject *args) { const char* str = NULL; if ( !PyArg_ParseTuple(args, "s", &str) ) { return 0; } SetData(str); Py_RETURN_NONE; } PyDoc_STRVAR(PyGetData_doc__, "\ 打印數據\n \n PyGetData()\n 返回: \n data \n "); static PyObject* PyGetData(PyObject *self, PyObject *args) { const char* str = NULL; return PyString_FromString(GetData()); } static PyMethodDef module_methods[] = { {"py_set_data", PySetData, METH_VARARGS, PySetData_doc__}, {"py_get_data", PyGetData, METH_VARARGS, PyGetData_doc__}, {NULL} }; void InitCCallPy() { PyObject *module = Py_InitModule3("pycallc", module_methods, "python call c"); }View Code
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導入這個模塊,並調用這個模塊中的函數。具體代碼如下:
# -*- coding: utf-8 -*- import pycallc def test(): print 'in python : ', pycallc.py_get_data() pycallc.py_set_data("change hello world!")
這樣我們就實現了在python腳本中調用c中的方法。
上面完整的代碼demo的鏈接:https://github.com/morningstatus/python/tree/master/ccallpy
總結
從上面c調用python,python調用c,其實都是一些固定的步驟,知道就會用了,沒有會不會的問題,只有想不想知道的問題。沒有接觸這個技術前可能覺得它很高深,但其實只要稍微花點心思去了解它,它也其實沒有這麼難。計算機很多技術不外乎都是這樣,只有你想不想的問題,沒有你會不會的問題,多問,多思考,多學習,總有一天你也能成為技術大牛。
參考
python官方:https://docs.python.org/2/c-api/index.html