最近公司项目使用python,个人突然对游戏引擎中python与c++之间的交互产生了兴趣。正赶上朋友要我帮忙做个五行相生相克的演示程序,于是想到写个hge的python导出库,顺便熟悉下python的c api。特写下此文,记录python与C++相互调用的要点,以便日后查阅。
准备工作
首先是配置开发环境了,可以选择编译python源代码,或者直接使用python的安装包。如果想要发布出去,在无python环境的用户机上直接运行,建议直接编译源代码。
下面是我们熟悉的过程,在VS中配置好python的路径、代码中include python的头文件、以及将lib引入工程。
如果你不是编译的源代码,在debug下,链接时会找不到pythonxx_d.lib。简单的解决办法是:
在python的安装目录下 include/pyconfig.h的368行(我使用的是python2.7)。将定义Py_DEBUG的宏注释掉:
/*
#ifdef _DEBUG
#define Py_DEBUG
#endif
*/
再到326行将python27_d.lib改为python27.lib:
ifdef _DEBUG
#pragma comment(lib,"python27.lib")
#elif
#pragma comment(lib,"python27.lib")
#endif /* _DEBUG */
这样就可以通过链接。
1. python调用C++函数
一. Python解释器的开启与关闭
本文是这个系列第一篇、主要是讲述如何将C++中定义的函数导出到python中调用。
首先在程序中先启动python的解释器:
Py_Initialize();
这个函数要在调用其他任何python API函数之前调用。
对应的,在程序结束的时候,需要关闭:
Py_Finalize();
二. C++函数的导出
下面说明如何导出C++的函数到py。本文的最后会提到如何将函数注册到module中,以及如何在Py中调用C++导出的函数。
static PyObject* Function(PyObject *pSelf, PyObject *pParams)
{
.......
}
所有函数都以这种形式导出。由于不是某类型的成员函数,可以不理会第一个参数pSelf。第二个参数pParams是从py中传进来的参数。这里解释个概念,PyObject是一个结构体,它代表了Python相关的数据。实际上,在PythonAPI中任何东西都可用PyObject结构体来表示,如:整型、字符串、函数或者整个脚本。Python与C++之间数据解析的关键就是搞清楚PyObject,即:如何将PyObject解析成C++类型,传给C++函数(传参)。如何将C++类型解析成PyObject,用以返回(传给python的返回值)
给个简单的例子:
static PyObject* Print(PyObject *pSelf, PyObject *pParams)
{
printf("hello my python world");
Py_INCREF(Py_None);
return Py_None;
}
这就是个最简单、无参数的函数。
下面个例子可以了解如何解析从py传来的参数,以及返回值。
static PyObject* Add(PyObject *pSelf, PyObject *pParams)
{
float x, y;
if (!PyArg_ParseTuple(pParams, "ff", &x, &y))
{
return NULL;
}
float result = x + y;
return PyFloat_FromDouble(result);
}
解释下这个函数PyArg_ParseTuple。Py的参数传递,其实是以tuple的形式进行的。pParams实际上是个Python Tuple Object的指针,这个tuple的每个元素对应于py函数的参数。PyArg_ParseTuple函数可以将一个Tuple类型,用代表格式的字符串解析成C++类型。见上例,其中pParams就是从PY传进来的参数,"ff"是代表格式的字符串,f代表浮点型。x, y是返回值。好了,解析一个py传进来的参数就是这么简单。可能有人会问,我怎么知道他传几个参数、以及参数的类型?别忘了,这个函数是你提供给py脚本程序员的,参数个数是你自己定的。
返回值PyFloat_FromDouble是用double创建了一个指向PyFloat类型的指针,内容是函数计算的结果。这里也可以使用另一个函数Py_BuildValue("f", result);第一个参数也是代表格式的字符串。
下面罗列一下,各常用字符串所代表的类型。
s | |
s# | |
z | |
z# | |
b | |
h | |
i | |
l(小写的L) | |
c | |
f | |
d | |
D | |
O | |
O! | |
O& | |
S | |
(items) | |
| | |
: | |
; |
三. 将函数注册到python的module中,导出module到python
文档中将要导出到python的函数称作 host api。
首先,我们需要将要导出函数的名字、函数地址等信息罗列到PyMethodDef类型的数组中,如:
static PyMethodDef Methods[]=
{
{"print", Print,METH_NOARGS, "print hello to python"},
{"add", Add,METH_VARARGS, "add two float."},
{NULL,NULL,0,NULL}
};
PyMethodDef成员分别是:导出到python的函数名,函数指针, 参数形式(常用的就这两种:METH_NOARGS代表没有参数。METH_VARARGS:代表有参数),函数的doc,在py中可以通过__doc__打印出来。最后一行的NULL是结束标记。
下面要注册module到python了。
if (!PyImport_AddModule("Sample"))
{
cout<<"Host API module could not be created."<<endl;
}
PyObject* module = Py_InitModule("Sample", Methods);
if (!module)
{
cout<<"Host Api functions could not be initialized."<<endl;
return;
}
函数PyImport_AddModule创建了一个module, Py_InitModule在init这个module的时候,将定义host api的数组传进去。这样就可以了
四. python中调用这些函数
import Sample
Sample.print()
result = Sample.add(1, 2)
print result
五. 具体例子
往往简单的例子不能让人快速的将技术使用到项目中,下面给出,我做HGE导出的一段函数。
C++:
static PyObject* HGE_System_Effect_Load(PyObject *pSelf, PyObject *pParams)
{
char* effectPath;
if (!PyArg_ParseTuple(pParams, "s", &effectPath))
{
return NULL;
}
DWORD effectHandle = g_hge->Effect_Load(effectPath);
return PyInt_FromLong(effectHandle);
}
.......
static PyMethodDef HostAPIFuncs[] =
{
.......
{"Effect", HGE_System_Effect_Load, METH_VARARGS, NULL},.......
{NULL, NULL, NULL, NULL}
};
........
if (!PyImport_AddModule("Hge"))
{
cout<<"Host API module could not be created."<<endl;
}
PyObject* module = Py_InitModule("Hge", HostAPIFuncs);
.........
Python:
import Hge
eff = Hge.Effect(".....")
这个函数实际上就是将hge的 Effect_Load函数导出到py中调用。
下一篇,将介绍如何导出C++定义的类型、以及成员函数和属性