Мы решили представить один из наших модулей 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