C/C++和python之间相互调用的讲解***

====================================================||

欢迎讨论技术的可以相互加微信:windgs (请备注csdn+xx职业)

====================================================||

目录

简述

Python/CAPI简介

C++调用API列表

void Py_Initialize(void)

int Py_IsInitialized(void)

void Py_Finalize()

int PyRun_SimpleString(const char *command)

PyObject* PyImport_ImportModule(char *name)

PyObject* PyModule_GetDict( PyObject *module)

PyObject* PyRun_String(const char* str, int start,PyObject* globals, PyObject* locals)

int PyArg_Parse(PyObject* args, char* format, ...)

PyObject* PyObject_GetAttrString(PyObject *o, char*attr_name)

PyObject* Py_BuildValue(char* format, ...)

PyObject* PyEval_CallObject(PyObject* pfunc, PyObject*pargs)

C++代码

Python代码

C++怎么向Python传递参数

一、使用PyTuple_New,Py_BuildValue,PyTuple_SetItem构造Python元组方式

二、直接使用PyObject* Py_BuildValue(char *format, ...) 函数来直接来构造tuple

C++怎么转换Python的返回值

直接调用Python脚本文件

一种调用方式:直接调用PyRun_SimpleString函数

另一种调用方法:PyRun_SimpleFile()函数来直接运行一个Python文件


简述

一般开发过游戏的都知道Lua和C++可以很好的结合在一起,取长补短,把Lua脚本当成类似动态链接库来使用,很好的利用了脚本开发的灵活性。而作为一门流行的通用型脚本语言Python,也是可以做到的。在一个C++应用程序中,我们可以用一组插件来实现一些具有统一接口的功能,一般插件都是使用动态链接库实现,如果插件的变化比较频繁,我们可以使用Python来代替动态链接库形式的插件(堪称文本形式的动态链接库),这样可以方便地根据需求的变化改写脚本代码,而不是必须重新编译链接二进制的动态链接库。灵活性大大的提高了。

 

Python/CAPI简介

通过C++调用Python脚本主要要用到如下的一些Python提供的API,因为实际上C++要调用的是Python的解释器,而Python解释器本质就是实现在动态链接库里面的,因此在调用前和调用后要进行一些初始化和资源释放的工作,另外,要调用Python脚本里面的函数等等东西,需要Python提供的一些特殊API来包装C++调用。(可以参考[2])。

C++调用API列表

void Py_Initialize(void)

初始化Python解释器,如果初始化失败,继续下面的调用会出现各种错误,可惜的是此函数没有返回值来判断是否初始化成功,如果失败会导致致命错误。

 

int Py_IsInitialized(void)

检查是否已经进行了初始化,如果返回0,表示没有进行过初始化。

 

void Py_Finalize()

反初始化Python解释器,包括子解释器,调用此函数同时会释放Python解释器所占用的资源。

 

int PyRun_SimpleString(const char *command)

实际上是一个宏,执行一段Python代码。commandpython :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()来构建。
 

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("import sys");
              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 ret1 = 0;
              int ret2 = 0;
              if (pRet && PyArg_ParseTuple(pRet,"ii", &ret1,&ret2))
              {
                     printf("Function[AddMult] call successful a + b = %d, a * b = %d\n", ret1, ret2);
                     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传递参数

一、使用PyTuple_New,Py_BuildValue,PyTuple_SetItem构造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

也可以直接使用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_AsLongPyFloat_AsDouble这些系列的函数。Python比较喜欢传回一个元组,可以使用PyArg_ParseTuple这个函数来解析。这个函数也要用到上面的格式常量(参考[10])。还有一个比较通用的转换函数是PyArg_Parse,也需要用到格式常量,够不够强大,用了就知道了。

 

直接调用Python脚本文件

一种调用方式:直接调用PyRun_SimpleString函数

初始化,反初始化都一样,此种方式其实就是直接调用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;
}

Python/C API涉及的引用计数问题

  通过上面的例子,是不是觉得写python的C扩展模块非常的简单呢?其实不然,主要是python中有个引用计数问题,在写扩展模块的时候必须非常小心的处理,否则很有容易导致内存泄露。根据python官方的定义,在Python/C API中,引用计数的行为被归纳为三种:new reference、borrow reference和steal reference,前两种用于描述返回PyObject*类型的函数对返回的这个对象的引用计数的行为;后一种用于将一个PyObject*类型传入函数后,函数对这个对象的引用计数的行为。new referenc表示函数将这个对象引用的所有权转交给函数调用者了,由函数的调用者来管理这个引进的计数,也就是说调用者不用这个引用的时候必须显示的调用 Py_DECREF()或者Py_XDECREF()来释放这个引用,典型的函数是PyObject_、PyNumber_PySequence_和PyMapping_;borrow reference与new reference刚好相反,表示函数的调用者只管用这个引用,不用关心它的引用计数,用完了也不用显示调用Py_DECREF()或者Py_XDECREF()来释放这个引用,典型的函数是PyList_GetItem、PyTuple_GetItem;steal reference表示函数内部只会使用这个引用,不会调用Py_INCREF来增加这个引用的引用计数,相当于“偷了”被调用者的一个引用计数,典型的函数是PyList_SetItem()和PyTuple_SetItem()。因此,在编写C扩展的时,如果遇到某个Python/C API不确定是哪种reference的时候,建议查下官方文档,文档中会明确的说明这个函数是哪类reference(如下图所示),这样能大大减少引用计数的问题。

参考文献

[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

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

隨意的風

如果你觉得有帮助,期待你的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值