0

我想将一些 Capnproto 结构存储在 LevelDB 中,因此我必须将其序列化为字符串,并稍后从 std::string 反序列化。目前,我使用以下内容(改编自此处:https ://groups.google.com/forum/#!msg/capnproto/viZXnQ5iN50/B-hSgZ1yLWUJ ):

capnp::MallocMessageBuilder message;
WortData::Builder twort = message.initRoot<WortData>();
twort.setWid(1234);
twort.setW("Blabliblub");
kj::Array<capnp::word> dataArr = capnp::messageToFlatArray(message);
kj::ArrayPtr<kj::byte> bytes = dataArr.asBytes();
std::string data(bytes.begin(), bytes.end());
std::cout << data << std::endl;
const kj::ArrayPtr<const capnp::word> view(
    reinterpret_cast<const capnp::word*>(&(*std::begin(data))),
    reinterpret_cast<const capnp::word*>(&(*std::end(data))));
capnp::FlatArrayMessageReader message2(view);
WortData::Reader wortRestore = message2.getRoot<WortData>();
std::cout << wortRestore.getWid() << " " << std::string(wortRestore.getW()) << std::endl;

它基本上可以工作,但是上面链接中的人不确定这种方法以后是否会导致错误,并且由于讨论已经很老了,我想问是否有更好的方法。最后有人说“使用 memcpy!”,但我不确定这是否有用,以及如何使用FlatArrayMessageReader.

提前致谢!
dvs23

更新:

我尝试实施与字对齐相关的建议:

capnp::MallocMessageBuilder message;
WortData::Builder twort = message.initRoot<WortData>();
twort.setWid(1234);
twort.setW("Blabliblub");
kj::Array<capnp::word> dataArr = capnp::messageToFlatArray(message);
kj::ArrayPtr<kj::byte> bytes = dataArr.asBytes();
std::string data(bytes.begin(), bytes.end());
std::cout << data << std::endl;

if(reinterpret_cast<uintptr_t>(data.data()) % sizeof(void*) == 0) {
    const kj::ArrayPtr<const capnp::word> view(
        reinterpret_cast<const capnp::word*>(&(*std::begin(data))),
        reinterpret_cast<const capnp::word*>(&(*std::end(data))));
    capnp::FlatArrayMessageReader message2(view);
    WortData::Reader wortRestore = message2.getRoot<WortData>();
    std::cout << wortRestore.getWid() << " " << std::string(wortRestore.getW()) << std::endl;
}
else {
    size_t numWords = data.size() / sizeof(capnp::word);

    if(data.size() % sizeof(capnp::word) != 0) {
        numWords++;
        std::cout << "Something wrong here..." << std::endl;
    }

    std::cout << sizeof(capnp::word) << " " << numWords << " " << data.size() << std::endl;

    capnp::word dataWords[numWords];
    std::memcpy(dataWords, data.data(), data.size());
    kj::ArrayPtr<capnp::word> dataWordsPtr(dataWords, dataWords + numWords);
    capnp::FlatArrayMessageReader message2(dataWordsPtr);
    WortData::Reader wortRestore = message2.getRoot<WortData>();
    std::cout << wortRestore.getWid() << " " << std::string(wortRestore.getW()) << std::endl;
}
4

1 回答 1

1

据我所知,链接的对话仍然准确。(该线程上的大多数消息都是我,我是 Cap'n Proto 的作者......)

在实践中,支持 any 的缓冲区很可能std::string是字对齐的——但不能保证。从 a 读取时std::string,您可能应该检查指针是否对齐(例如 by reinterpret_cast<uintptr_t>(str.data()) % sizeof(void*) == 0)。如果对齐,您可以reinterpret_cast将指针指向capnp::word*. 如果未对齐,则需要制作副本。在实践中,代码可能永远不会复制,因为std::string的后备缓冲区可能总是对齐的。

在写作方面,避免复制是比较棘手的。您编写的代码实际上制作了两个副本。

一个在这里:

kj::Array<capnp::word> dataArr = capnp::messageToFlatArray(message);

这里有一个:

std::string data(bytes.begin(), bytes.end());

看起来 LevelDB 支持一种名为 的类型Slice,您可以在编写时使用它来代替std::string,以避免第二次复制:

leveldb::Slice data(bytes.begin(), bytes.size());

这将引用底层字节而不是复制,并且应该可以在所有 LevelDB 写入函数中使用。

不幸的是,这里不可避免地需要一份副本,因为 LevelDB 希望该值是一个连续的字节数组,而 Cap'n Proto 消息可能被分成多个段。避免这种情况的唯一方法是 LevelDB 添加对“收集写入”的支持。

于 2018-04-09T21:06:32.493 回答