尝试读出在 Python 中创建的对象时发生访问冲突,该对象在 C++ 端传递给 std::vector,然后返回给 Python
Access violation when trying to read out object created in Python passed to std::vector on C++ side and then returned to Python
使用 VS 2019,Python 3.7 64 位 Windows 10 和 pybind11 2.4.3 我 运行 遇到以下问题:
当我在 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 使用有什么问题吗?
PyBind11 可以使用 RTTI 技巧自动投射(即 polymorphic_type_hook
;这与我在 cppyy 中做的一样:你制作一个假基地 class,将给定地址投射到假基地,然后读取 RTTI 以获取实际名称,然后查找 Python-proxy 并根据需要将基数应用于派生偏移量)。如果 Python 代码首先创建对象,稍后通过地址找到它(这是为了保证对象身份),因此不会发生转换。
为了使自动转换正常工作,您确实需要一个虚拟析构函数来保证(根据 C++ 标准)RTTI 在 dll 中的正确放置。我在你的基础中没有看到 class (XdmValue).
(另外,具体到 Windows,我也总是从主应用程序导出 RTTI 根节点以保证只有一个。但如果是这样,那应该是 Python 解释器在做,或第一个模块,所以我认为这不适用于此处。此外,我当然假设您在构建时启用了 RTTI。)
注意:到目前为止,我还不是 PyBind11 专家,我只是阅读了问题并试图找出可能的原因。
我的猜测是,不同之处在于,在它不起作用的情况下,Python 对象是在 [ 之前创建的=50=] 调用(C++ 包装的调用也是如此)然后在调用之后它被垃圾收集(以及 C++ 包裹了一个),产生 Undefined Behavior(无效指针)。
相反,在它工作的情况下,对象没有被垃圾收集,因为它在 item 中 "saved"(它的 refcount 大于 0),因此 C++ 包装对象也存在。 value.add_item(item)
之后的 delete item
应该会重现错误行为。
根据[ReadTheDocs.PyBind11]: Functions - Keep alive:
In general, this policy is required when the C++ object is any kind of container and another object is being added to the container. keep_alive<Nurse, Patient>
indicates that the argument with index Patient
should be kept alive at least until the argument with index Nurse
is freed by the garbage collector.
因此,解决方案是使 UseClassHierarchyAsPythonModule.PyXdmItem 对象持久化,直到容器被销毁(请注意,这可能会使对象在内存中的保留时间比预期的长,可能是实现这一目标的更简洁的方法),那就是在 add_item:
中指定
...
.def("add_item", &XdmValue::addXdmItem, py::keep_alive<1, 2>());
使用 VS 2019,Python 3.7 64 位 Windows 10 和 pybind11 2.4.3 我 运行 遇到以下问题:
当我在 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 使用有什么问题吗?
PyBind11 可以使用 RTTI 技巧自动投射(即 polymorphic_type_hook
;这与我在 cppyy 中做的一样:你制作一个假基地 class,将给定地址投射到假基地,然后读取 RTTI 以获取实际名称,然后查找 Python-proxy 并根据需要将基数应用于派生偏移量)。如果 Python 代码首先创建对象,稍后通过地址找到它(这是为了保证对象身份),因此不会发生转换。
为了使自动转换正常工作,您确实需要一个虚拟析构函数来保证(根据 C++ 标准)RTTI 在 dll 中的正确放置。我在你的基础中没有看到 class (XdmValue).
(另外,具体到 Windows,我也总是从主应用程序导出 RTTI 根节点以保证只有一个。但如果是这样,那应该是 Python 解释器在做,或第一个模块,所以我认为这不适用于此处。此外,我当然假设您在构建时启用了 RTTI。)
注意:到目前为止,我还不是 PyBind11 专家,我只是阅读了问题并试图找出可能的原因。
我的猜测是,不同之处在于,在它不起作用的情况下,Python 对象是在 [ 之前创建的=50=] 调用(C++ 包装的调用也是如此)然后在调用之后它被垃圾收集(以及 C++ 包裹了一个),产生 Undefined Behavior(无效指针)。
相反,在它工作的情况下,对象没有被垃圾收集,因为它在 item 中 "saved"(它的 refcount 大于 0),因此 C++ 包装对象也存在。 value.add_item(item)
之后的 delete item
应该会重现错误行为。
根据[ReadTheDocs.PyBind11]: Functions - Keep alive:
In general, this policy is required when the C++ object is any kind of container and another object is being added to the container.
keep_alive<Nurse, Patient>
indicates that the argument with indexPatient
should be kept alive at least until the argument with indexNurse
is freed by the garbage collector.
因此,解决方案是使 UseClassHierarchyAsPythonModule.PyXdmItem 对象持久化,直到容器被销毁(请注意,这可能会使对象在内存中的保留时间比预期的长,可能是实现这一目标的更简洁的方法),那就是在 add_item:
中指定...
.def("add_item", &XdmValue::addXdmItem, py::keep_alive<1, 2>());