• C++嵌入Python,以及两者混用


      以前项目中是C++嵌入Python,开发起来很便利,逻辑业务可以放到python中进行开发,容易修改,以及功能扩展。不过自己没有详细的研究过C++嵌入python的细节,这次详细的研究一下。首先我们简单的使用C++调用一个Python的py脚本,然后通过Python使用C++中的对象和方法。我们使用的Python是2.7.11

      1. 使用C++使用python的功能,比如我们写一个show.py,代码如下:  

    def show(name):
        return "hello " + name

      这个python脚本实在是太简单了,不需要任何解释了。然后简单的写一个C++函数,来简单的调用这个show.py中的函数show:

    #include <Python.h>
    #include <iostream>
    using namespace std;
    
    void python_test() {
        Py_SetPythonHome("D:/Python27");
        Py_Initialize();
    
        PyObject* pModule = PyImport_ImportModule("show");
        if (!pModule) {
            cout << "import python module test failed." << endl;
            return;
        }
    
        PyObject* pFunc = PyObject_GetAttrString(pModule, "show");
        if (!pFunc) {
            cout << "import python func failed." << endl;
            return;
        }
    
        PyObject* pParam = Py_BuildValue("(s)", "hahaha");
        PyObject* pResult = PyObject_CallObject(pFunc, pParam);
        if (pResult) {
            char* pBuf = NULL;
            int nBufSize = 0;
            //if (PyArg_ParseTuple(pResult, "si", &pBuf, &nBufSize))
            //{
            //    cout << "buf=" << pBuf << endl;
            //    cout << "buf size=" << nBufSize << endl;
            //}
    
            if (PyArg_Parse(pResult, "s", &pBuf))
            {
                cout << "buf=" << pBuf << endl;
            }
        }
    
        Py_DECREF(pParam);
        Py_DECREF(pResult);
    
        Py_Finalize();
        return;
    }
    
    int main() {
        python_test();
    }

      这里需要注意几个地方:

      (1) 首先Py_SetPythonHome("D:/Python27");这条语句是用来设置Python脚本的目录的,如果不设置这个目录,我们就不知道Python去哪个目录去读取我们的脚本了

      (2) Py_BuildValue("(s)", "hahaha"); 即使这里只需要一个参数,也需要使用tuple这样的方式传递参数,否则python解析模块会报错,它内部做了参数类型判断。

      (3) 如果show函数返回的是一个普通参数,我们使用PyArg_Parse来解析, 如果是一个元组,我们使用PyArg_ParseTuple来解析。不能混用,否则内部也会报错。

      从代码上看起来也是非常的简单,不过一开始部署环境不是这么简单,因为我们默认安装的Python是没有python27_d.lib 和python27_d.dll这2个文件的,具体如何解决python环境的问题,看第二节。

      

      2. python代码编译

      之前说了默认安装的Python是没有python27_d.lib 和python27_d.dll这2个文件的,我尝试过从网上单独下载这2个文件,不过小版本号不匹配也会出现运行时的crash。所以我选择了从网上下载了python2.7.11源代码,然后自己进行编译。不过这个编译总的来说是非常简单的,我是在windows下使用vs2012编译的,主要需要注意一下几点:

      (1) 使用PCbuild中的pcbuild.sln,直接打开,然后再vs中选中python项目,直接编译,我的编译过程是没有出现任何错误的,有些警告,我们直接忽略。在debug模式下,直接生成python27_d.lib 和python27_d.dll,还有python27_d.exe

      (2) 虽然我们有了python27_d.lib 和python27_d.dll,然后再在之前测试工程中添加好include和lib的目录,编译的时候,会提示找不到pyconfig.h这个文件。 这是因为这个文件是windows下特有的,我们需要在PC目录中copy这个文件到include目录下,然后再编译,就可以正常完成了。

      

      3. python调用C++对象

      python调用C++接口,有多种办法。通常python调用C++接口是一种功能扩展,将C++功能编译成动态库,然后python通过ctypes来导入库文件,在windows下是dll,linux下是so文件,这个使用比较简单,而且例子非常多,就不再介绍了。在很多复杂项目中,比如游戏中,我们希望python中操作玩家对象,那么如果将这个模块导成库文件,非常麻烦,而且不方便操作玩家对象实例。我们采用动态的方式来进行:

      示例中我们新建2个c++类,一个Person,一个PersonWrap。 Person对象代表某个人,PersonWrap是针对Person的Python接口扩展,Person.h代码如下:

    #ifndef PERSON_H
    #define PERSON_H
    
    #include <string>
    using namespace std;
    
    class Person {
    public:
        Person(){}
        ~Person(){}
    
        void setName(const string& _name) {
            name = _name;
        }
    
        string& getName() {
            return name;
        }
    
    private:
        string name;
    };
    
    #endif

      PersonWrap.h代码如下:

    #ifndef PERSON_WRAP_H
    #define PERSON_WRAP_H
    
    #include <Python.h>
    #include "Person.h"
    
    class PersonWrap {
    public:
        static PyObject* SetName(PyObject* self, PyObject* args) {
            cout << "Wrap setName" << endl;
            int personAddr = 0;
            char* pName = NULL;
            if (!PyArg_ParseTuple(args, "is", &personAddr, &pName)) {
                return NULL;
            }
            Person* p = (Person*)personAddr;
            p->setName(pName);
            
            return Py_BuildValue("i", 0);
        }
    
        static PyObject* GetName(PyObject* self, PyObject* args) {
            int personAddr = 0;
            if (!PyArg_ParseTuple(args, "i", &personAddr)) {
                return NULL;
            }
            Person* p = (Person*)personAddr;
    
            string& name = p->getName();
            return Py_BuildValue("s", name.c_str());
        }
    
        static PyObject* GetDesc(PyObject* self, PyObject* args) {
            return Py_BuildValue("s", "whoknows");
        }
    
        static PyObject* CreatePerson(PyObject* self, PyObject* args) {
            cout << "Wrap create person" << endl;
    
            char* pName = NULL;
            if (!PyArg_ParseTuple(args, "s", &pName)) {
                return NULL;
            }
    
            Person* p = new Person();
            p->setName(pName);
            return Py_BuildValue("i", p);
        }
    };
    
    PyMethodDef WrapMethods[] = {
        {"SetName", PersonWrap::SetName, METH_VARARGS, "SetName"},
        {"GetName", PersonWrap::GetName, METH_VARARGS, "GetName"},
        {"GetDesc", PersonWrap::GetDesc, METH_VARARGS, "GetDesc"},
        {"CreatePerson", PersonWrap::CreatePerson, METH_VARARGS, "CreatePerson"},
        {NULL, NULL, 0, NULL}
    };
    
    PyMODINIT_FUNC initPythonWrap() {
        PyObject* module;
        module = Py_InitModule("PythonWrap", WrapMethods);
    }
    
    #endif

      Person类的代码非常简单,不需要解释了。PersonWrap针对Person的接口进行了封装,可以看出PersonWrap中都是static函数,这是为了能够导出接口供Python使用,同时定义一个WrapMethods数组,将这些接口用Python方法的定义方式导出。最后用一个initPythonWrap方法来对这个模块进行动态初始化。这样我们的ptyhon脚本就可以调用了。

      不过需要在main函数中,新增initPythonWrap();的调用,否则不会生效。我们测试的python代码如下:

    import sys
    import PythonWrap
    
    def show(name):
        person = PythonWrap.CreatePerson("tom")
        PythonWrap.SetName(person, "jim")
        return "hello " + PythonWrap.GetName(person)

      从python脚本可以看出,我们import了我们动态生成的PythonWrap模块,然后就能像其他普通python模块一样随意的调用其接口。需要注意的是,我们的Person对象是一个C++对象,根据文档查询,如果不利于第三方插件是不能直接将C++对象传递给Python的,所以我们在传出和传入的时候,是将Person对象指针通过整数的方式进行传递的,然后强制进行指针转换。

      如果在debug模式下,在我们调用python的show函数时,使用到了PythonWrap中的接口时,会进入我们的C++代码,调试起来还是比较方便的。不过还存在一些问题,就是如果我们的SetName接口写成这样:

    static PyObject* SetName(PyObject* self, PyObject* args) {
            cout << "Wrap setName" << endl;
            int personAddr = 0;
            char* pName = NULL;
            if (!PyArg_ParseTuple(args, "is", &personAddr, &pName)) {
                return NULL;
            }
            Person* p = (Person*)personAddr;
            p->setName(pName);
            
            return NULL;
        }

      那么这个接口就只能被调用一次,第二次调用的时候就不会再触发,应该是针对返回值,python模块代码中做了特殊的处理,现在还不知道具体原因。等后面弄明白之后再来补充。

  • 相关阅读:
    编译gcc报错make[3]: Leaving directory `/usr/local/src/gcc-7.4.0/build/gcc' make[2]: *** [all-stage1-gcc] Error 2 处理
    ERROR 1176 (42000): Key 'XXX' doesn't exist in table 'XXX'报错处理
    /lib64/libc.so.6: version `GLIBC_2.18' not found报错解决
    Centos7上pkg-config的安装
    ERROR: Error in Log_event::read_log_event(): 'Found invalid event in binary log', data_len: 31, event_type: 35报错处理
    MySQL5.7主从复制slave报Last_Errno: 1146错误解决
    详述 hosts 文件的作用及修改 hosts 文件的方法
    Java Decompiler(Java反编译工具)
    使用Charles代理工具导致电脑无法正常访问网站(您的连接不是私密连接)
    阿里云服务器Svn-Server无法连接,阿里云服务器SVNServer配置
  • 原文地址:https://www.cnblogs.com/chobits/p/5045946.html
Copyright © 2020-2023  润新知