環境
VS2005Python2.5.4 Windows XP SP3
簡述
一般開發過游戲的都知道Lua和C++可以很好的結合在一起,取長補短,把Lua腳本當成類似動態鏈接庫來使用,很好的利用了腳本開發的靈活性。而作為一門流行的通用型腳本語言python,也是可以做到的。在一個C++應用程序中,我們可以用一組插件來實現一些具有統一接口的功能,一般插件都是使用動態鏈接庫實現,如果插件的變化比較頻繁,我們可以使用Python來代替動態鏈接庫形式的插件(堪稱文本形式的動態鏈接庫),這樣可以方便地根據需求的變化改寫腳本代碼,而不是必須重新編譯鏈接二進制的動態鏈接庫。靈活性大大的提高了。
Python/CAPI簡介
通過C++調用Python腳本主要要用到如下的一些Python提供的API,因為實際上C++要調用的是Python的解釋器,而Python解釋器本質就是實現在動態鏈接庫里面的,因此在調用前和調用后要進行一些初始化和資源釋放的工作,另外,要調用Python腳本里面的函數等等東西,需要Python提供的一些特殊API來包裝C++調用。(可以參考[2])。
void Py_Initialize(void)
初始化Python解釋器,如果初始化失敗,繼續下面的調用會出現各種錯誤,可惜的是此函數沒有返回值來判斷是否初始化成功,如果失敗會導致致命錯誤。
int Py_IsInitialized(void)
檢查是否已經進行了初始化,如果返回0,表示沒有進行過初始化。
void Py_Finalize()
反初始化Python解釋器,包括子解釋器,調用此函數同時會釋放Python解釋器所占用的資源。
int PyRun_SimpleString(const char *command)
實際上是一個宏,執行一段Python代碼。
PyObject* PyImport_ImportModule(char *name)
導入一個Python模塊,參數name可以是*.py文件的文件名。類似Python內建函數import。
PyObject* PyModule_GetDict( PyObject *module)
相當於Python模塊對象的__dict__屬性,得到模塊名稱空間下的字典對象。
PyObject* PyRun_String(const char* str, int start,PyObject* globals, PyObject* locals)
執行一段Python代碼。
int PyArg_Parse(PyObject* args, char* format, ...)
把Python數據類型解析為C的類型,這樣C程序中才可以使用Python里面的數據。
PyObject* PyObject_GetAttrString(PyObject *o, char*attr_name)
返回模塊對象o中的attr_name 屬性或函數,相當於Python中表達式語句,o.attr_name。
PyObject* Py_BuildValue(char* format, ...)
和PyArg_Parse剛好相反,構建一個參數列表,把C類型轉換為Python對象,使得Python里面可以使用C類型數據。
PyObject* PyEval_CallObject(PyObject* pfunc, PyObject*pargs)
此函數有兩個參數,而且都是Python對象指針,其中pfunc是要調用的Python 函數,一般說來可以使用PyObject_GetAttrString()獲得,pargs是函數的參數列表,通常是使用Py_BuildValue()來構建。
更多的API請參考官方的文檔,比較直觀簡單,譬如怎樣初始化一個類實例,怎樣調用類成員函數。下面上點代碼,感受下這個過程。
C++代碼
#include "stdafx.h"
#include "Python.h"
int _tmain(int argc, _TCHAR* argv[])
{
int nRet = -1;
PyObject* pName = NULL;
PyObject* pModule =NULL;
PyObject* pDict = NULL;
PyObject* pFunc = NULL;
PyObject* pArgs = NULL;
PyObject* pRet = NULL;
do
{
// 初始化Python
// 在使用Python系統前,必須使用Py_Initialize對其
// 進行初始化。它會載入Python的內建模塊並添加系統路
// 徑到模塊搜索路徑中。這個函數沒有返回值,檢查系統
// 是否初始化成功需要使用Py_IsInitialized。
Py_Initialize();
// 檢查初始化是否成功
if (!Py_IsInitialized())
{
break;
}
// 添加當前路徑
// 把輸入的字符串作為Python代碼直接運行,返回
// 表示成功,-1表示有錯。大多時候錯誤都是因為字符串
// 中有語法錯誤。
PyRun_SimpleString("importsys");
PyRun_SimpleString("sys.path.append('./')");
// 載入名為PyPlugin的腳本
pName = PyString_FromString("PyPlugin");
pModule = PyImport_Import(pName);
if (!pModule)
{
printf("can't findPyPlugin.py\n");
break;
}
pDict = PyModule_GetDict(pModule);
if (!pDict)
{
break;
}
// 找出函數名為AddMult的函數
pFunc = PyDict_GetItemString(pDict, "AddMult");
if (!pFunc || !PyCallable_Check(pFunc))
{
printf("can't findfunction [AddMult]\n");
break;
}
pArgs = Py_BuildValue("ii", 12, 14);
PyObject* pRet = PyEval_CallObject(pFunc,pArgs);
int a = 0;
int b = 0;
if (pRet && PyArg_ParseTuple(pRet,"ii", &a,&b))
{
printf("Function[AddMult] call successful a + b = %d, a * b = %d\n", a, b);
nRet = 0;
}
if (pArgs)
Py_DECREF(pArgs);
if (pFunc)
Py_DECREF(pFunc);
// 找出函數名為HelloWorld的函數
pFunc = PyDict_GetItemString(pDict, "HelloWorld");
if (!pFunc || !PyCallable_Check(pFunc))
{
printf("can't findfunction [HelloWorld]\n");
break;
}
pArgs = Py_BuildValue("(s)", "magictong");
PyEval_CallObject(pFunc,pArgs);
} while (0);
if (pRet)
Py_DECREF(pRet);
if (pArgs)
Py_DECREF(pArgs);
if (pFunc)
Py_DECREF(pFunc);
if (pDict)
Py_DECREF(pDict);
if (pModule)
Py_DECREF(pModule);
if (pName)
Py_DECREF(pName);
Py_Finalize();
return 0;
}
Python代碼
#!/usr/bin/python
import string
class CMyClass:
def HelloWorld(self):
print 'HelloWorld'
class SecondClass:
def invoke(self,obj):
obj.HelloWorld()
def HelloWorld(strName):
print "Hello ", strName
def Add(a, b, c):
return a + b + c
def AddMult(a, b):
"""
"""
print "in FunctionAddMult..."
print a
print b
return a + b, a * b
def StringToUpper(strSrc):
return string.upper(strSrc)
下面還有幾個比較重要的問題需要解決,且聽慢慢道來。
C++怎么向Python傳遞參數
C++向Python傳參數是以元組(tuple)的方式傳過去的,因此我們實際上就是構造一個合適的Python元組就可以了,要用到PyTuple_New,Py_BuildValue,PyTuple_SetItem等幾個函數,其中Py_BuildValue可以有其它一些的替換函數。
PyObject* pyParams = PyTuple_New(2);
PyObject* pyParams1= Py_BuildValue("i",5);
PyObject* pyParams2= Py_BuildValue("i",6);
PyTuple_SetItem(pyParams,0, pyParams1);
PyTuple_SetItem(pyParams,1, pyParams2);
pRet = PyEval_CallObject(pFunc, pyParams);
也可以直接使用PyObject* Py_BuildValue(char *format, ...) 函數來直接來構造tuple,此函數的使用也很簡單,記住一些轉換的格式常量即可輕松進行轉換(格式常量有點類似printf,參考[9])。譬如s 表示字符串,i表示整型變量,f表示浮點數,o表示一個Python對象等等。
Py_BuildValue("") None
Py_BuildValue("i",123) 123
Py_BuildValue("iii",123, 456, 789) (123, 456, 789)
Py_BuildValue("s","hello") 'hello'
Py_BuildValue("ss","hello", "world") ('hello', 'world')
Py_BuildValue("s#","hello", 4) 'hell'
Py_BuildValue("()") ()
Py_BuildValue("(i)",123) (123,)
Py_BuildValue("(ii)",123, 456) (123, 456)
Py_BuildValue("(i,i)",123, 456) (123, 456)
Py_BuildValue("[i,i]",123, 456) [123, 456]
Py_BuildValue("{s:i,s:i}",
"abc", 123, "def", 456) {'abc': 123, 'def': 456}
Py_BuildValue("((ii)(ii))(ii)",
1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6))
C++怎么轉換Python的返回值
Python傳回給C++的都是PyObject對象,因此可以調用Python里面的一些類型轉換API來把返回值轉換成C++里面的類型。類似PyInt_AsLong,PyFloat_AsDouble這些系列的函數。Python比較喜歡傳回一個元組,可以使用PyArg_ParseTuple這個函數來解析。這個函數也要用到上面的格式常量(參考[10])。還有一個比較通用的轉換函數是PyArg_Parse,也需要用到格式常量,夠不夠強大,用了就知道了。
直接調用Python腳本文件——另一種調用方式
初始化,反初始化都一樣,此種方式其實就是直接調用PyRun_SimpleString函數。
if(fp && PyRun_SimpleString("execfile('PyFile.py')") != 0)
{
fclose(fp);
printf("PyRun_SimpleFile(%s)failed!", szFile);
return -1;
}
還有一種方法是調用PyRun_SimpleFile()函數來直接運行一個Python文件,不過這種方式有點危險,因為這個API要求傳入一個FILE指針,而微軟的幾個CRT版本FILE指針的定義有了變化,因此傳入你使用VS2005編譯的FILE指針或者其它版本的FILE極有可能崩潰,如果你想安全調用,最好是自己把Python的源代碼使用和應用程序相同的環境一起編譯出lib來使用。
char szFile[] = "PyFile.py";
FILE* fp = fopen(szFile, "r");
if(fp && PyRun_SimpleFile(fp,szFile) != 0)
{
fclose(fp);
printf("PyRun_SimpleFile(%s)failed!", szFile);
return -1;
}
參考文獻
[1] python官網 http://www.python.org/
[2] python/c APIReference Manual http://docs.python.org/2/c-api/index.html
[3] 用C語言擴展python的功能 https://www.ibm.com/developerworks/cn/linux/l-pythc/
[4] C++擴展和嵌入Python http://www.vckbase.com/index.php/wv/1258
[5] Python調用C/C++模塊 http://blog.csdn.net/masefee/article/details/4750920
[6] Extendingand Embedding the Python Interpreter
http://docs.python.org/2/extending/index.html
[7] EmbeddingPython in Another Application
http://docs.python.org/2/extending/embedding.html
[8] C調用Python類/函數簡單代碼
http://www.360doc.com/content/12/0506/13/9369336_209021809.shtml
[9] The Py_BuildValue()Function
http://docs.python.org/release/1.5.2p2/ext/buildValue.html
[10] FormatStrings for PyArg_ParseTuple()
http://docs.python.org/release/1.5.2p2/ext/parseTuple.html
[11]Python編程
http://wiki.woodpecker.org.cn/moin/PP3eD
http://blog.csdn.net/magictong/article/details/8947892