Boost python не будет автоматически преобразовывать элементы данных char *

Я пытаюсь обернуть C++ API, и я натыкаюсь на контрольно-пропускной пункт для некоторых членов класса char*. Похоже, что boost-python автоматически преобразует типы char const * и std::string в объекты python (на основе этот ответ), но он отказывается от char* типов. Это ошибка, которую я получаю (в python):

TypeError: No to_python (by-value) converter found for C++ type: char*

Оказывается, эти конкретные члены char *, вероятно, должны были быть объявлены как char const *, поскольку строки никогда не изменяются.

Я новичок в boost-python, поэтому, возможно, есть очевидный ответ, но мне не очень повезло с гуглом.

Есть ли простой способ заставить boost-python автоматически конвертировать эти char* членов? (К сожалению, я не могу изменить объявления char * на char const *, так как API, который я оборачиваю, не находится под моим контролем.)

ОБНОВЛЕНИЕ:

Итак, я думаю, что мне нужно добавить собственный преобразователь для обработки char* элементов. Я начал писать один:

/** to-python convert for char* */
struct c_char_p_to_python_str
{
    static PyObject* convert(char* s) {
        return incref(object(const_cast<const char*>(s)).ptr());
    }
};

// register the QString-to-python converter
to_python_converter<char*, c_char_p_to_python_str>();

К сожалению, это не работает. Это ошибка:

error: expected unqualified-id
to_python_converter<char*, c_char_p_to_python_str>();
                                                   ^

Изучив документы, я могу посмотрите, что аргументы шаблона имеют эту подпись:

template <class T, class Conversion, bool convertion_has_get_pytype_member=false>

Поскольку char* не является классом, я предполагаю, что это не сработало. У кого-нибудь есть понимание?

ОБНОВЛЕНИЕ 2:

Неа. Оказывается, to_python_converter нужно вызвать внутри вызова BOOST_PYTHON_MODULE.

У меня работает to_python_converter (с некоторыми изменениями). Я также написал функцию для преобразования формы python и зарегистрировал ее с помощью converter::registry::push_back. Я вижу, как работает мой код to_python, но код from_python никогда не запускается.


