Как представить сериализованные данные C ++ в python, используя boost_python - PullRequest
0 голосов
/ 18 октября 2018

Мы решили представить один из наших модулей IPC (межпроцессное взаимодействие), написанных на C ++, для python (я знаю, это не самая лучшая идея).Мы используем пакеты данных, которые можно сериализовать и десериализовать в / из std::string (поведение аналогично буферам протокола, но не так эффективно), поэтому наш класс IPC также возвращает и принимает std::string.

Проблемас выставлением этого класса на python то, что std::string c ++ тип преобразуется в str тип python, и в случае, если возвращаемое std::string состоит из символов, которые не могут быть декодированы до UTF-8 (что чаще всего), яполучить исключение UnicodeDecodeError.

Мне удалось найти два обходных пути (или даже "решения"?) для этой проблемы, но я не особенно доволен любым из них.

Этомой код на C ++ для воспроизведения проблемы UnicodeDecodeError и пробного решения:

/*
 * boost::python string problem
 */

#include <iostream>
#include <string>
#include <vector>
#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>

struct Packet {
    std::string serialize() const {
        char buff[sizeof(x_) + sizeof(y_)];
        std::memcpy(buff, &x_, sizeof(x_));
        std::memcpy(buff + sizeof(x_), &y_, sizeof(y_));
        return std::string(buff, sizeof(buff));
    }
    bool deserialize(const std::string& buff) {
        if (buff.size() != sizeof(x_) + sizeof(y_)) {
            return false;
        }
        std::memcpy(&x_, buff.c_str(), sizeof(x_));
        std::memcpy(&y_, buff.c_str() + sizeof(x_), sizeof(y_));
        return true;
    }
    // whatever ...
    int x_;
    float y_;
};

class CommunicationPoint {
public:
    std::string read() {
        // in my production code I read that std::string from the other communication point of course
        Packet p;
        p.x_ = 999;
        p.y_ = 1234.5678;
        return p.serialize();
    }
    std::vector<uint8_t> readV2() {
        Packet p;
        p.x_ = 999;
        p.y_ = 1234.5678;
        std::string buff = p.serialize();
        std::vector<uint8_t> result;
        std::copy(buff.begin(), buff.end(), std::back_inserter(result));
        return result;
    }
    boost::python::object readV3() {
        Packet p;
        p.x_ = 999;
        p.y_ = 1234.5678;
        std::string serialized = p.serialize();
        char* buff = new char[serialized.size()];  // here valgrind detects leak
        std::copy(serialized.begin(), serialized.end(), buff);
        PyObject* py_buf = PyMemoryView_FromMemory(
            buff, serialized.size(), PyBUF_READ);
        auto retval = boost::python::object(boost::python::handle<>(py_buf));
        //delete[] buff;  // if I execute delete[] I get garbage in python
        return retval;
    }
};

BOOST_PYTHON_MODULE(UtfProblem) {
    boost::python::class_<std::vector<uint8_t> >("UintVec")
        .def(boost::python::vector_indexing_suite<std::vector<uint8_t> >());
    boost::python::class_<CommunicationPoint>("CommunicationPoint")
        .def("read", &CommunicationPoint::read)
        .def("readV2", &CommunicationPoint::readV2)
        .def("readV3", &CommunicationPoint::readV3);
}

Он может быть скомпилирован с g++ -g -fPIC -shared -o UtfProblem.so -lboost_python-py35 -I/usr/include/python3.5m/ UtfProblem.cpp (в производстве мы используем CMake, конечно).

Этокороткий скрипт на python, который загружает мою библиотеку и декодирует числа:

import UtfProblem
import struct

cp = UtfProblem.CommunicationPoint()

#cp.read()  # exception

result = cp.readV2()
# result is UintVec type, so I need to convert it to bytes first
intVal = struct.unpack('i', bytes([x for x in result[0:4]]))
floatVal = struct.unpack('f', bytes([x for x in result[4:8]]))
print('intVal: {} floatVal: {}'.format(intVal, floatVal))

result = cp.readV3().tobytes()
intVal = struct.unpack('i', result[0:4])
floatVal = struct.unpack('f', result[4:8])
print('intVal: {} floatVal: {}'.format(intVal, floatVal))

В первом обходном пути вместо возврата std::string я возвращаю std::vector<unit8_t>.Он работает нормально, но мне не нравится тот факт, что он заставляет меня выставлять дополнительный искусственный тип Python UintVec, который не имеет встроенной поддержки для преобразования в Python bytes.

Второй обходной путьэто хорошо, потому что он выставляет мой сериализованный пакет как блок памяти с встроенной поддержкой преобразования в байты, но это приводит к утечке памяти.Я проверил утечку памяти, используя valgrind: valgrind --suppressions=../valgrind-python.supp --leak-check=yes -v --log-file=valgrindLog.valgrind python3 UtfProblem.py и, кроме множества недопустимых чтений (вероятно, ложных срабатываний) из библиотеки python, он показывает мне

8 байтов в 1 блоках определенно потеряно

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

Вопрос:

Как я могу соответствующим образом представить свои сериализованные данные в python?В C ++ для представления массива байтов мы обычно используем std::string или const char*, что, к сожалению, не очень хорошо переносится на python.

Если мой второй обходной путь вам подходит, какможно ли избежать утечки памяти?

Если выставить возвращаемое значение как std::string в целом нормально, как я могу избежать UnicodeDecodeError?

Дополнительная информация:

  • g ++ (Debian 6.3.0-18 + deb9u1) 6.3.0 20170516
  • Python 3.5.3
  • boost 1.62

Ответы [ 2 ]

0 голосов
/ 19 октября 2018

Согласно AntiMatterDynamite комментарию, возвращение питонического bytes объекта (с использованием Python API) работает отлично:

PyObject* read() {
    Packet p;
    p.x_ = 999;
    p.y_ = 1234.5678;
    std::string buff = p.serialize();
    return PyBytes_FromStringAndSize(buff.c_str(), buff.size());
}
0 голосов
/ 18 октября 2018

Я предлагаю вам определить свой собственный класс возвращаемого типа в C ++ и раскрыть его с помощью Boost Python.Например, вы можете использовать буферный протокол.Тогда у вас будет обычный деструктор C ++, который будет вызываться в нужное время - вы даже можете использовать умный указатель внутри класса для управления временем жизни выделенной памяти.

Как только вы это сделаетеследующий вопрос будет таким: почему бы просто не вернуть свойства возвращаемого объекта для доступа к полям, не вызывая вызывающую функцию struct.unpack()?Тогда ваш код вызова может быть намного проще:

result = cp.readV5()
print('intVal: {} floatVal: {}'.format(result.x, result.y))
...