将C++对象传递给Python

30

这个问题是关于如何将一个C++对象传递给在(C++)嵌入式Python解释器中调用的Python函数的。

下面的C++类(MyClass.h)是为了测试而设计的:

#ifndef MyClassH
#define MyClassH
#include <string>

using std::string;
class MyClass
{
    public:
                        MyClass(const string& lbl): label(lbl) {}
                        ~MyClass(){}
        string          getLabel() {return label;}

    private:
        string          label;
};
#endif

以下Swig接口文件可以生成一个暴露C++类的Python模块:

%module passmetopython

%{    #include "MyClass.h"    %}

%include "std_string.i"

//Expose to Python
%include "MyClass.h"

以下是使用Python模块的Python脚本

import passmetopython as pmtp

def execute(obj):
    #This function is to be called from C/C++, with a
    #MyClass object as an argument
    print ("Entering execute function")
    lbl = obj.getLabel();
    print ("Printing from within python execute function. Object label is: " + lbl)
    return True

def main():
    c = pmtp.MyClass("Test 1")
    retValue = execute(c)
    print("Return value: " + str(retValue))

#Test function from within python
if __name__ == '__main__':
    main()

这个问题是关于如何在从C++中调用Python的execute()函数,并将一个C++对象作为参数进行传递。

下面的C++程序旨在测试这些函数(仅包含最少量的错误检查):

#include "Python.h"
#include <iostream>
#include <sstream>
#include "MyClass.h"

using namespace std;

int main()
{
    MyClass obj("In C++");
    cout << "Object label: \"" << obj.getLabel() << "\"" << endl;

    //Setup the Python interpreter and eventually call the execute function in the
    //demo python script
    Py_Initialize();

    //Load python Demo script, "passmetopythonDemo.py"
    string PyModule("passmetopythonDemo");
    PyObject* pm = PyUnicode_DecodeFSDefault(PyModule.c_str());

    PyRun_SimpleString("import sys");
    stringstream cmd;
    cmd << "sys.path.append(\"" << "." << "\")";
    PyRun_SimpleString(cmd.str().c_str());
    PyObject* PyModuleP = PyImport_Import(pm);
    Py_DECREF(pm);

    //Now create PyObjects for the Python functions that we want to call
    PyObject* pFunc = PyObject_GetAttrString(PyModuleP, "execute");

    if(pFunc)
    {
        //Setup argument
        PyObject* pArgs = PyTuple_New(1);

        //Construct a PyObject* from long
        PyObject* pObj(NULL);

        /* My current attempt to create avalid argument to Python */
        pObj = PyLong_FromLong((long) &obj);


        PyTuple_SetItem(pArgs, 0, pObj);

        /***** Calling python here *****/
        cout<<endl<<"Calling function with an MyClass argument\n\n";
        PyObject* res = PyObject_CallObject(pFunc, pArgs);
        if(!res)
        {
            cerr << "Failed calling function..";
        }
    }

    return 0;
}
当运行上述代码时,execute() Python函数使用一个MyClass对象作为参数失败并返回NULL。但是,Python函数已经被调用,因为我可以在控制台输出中看到输出(Entering execute function),这表明传递的对象确实不是一个有效的MyClass对象。
有很多关于如何从C/C++传递简单类型(如int、double或字符串类型)到Python的例子。但是很少有例子展示如何传递C/C++对象/指针,这有点令人困惑。
上述代码及其CMake文件可以从GitHub检出: https://github.com/TotteKarlsson/miniprojects/tree/master/passMeToPython 此代码不使用任何boost python或其他API。但Cython听起来很有趣,如果它可以用于简化C++端,那么它可能是可接受的。

你有没有考虑将它序列化为JSON或XML,然后使用构造函数在Python中构建对象? - Jake Steele
我的目标是能够为对象共享内存,因此不需要复制。 - Totte Karlsson
你的代码片段中没有 pFunc 对象。它不是这样工作的。myObject 必须是一个有效的 PyObject(我认为它不是 - 直到我看到 MyObject 的定义,我不能确定)。“C++类已经使用swig进行了封装”:那么我认为你不需要编写这个C代码。 - CristiFati
感谢CristiFati,在这个问题中,Python解释器嵌入在C++应用程序中,因此上面的Python函数是从C/C++调用的。 MyObject是一个空的“演示”C++类,其内部是无关紧要的。它仅用于理解概念,正如标题所述,“如何将C++对象传递给Python?” - Totte Karlsson
2个回答

9
这是我自己问题的部分答案。我说部分,因为我确信有更好的方法。
在此帖子http://swig.10945.n7.nabble.com/Pass-a-Swig-wrapped-C-class-to-embedded-Python-code-td8812.html的基础上,我生成了SWIG运行时头文件,如此处所述,第15.4节:http://www.swig.org/Doc2.0/Modules.html#Modules_external_run_time 将生成的头文件包含在上面的C++代码中,可以编写以下代码:
    PyObject* pObj = SWIG_NewPointerObj((void*)&obj, SWIG_TypeQuery("_p_MyClass"),  0 ); 

此代码使用Swig Python包装源文件中的信息,即类型MyClass的“swig”名称,即_p_MyClass

将上述PyObject*作为PyObject_CallObject函数的参数,上面的代码中的python execute()函数正常执行,并且使用生成的python模块的Python代码确实可以正确访问MyClass对象的内部数据。这很好。

尽管上述代码演示了如何以相当简单的方式在C++和Python之间传递和检索数据,但我认为它并不理想。

C++代码中使用的swig头文件真的不太美观,而且还需要用户“手动”查看swig生成的包装器代码,以找到“_p_MyClass”代码。

一定有更好的方法!也许应该在swig接口文件中添加一些内容,以使其看起来更加美观?


这基本上就是答案。如果你喜欢的话,你应该能够写出 SWIG_TypeQuery("MyClass *") - Flexo
然而,我可能会尝试通过包装的C++函数使execute通过查找obj实例来完成,而不是将其作为参数传递。例如,它调用api.getInstance(),其中api是通过PyImport_AppendInittab()提供的。 - Flexo
谢谢Flexo,也许包含swig包装头文件不会太糟糕。使用您的建议查询类型的能力,带有“MyClass *”,消除了必须查看swig包装代码的必要性,这是一个很大的改进,我认为。不确定如何使用AppendInittab技术。 - Totte Karlsson

-1
PyObject *pValue;
pValue = PyObject_CallMethod(pInstance, "add","(i)",x);
if (pValue)
    Py_DECREF(pValue);
else
    PyErr_Print();

好奇,pInstance、x和“add”是什么? - Totte Karlsson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接