将浅复制到协议缓冲区的字节字段

14

假设我有一个带有字节数组字段的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(以便稍后可以对其进行序列化),而不必将源数据深度复制到字节字段中?

我知道我可以手动进行序列化,但如果可能的话,我宁愿不这样做。


在这个你无法控制的API中,它是否分配/拥有源数据的缓冲区,还是你必须自己分配并让API填充它? - Mark Waterman
@Mark 不,API 拥有缓冲区,我无法告诉它在哪里分配数据。 - Chris
唉,太糟糕了,否则你可能可以通过请求protobuf消息的可变字符串并将其底层缓冲区提供给API来解决它... 你被困在protobuf中吗?我记得MessagePack在这方面更加灵活。 - Mark Waterman
是的,这是一个使用protobuf的大型项目的一部分。 - Chris
只是好奇 - 你可以自由更改.proto定义吗? - Alejandro C De Baca
1个回答

9

好问题,可选项包括:

  1. UPDATE: StringPiece is obsolete according to an online developer discussion, which may render this option moot. If you can alter your .proto file, consider implementing the ctype field option for StringPiece, Google's equivalent of C++17 string_view. This is how Google would handle such a case internally. The FieldOptions message already has semantics for StringPiece, but Google has not yet open-sourced the implementation.

    message MyProto {
        bytes data = 1 [ctype = STRING_PIECE];
    }
    
  2. Use a different protocol buffer implementation, perhaps only for this particular message type. protobuf-c and protobluff are C-language implementations that look promising.

  3. Feed a buffer to your 3rd party API. I see from the comments that you can't, but I'm including it for completeness.

    ::std::string * buf = myProto->mutable_data();
    buf->resize(size);
    api(buf->data(), size); /* data is contiguous per c++11 std */
    
  4. NON STANDARD: Break encapsulation by overwriting the data in a string instance. C++ has some gnarly features that give you enough rope to hang yourself. This option is not safe and depends on your std::string implementation and other factors.

    // 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
    }
    

注意:不要在生产环境中执行此操作。这对于测试很有用,以测量如果您采用零拷贝路线会对性能产生影响。

如果您选择选项#1,如果您能发布源代码,那将非常好,因为许多其他人也会受益于此功能。祝你好运。


1
对于解决方案3,buf->reserve(size) 应该改为 buf->resize(size)。 - WeidongLian
@WeidongLian 感谢你的指出,已进行更新。谢谢! - Alejandro C De Baca

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接