Нарушение прав доступа при попытке считывания объекта, созданного в Python, переданного в std :: vector на стороне C ++, а затем возвращенного в Python - PullRequest
4 голосов
/ 07 января 2020

Работа с VS 2019, Python 3.7 64bit на Windows 10 и pybind11 2.4.3 Я столкнулся со следующей проблемой:

Когда я создаю объект с pybind11 py::class_ на Python и передают его непосредственно методу на стороне 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?

Ответы [ 2 ]

3 голосов
/ 07 января 2020

Примечание: я не эксперт по PyBind11 , я просто прочитал вопрос и попытался выяснить, в чем причина.
Я предполагаю, что разница в том, что в случае, если он не работает, объект Python создается непосредственно перед вызовом add_item (как и C ++ обернут один), затем сразу после вызова происходит сборка мусора (и вместе с ним C ++ обернутый один), получая Undefined Behavior (недопустимый указатель).
И наоборот, в случае, если он работает, объект не подвергается сборке мусора, поскольку он «сохраняется» в item (его refcount больше 0), и, следовательно, C ++ упакованный объект также присутствует. A delete item сразу после value.add_item(item) должно воспроизвести ошибочное поведение.

В соответствии с [ReadTheDocs.PyBind11]: Функции - сохранить в живых :

In В общем, эта политика требуется, когда объектом C ++ является контейнер любого типа, а другой объект добавляется в контейнер. keep_alive<Nurse, Patient> указывает, что аргумент с индексом Patient должен поддерживаться в рабочем состоянии как минимум до тех пор, пока сборщик мусора не освободит аргумент с индексом Nurse.

Итак, решение состоит в том, чтобы UseClassHierarchyAsPythonModule.PyXdmItem Объект сохраняется до тех пор, пока контейнер не будет уничтожен (обратите внимание, что это может сохранить объекты в памяти дольше, чем ожидалось, для этого может быть более чистый способ), то есть, указав в add_item :

...

.def("add_item", &XdmValue::addXdmItem, py::keep_alive<1, 2>());
3 голосов
/ 07 января 2020

PyBind11 может автоматически приводиться с использованием трюка RTTI (это polymorphic_type_hook; это то же самое, что я делаю в cppyy: вы создаете поддельный базовый класс, приводите указанный адрес к поддельной базе, затем читаете RTTI, чтобы получить фактическое имя, затем выполните поиск Python -прокси и примените базу к производному смещению при необходимости). Если код Python сначала создает объект, то позднее он обнаруживается по адресу (это гарантирует идентичность объекта), поэтому приведение не происходит.

Чтобы это автоматическое приведение работало правильно, вам действительно требуется виртуальный объект. деструктор, чтобы гарантировать (согласно стандарту C ++) правильное размещение RTTI в dll. Я не вижу в вашем базовом классе (XdmValue).

(Кроме того, специфицируя c в Windows, я также всегда экспортирую узел RTTI root из основного приложения, чтобы гарантировать наличие только 1. Но если так, то это должен делать интерпретатор Python или первый модуль, так что я не думаю, что это применимо здесь. Кроме того, я предполагаю, конечно, что вы включаете RTTI при сборке.)

...