Неверное копирование в поле байтов протокола буфера

Предположим, что у меня есть proto с байтом:

message MyProto {
    optional bytes data = 1;
}

API, который я не контролирую, дает мне указатель на исходные данные и их размер. Я хочу сделать MyProto из этих данных без глубокого копирования. Я думал, что это будет легко сделать, но это кажется невозможным. Глубокое копирование легко с set_data. Protobuf предоставляет функцию set_allocated_data, но он принимает указатель на std::string, что не помогает мне, поскольку (если я не ошибаюсь), нет способа сделать std::string без глубокого копирования в него.

void populateProto(void* data, size_t size, MyProto* message) {
    // Deep copy is fine, I guess.
    message->set_data(data, size);

    // Shallow copy would be better...
    // message->set_allocated_data( ??? );
}

Есть ли способ правильно заполнить этот proto (чтобы он мог быть сериализован позже) без глубокого копирования исходных данных в поле байтов?

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

Ответ 1

Отличный вопрос. Возможные варианты:

  • Если вы можете изменить свой .proto файл, подумайте об использовании опции поля ctype для StringPiece, эквивалент Google к предстоящему С++ 17 string_view. Так Google будет обрабатывать такой случай внутри страны. В сообщении FieldOptions уже есть семантика для StringPiece, но Google еще не открыла реализацию.

    message MyProto {
        bytes data = 1 [ctype = STRING_PIECE];
    }
    

    См. это обсуждение для руководства по внедрению. Вы можете игнорировать заметки о распределении арены, которые не относятся к вашему делу. Это стоит попросить Google о ETA.

  • Используйте другую реализацию буфера протокола, возможно, только для этого конкретного типа сообщения. protobuf-c и protobluff - это C- которые выглядят многообещающими.

  • Подайте буфер вашему стороннему API. Я вижу из комментариев, которые вы не можете, но я включаю их для полноты.

    ::str::string* buf = myProto->mutable_data();
    buf->reserve(size);
    api(buf->data(), size); // data is contiguous per c++11 std
    
  • НЕТ СТАНДАРТ: Разрыв инкапсуляции путем перезаписи данных в экземпляре строки. У С++ есть некоторые gnarly функции, которые дают вам достаточно веревки, чтобы повесить себя. Эта опция небезопасна и зависит от вашей реализации std::string и других факторов.

    // NEVER USE THIS IN PRODUCTION
    void string_jam(::std::string * target, void * buffer, size_t len) {
      /* On my system, std::string layout
       *   0: size_t capacity
       *   8: size_t size
       *  16: char * data (iff strlen > 22 chars) */
      assert(target->size() > 22);
      size_t * size_ptr = (size_t*)target;
      size_ptr[0] = len; // Overwrite capacity
      size_ptr[1] = len; // Overwrite length
    
      char ** buf_ptr = (char**)(size_ptr + 2); 
      free(*buf_ptr); // Free the existing buffer
      *buf_ptr = (char*)buffer; // Jam in our new buffer
    }
    

    Примечание. Это может заставить вас уволить. Это полезно для тестирования для измерения влияния производительности, если вы отправили нулевой маршрут, но не делайте этого в prod.

Если вы перейдете с опцией # 1, было бы замечательно, если бы вы могли освободить исходный код, как многие другие выиграют от этой возможности. Удачи.