從C、C++語言編寫的程序中調用Python可以加快編程速度,充分利用Python編程的便捷性。
需要理解的問題:
支持callback函數的庫
Callback在維基上的解釋是:在計算機編程中,一個callback是一段可執行代碼,它作為參數傳遞給其他代碼,以在適當的時候使這段參數代碼被調用執行(call back/execute)。它有同步callback和異步callback二種,取決於其他代碼與callback代碼的調用時間之間的關系。
在C語言中調用Python時,C語言代碼利用callbacks來完成對Python的調用。那么,在C語言的范疇中,Python的等價描述(equivalent)中就需要為Python編程人員提供一種callback的機制;實現中,就需要在C語言的callback代碼中調用Python的callback代碼。Python的兩個特點為上述功能的實現提供了方便:首先,Python的解釋器可以被遞歸得調用;其次,存在調用Python函數的標准接口。
詳細的調用是這樣的,首先Python程序必須以某種方式提供Python函數對象,這里我們可以用一個函數或者接口來完成這個功能。當這個函數或者接口被調用的時候,在全局變量(或其他合適的域)中保存這個函數(或接口)對象的指針。下面是一個例子:
static PyObject *my_callback = NULL; static PyObject * my_set_callback(PyObject *dummy, PyObject *args) { PyObject *result = NULL; PyObject *temp; if(PyArg_ParseTuple(args, "O:set_callback", &temp)) { if(!PyCallable_Check(temp)) { PyErr_SetString(PyExc_TypeError, "parameter must be callable"); return NULL; } Py_XINCREF(temp); //為新生成callback代碼對象增加一個引用 Py_XDECREF(my_callback); //處理my_callback之前引用的對象 my_callback = temp; //用my_callback保存當前的callback代碼引用 // 用於返回None的樣板 Py_INCREF(Py_None); result = Py_None; } return result; }
用於提供函數對象的函數必須在解釋器里注冊,詳細過程如下:
static PyMethodDef SetCallbackMethods[] = { ... { "SetCallback", //方法的名稱 my_set_callback, //指向C實現的指針 METH_VARARGS, //用於指示如何構造調用的比特標志 "set the callback code piece object" //指向相關文檔內容的指針 }, ... {NULL, NULL, 0, NULL} //標記 };
上面代碼中的PyMethodDef是一個結構體(方法表),用於描述Python中擴展類型的方法。主要有4個域
域 | C類型 | 含義 |
ml_name | char * | 方法的名稱 |
ml_method | PyCFunction | 指向C實現的指針 |
ml_flags | int | 用於指示如何構造調用的比特標志 |
ml_doc | char * | 指向相關文檔內容的指針 |
其中,ml_method是一個C函數指針,它所指向的函數全部返回PyObject * 類型的引用(新引用,在Python中可見)。PyCFunction是C語言中用於實現可調用Python模塊的典型函數類型。這類函數有2個PyObject*類型的參數(需要理解Python中self的C類型表示)。ml_flags包含calling或者binding兩種協議,如果是0則表示使用PyArg_ParseTuple()變量(已過時)。PyArg_ParseTuple(PyObject *args, const char *format, ...)用於解析一個函數的參數,並按照位置把參數保存到本地變量中。如果ml_flags是METH_VARARGS,則函數只應該接收Python級(由PyArg_ParseTuple()函數來解析)的參數;如果參數接收關鍵字字典,則METH_KEYWORDS比特位應該置位(METH_VARARGS|METH_KEYWORDS),對應的函數應該有第三個PyOjbect*類型的參數用於接收關鍵字字典(由PyArg_ParseTupleAndKeywords()來解析)。
在Python模塊初始化階段,符號表必須要給到解釋器。初始化函數要命名為initname(),其中name是模塊的名稱,這個函數是模塊文件中唯一的非靜態成員。例如,
PyMODINIT_FUNC initSetCallback(void) { (extern "C") Py_InitModule("SetCallback", SetCallbackMethods); }
當Python程序第一次imports “SetCallback”模塊時,initSetCallback被調用。Py_InitModule()生成一個新的module對象,並在sys.modules 字典中插入以SetCallback為key的元素,根據Py_InitModule的第二個參數(PyMethodDef型數組),將內建的函數對象插入到新模塊中,如果成功,返回指向這個新模塊的指針。
當在C或C++代碼中嵌入Python代碼時,initSetCallback如果在_PyImport_Inittab表中有記錄,就會被自動調用;否則,需要在代碼中直接調用,即initSetCallback(),例如:
int main(int argc, char *argv[]) { // 向解釋器傳遞第一個參數 Py_SetProgramName(argv[0]); // 初始化一個Python解釋器,這個是必需的 Py_Initialize(); // 增加一個靜態的模塊 initSetCallback(); ...... }
關於PyArg_ParseTuple(PyObject *arg, char *format, ...),arg 是一個包含參數列表的tuple對象,由Python傳遞到C函數里,format字符串由0到多個格式單元組成,一個格式單元描述一個Python對象,多個格式單元用括號包圍,逗號分隔。