首页 > 解决方案 > 尝试读取在 Python 中创建的对象时访问冲突传递给 C++ 端的 std::vector 然后返回给 Python

问题描述

在 Windows 10 和 pybind11 2.4.3 上使用 VS 2019、Python 3.7 64bit 我遇到了以下问题:

当我在 Python 端创建一个带有 pybind11 的对象py::class_并将其直接传递给 C++ 端的一个方法时,将其存储在 std::vector 中,稍后尝试从 Python 中读取该对象会导致Access violation - no RTTI data. 如果 Python 代码首先将创建的对象存储在 Python 变量中,然后将其传递给 C++,那么来自 Python 的后续访问将按预期工作。

我在使用 pybind11 和 C++ 方面没有太多经验,所以我可能犯了一个简单的错误,希望能提供有关如何设置 C++ 和 pybind11 用法的任何帮助,以便不需要使用变量的 Python 解决方法,我不需要'没有任何访问冲突。

下面是一些代码细节,C++代码是

#include <iostream>

#include <vector>

#include <pybind11/pybind11.h>

using namespace std;


class XdmItem;

class XdmValue {

public:

    virtual XdmItem* itemAt(int n);

    virtual int size();

    void addXdmItem(XdmItem* val);


protected:
    std::vector<XdmItem*> values;
};

void XdmValue::addXdmItem(XdmItem* val) {
    values.push_back(val);
}

XdmItem* XdmValue::itemAt(int n) {
    if (n >= 0 && (unsigned int)n < values.size()) {
        return values[n];
    }
    return NULL;
}

int XdmValue::size() {
    return values.size();
}

class XdmItem : public XdmValue {

public:

    int size();

};

int XdmItem::size() {
    return 1;
}


namespace py = pybind11;

PYBIND11_MODULE(UseClassHierarchyAsPythonModule, m) {


    py::class_<XdmValue>(m, "PyXdmValue")
        .def(py::init<>())
        .def("size", &XdmValue::size)
        .def("item_at", &XdmValue::itemAt)
        .def("add_item", &XdmValue::addXdmItem);

    py::class_<XdmItem, XdmValue>(m, "PyXdmItem")
        .def(py::init<>())
        .def("size", &XdmItem::size);



#ifdef VERSION_INFO
    m.attr("__version__") = VERSION_INFO;
#else
    m.attr("__version__") = "dev";
#endif
}

完美运行的 Python 代码是

import UseClassHierarchyAsPythonModule

value = UseClassHierarchyAsPythonModule.PyXdmValue()

print(value, type(value))

print(value.size())

item = UseClassHierarchyAsPythonModule.PyXdmItem()

value.add_item(item)

print(value.size())

item0 = value.item_at(0)

print(item, type(item))

而以下代码导致Access violation - no RTTI data!

import UseClassHierarchyAsPythonModule

value = UseClassHierarchyAsPythonModule.PyXdmValue()

print(value, type(value))

print(value.size())

value.add_item(UseClassHierarchyAsPythonModule.PyXdmItem())

print(value.size())

item0 = value.item_at(0)

print(item, type(item))

它给

  Message=Access violation - no RTTI data!
  Source=C:\SomePath\AccessViolation.py
  StackTrace:
  File "C:\SomePath\AccessViolation.py", line 13, in <module>
    item0 = value.item_at(0)

如果我启用本机代码调试,堆栈跟踪包括 pybind C++ 代码并且是

>   UseClassHierarchyAsPythonModule.pyd!pybind11::polymorphic_type_hook<XdmItem,void>::get(const XdmItem * src, const type_info * & type) Line 818  C++
    UseClassHierarchyAsPythonModule.pyd!pybind11::detail::type_caster_base<XdmItem>::src_and_type(const XdmItem * src) Line 851 C++
    UseClassHierarchyAsPythonModule.pyd!pybind11::detail::type_caster_base<XdmItem>::cast(const XdmItem * src, pybind11::return_value_policy policy, pybind11::handle parent) Line 871  C++
    UseClassHierarchyAsPythonModule.pyd!pybind11::cpp_function::initialize::__l2::<lambda>(pybind11::detail::function_call & call) Line 163 C++
    UseClassHierarchyAsPythonModule.pyd!pybind11::handle <lambda>(pybind11::detail::function_call &)::<lambda_invoker_cdecl>(pybind11::detail::function_call & call) Line 100   C++
    UseClassHierarchyAsPythonModule.pyd!pybind11::cpp_function::dispatcher(_object * self, _object * args_in, _object * kwargs_in) Line 624 C++
    [External Code] 
    AccessViolation.py!<module> Line 13 Python

知道我的 C++/pybind11 使用有什么问题吗?

标签: pythonc++pybind11

解决方案


PyBind11 可以使用 RTTI 技巧自动转换(这polymorphic_type_hook与我在 cppyy 中所做的相同:您创建一个假基类,将给定地址转换为假基类,然后读取 RTTI 以获取实际名称,然后执行查找 Python 代理并根据需要将基数应用于派生偏移量)。如果 Python 代码首先创建对象,则稍后会通过地址找到它(这是为了保证对象身份),因此不会发生强制转换。

为了使自动转换正常工作,您确实需要一个虚拟析构函数来保证(根据 C++ 标准)在 dll 中正确放置 RTTI。我在您的基类(XdmValue)中没有看到。

(除此之外,特定于 Windows,我也总是从主应用程序导出 RTTI 根节点以保证只有一个。但如果是这样,那应该是 Python 解释器做的,或者是第一个模块,所以我不认为适用于此。另外,我当然假设您在构建时启用了 RTTI。)


推荐阅读