person Stephen    schedule 27.03.2016    source источник
comment
По-видимому, const требуется. Кроме того, если вы еще не используете boost в другом месте, вам может быть интересно узнать, что существует более легкий, более свежий (c++ 11 и новее) проект, который реализует те же функции (это почти форк): pybind11, где эта проблема может отсутствовать или быть решенной.   -  person Silmathoron    schedule 27.03.2016
comment
@Silmathoron, pybind11 выглядит многообещающе. Я попробую.   -  person Stephen    schedule 27.03.2016
comment
Как насчет того, чтобы раскрыть их через функции получения и установки, которые будут адаптировать тип? Геттеры/сеттеры могут быть даже просто автономными функциями, поэтому вам не нужно изменять классы.   -  person Dan Mašek    schedule 28.03.2016
comment
@DanMašek Я поиграл с этим с add_property. Мне удалось заставить работать геттерную часть, но я не мог понять, как выполнять функцию установщика. Не могли бы вы опубликовать ответ с рабочим примером с простым классом C++ с членом char* и обернуть его функциями получения/установки?   -  person Stephen    schedule 28.03.2016
comment
Если подумать, что представляют собой эти char* участники? Владеет ли ими класс (отвечает ли он за выделение/освобождение памяти? Поведение геттера простое, но что именно, по вашему мнению, делает сеттер? Перезаписывать существующую строку/буфер, насколько это возможно? Перераспределять память, если необходимо? Вы упомянули, что строки никогда не изменяются - можно ли просто сделать это свойство доступным только для чтения?   -  person Dan Mašek    schedule 28.03.2016
comment
@DanMašek Они принадлежат классу. Когда я говорю, что строки константы, я имею в виду, что они никогда не изменяются на месте. Однако можно присвоить новый char* переменной-члену. Таким образом, они ведут себя точно так же, как строки Python. Неизменный (например, такие вещи, как: char *s = "foo"; s[0] = 'F'; никогда не бывает), но переназначаемый. Назначение из python должно выделять новый char * и устанавливать для него переменную-член.   -  person Stephen    schedule 28.03.2016


Ответы (2)


Давайте предположим, что мы обертываем какой-то сторонний API, и отбросим в сторону ужас, связанный с тем, что эти указатели выставлены напоказ и возятся с ними извне.

Вот краткое доказательство концепции:

#include <boost/python.hpp>
namespace bp = boost::python;

class example
{
public:
    example()
    {
        text = new char[1];
        text[0] = '\0';
    }

    ~example()
    {
        delete[] text;
    }

public:
    char* text;

};

char const* get_example_text(example* e)
{
    return e->text;
}

void set_example_text(example* e, char const* new_text)
{
    delete[] e->text;

    size_t n(strlen(new_text));
    e->text = new char[n+1];
    strncpy(e->text, new_text, n);
    e->text[n] = '\0';
}

BOOST_PYTHON_MODULE(so02)
{
    bp::class_<example>("example")
        .add_property("text", &get_example_text, &set_example_text)
        ;
}

Класс example владеет text и отвечает за управление памятью.

Мы предоставляем внешнюю функцию получения и установки. Геттер прост, он просто предоставляет доступ для чтения к строке. Сеттер освобождает старую строку, выделяет новую память соответствующего размера и копирует данные.

Вот простой тест в интерпретаторе Python:

>>> import so02
>>> e = so02.example()
>>> e.text
''
>>> e.text = "foobar"
>>> e.text
'foobar'

Примечания:

  • set_example_text() может, возможно, взять std::string или bp::object, чтобы у нас была легко доступная длина, и потенциально можно было бы присваивать больше, чем просто строки.
  • Если есть много переменных-членов для переноса и шаблон получения/установки аналогичен, сгенерируйте код с использованием шаблонов или даже нескольких макросов.
  • Возможно, есть способ сделать это с помощью преобразователей, я посмотрю на это завтра. Однако, поскольку здесь мы имеем дело с управлением памятью, лично я бы предпочел сделать это таким образом, так как гораздо более очевидно, что происходит.
person Dan Mašek    schedule 28.03.2016
comment
Спасибо за доказательство концепции. Я поиграю с шаблонами, чтобы сделать его более общим. - person Stephen; 28.03.2016

Это расширяет ответ Дэна. Я написал несколько определений макросов, которые генерируют лямбда-выражения. Преимущества этого подхода заключаются в том, что он не привязан к конкретному типу или имени члена.

В API, который я оборачиваю, мне нужно обернуть несколько сотен классов. Это позволяет мне сделать один вызов макроса для каждого члена класса char*.

Вот модифицированная версия примера кода Дэна:

#include <boost/python.hpp>
namespace bp = boost::python;

#define ADD_PROPERTY(TYPE, ATTR) add_property(#ATTR, SET_CHAR_P(TYPE, ATTR), \
                                              GET_CHAR_P(TYPE, ATTR))

#define SET_CHAR_P(TYPE, ATTR) +[](const TYPE& e){                  \
                                   if (!e.ATTR) return "";          \
                                   return (const char*)e.ATTR;      \
                               }
#define GET_CHAR_P(TYPE, ATTR) +[](TYPE& e, char const* new_text){  \
                                   delete[] e.ATTR;                 \
                                   size_t n(strlen(new_text));      \
                                   e.ATTR = new char[n+1];          \
                                   strncpy(e.ATTR, new_text, n);    \
                                   e.ATTR[n] = '\0';                \
                               }

class example
{
public:
    example()
    {
        text = new char[1];
        text[0] = '\0';
    }

    ~example()
    {
        delete[] text;
    }

public:
    char* text;

};


BOOST_PYTHON_MODULE(topics)
{
    bp::class_<example>("example")
        .ADD_PROPERTY(example, text);
}
person Stephen    schedule 28.03.2016