python與c互相調用

  雖然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 }
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中經過模塊的方式來解決這個問題。

 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 }
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導入這個模塊,並調用這個模塊中的函數。具體代碼以下:

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,其實都是一些固定的步驟,知道就會用了,沒有會不會的問題,只有想不想知道的問題。沒有接觸這個技術前可能以爲它很高深,但其實只要稍微花點心思去了解它,它也其實沒有這麼難。計算機不少技術不外乎都是這樣,只有你想不想的問題,沒有你會不會的問題,多問,多思考,多學習,總有一天你也能成爲技術大牛。

 

參考

  python官方:https://docs.python.org/2/c-api/index.html

相關文章
相關標籤/搜索