序列化C风格的结构(使用C ++)

时间:2010-10-14 23:12:23

标签: c++ c serialization

使用memcpy序列化struct对象是不是很邪恶?

在我的一个项目中,我正在执行以下操作:我记忆一个struct对象,base64对其进行编码,并将其写入文件。我在解析数据时做了反向。它似乎工作正常,但在某些情况下(例如,当使用WINDOWPLACEMENT作为Windows Media Player的HWND时),结果是解码数据与sizeof(WINDOWPLACEMENT)不匹配。

以下是一些代码片段:

// Using WINDOWPLACEMENT from Windows API headers:
typedef struct tagWINDOWPLACEMENT {
    UINT  length;
    UINT  flags;
    UINT  showCmd;
    POINT ptMinPosition;
    POINT ptMaxPosition;
    RECT  rcNormalPosition;
#ifdef _MAC
    RECT  rcDevice;
#endif
} WINDOWPLACEMENT;


static std::string EncodeWindowPlacement(const WINDOWPLACEMENT & inWindowPlacement)
{
    std::stringstream ss;
    {
        Poco::Base64Encoder encoder(ss); // From the Poco C++ libraries
        const char * offset = reinterpret_cast<const char*>(&inWindowPlacement);
        std::vector<char> buffer(offset, offset + sizeof(inWindowPlacement));
        for (size_t idx = 0; idx != buffer.size(); ++idx)
        {
            encoder << buffer[idx];
        }
        encoder.close();
    }
    return ss.str();
}


static WINDOWPLACEMENT DecodeWindowPlacement(const std::string & inEncoded)
{
    std::string decodedString;
    {
        std::istringstream istr(inEncoded);
        Poco::Base64Decoder decoder(istr); // From the Poco C++ libraries
        decoder >> decodedString;
        assert(decoder.eof());
        if (decoder.fail())
        {
            throw std::runtime_error("Failed to parse Window placement data from the configuration file.");
        }
    }

    if (decodedString.size() != sizeof(WINDOWPLACEMENT))
    {
        // !! Occurs frequently !!
        throw std::runtime_error("Errors occured during parsing of the Window placement.");
    }

    WINDOWPLACEMENT windowPlacement;
    memcpy(&windowPlacement, &decodedString[0], decodedString.size());
    return windowPlacement;
}

我知道使用memcpy在C ++中复制类可能会导致麻烦,因为复制构造函数没有正确执行。我不确定这是否也适用于C风格的结构。或者通过内存转储进行序列化只是未完成

更新: Poco的Base64编码器/解码器中的错误并非不可能,但不太可能。它的测试用例看起来非常彻底:Base64Test.cpp

5 个答案:

答案 0 :(得分:6)

如果您需要在不具有相同字节序和字大小的计算机之间传输这些文件,或者在将来的版本中添加/删除结构中的插槽并且需要保留二进制兼容性,则会遇到问题。

答案 1 :(得分:3)

我不确定operator>>()Poco::Base64Decoder是如何实施的。如果它与istream的{​​{1}}相同,则operator>>() decoder >> decodedString;后可能不包含输入中的所有字符。例如,如果编码字符串中有任何空格字符,则decodedString将读取该空格。

答案 2 :(得分:3)

如果它们只是Plain Old Data (POD),那么做一个memcpy类/结构是可以的,但是如果是这样的话,那么你可以依靠C ++通过复制构造函数为你做复制(存在适用于C ++中的structclass类型。

当然你可以按照你的方式去做 - 我用过memcpy序列化数据的产品之一,通过线路发送数据,客户端应用程序解码字节流以获得数据回来了。

但是如果你有一个选择,你可能想要更高级别的东西,比如boost.serialization,这提供了更多的灵活性和深度指针复制。前面提到的Google ProtoBuffers也可以很好地工作。

以下是一些讨论C ++中序列化方法的线程:

答案 3 :(得分:2)

我不会说这是邪恶的,但我认为在许多情况下它是在寻找麻烦和奇怪的问题。

我知道它已经完成并且它可以工作(我已经看到人们序列化这样的结构以通过网络连接发送),但是它已经指出了许多缺点(不灵活,字节序问题,包含指针,包装等的结构。)

我建议使用更强大的序列化和反序列化数据的方法。我已经听过很多关于Google protocol buffers的好东西,这样的东西会更加灵活,最终可能会让你头疼。

答案 4 :(得分:2)

以你完成的方式序列化数据并不是特别邪恶,如果你知道你留在具有相同字节大小,字大小,字节序等的机器上。因为你正在序列化窗口放置信息,您可能不关心两台不同机器之间的可移植性,只想在同一台机器上的会话之间保存此信息。我猜测你将它存储在注册表中。如果您希望将其他数据移植到其他架构时实际有用的可移植性,那么您可以查看此处发布的许多其他建议,例如Google协议缓冲区等。空白是一个红色的鲱鱼,因为所有WS在base64编码数据流中是无关紧要的,所有解码器都应该忽略它(PoCo确实如此)。我很想知道字符串的大小和失败时的结构。知道这一点可能会让你对这个问题有所了解。