65.9K
CodeProject 正在变化。 阅读更多。
Home

序列化入门 - 第 2 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (21投票s)

2002年2月18日

CPOL

3分钟阅读

viewsIcon

215841

本教程介绍如何在序列化过程中处理无效数据存储和支持版本控制。

本文是关于序列化的三部分教程的第二部分。

  • 第一部分介绍了序列化的基础知识。
  • 第二部分解释了如何优雅地处理读取无效数据存储并支持版本控制。
  • 第三部分描述了如何序列化复杂对象。

第一部分中,我们看到了如何使用CArchive通过一个serialize()方法来序列化一个简单的对象,就像这样

  int CFoo::serialize
    (CArchive* pArchive)
  {
    int nStatus = SUCCESS;

    // Serialize the object ...
    ASSERT (pArchive != NULL);
    TRY
    {
      if (pArchive->IsStoring()) {
         // Write employee name and id
         (*pArchive) << m_strName;
         (*pArchive) << m_nId;
      }
      else {
         // Read employee name and id
         (*pArchive) >> m_strName;
         (*pArchive) >> m_nId;
      }
    }
    CATCH_ALL (pException)
    {
      nStatus = ERROR;
    }
    END_CATCH_ALL

    return (nStatus);
  }

这段代码存在一个问题。如果我们错误地读取了一个不包含预期信息的数据文件怎么办?如果数据文件不包含CString后跟一个int,我们的serialize()方法将返回错误。这很好,但如果我们能识别这种情况并返回一个更具体的状态码,比如INVALID_DATAFILE,那就更好了。我们可以通过使用对象签名来检查我们是否正在读取有效的数据文件(即包含CFoo对象的那个)。

对象签名

对象签名只是一个标识对象的字符串(例如:“FooObject”)。我们通过修改类定义来为CFoo添加一个签名

  class CFoo
  {
    ...

    // Methods
    public:
      ...
      CString getSignature();

    // Data members
      ...
    protected:
      static const CString  Signature;  // object signature
  };

签名在Foo.cpp中声明

  // Static constants
  const CString CFoo::Signature = "FooObject";

接下来,我们修改serialize()方法,在序列化对象的数据成员之前序列化签名。如果遇到无效签名,或者缺少签名,则我们很可能试图读取一个不包含CFoo对象的数据存储。这是读取签名对象的逻辑

Using a signature to validate a data store

这是代码

  int CFoo::serialize
    (CArchive* pArchive)
  {
    int nStatus = SUCCESS;
    bool bSignatureRead = false;

    // Serialize the object ...
    ASSERT (pArchive != NULL);
    TRY
    {
      if (pArchive->IsStoring()) {
         // Write signature
         (*pArchive) << getSignature();

         // Write employee name and id
         (*pArchive) << m_strName;
         (*pArchive) << m_nId;
      }
      else {
         // Read signature - complain if invalid
         CString strSignature;
         (*pArchive) >> strSignature;
         bSignatureRead = true;
         if (strSignature.Compare (getSignature()) != 0) {
            return (INVALID_DATAFILE);
         }

         // Read employee name and id
         (*pArchive) >> m_strName;
         (*pArchive) >> m_nId;
      }
    }
    CATCH_ALL (pException)
    {
      nStatus = bSignatureRead ? ERROR : INVALID_DATAFILE;
    }
    END_CATCH_ALL

    return (nStatus);
  }

您应该确保所有对象都有唯一的签名。实际的签名是什么并不那么重要。如果您正在开发一套产品,那么拥有一个在公司范围内注册对象签名的流程会很有帮助。这样,开发人员就不会错误地将相同的签名用于不同的对象。如果您想让逆向工程您的数据文件更难,则应该使用与对象名称没有明显关联的签名。

版本控制

在产品生命周期内升级产品时,您可能需要通过添加或删除数据成员来修改CFoo的结构。如果您只是发布了新版本的CFoo,那么尝试从数据存储中读取旧版本的对象将会失败。这显然是不可接受的。任何版本的CFoo都应该能够从较旧的序列化版本恢复自身。换句话说,CFoo的序列化方法应该始终向后兼容。这可以通过对对象进行版本控制轻松实现。就像我们添加了对象签名一样,我们添加一个指定对象版本号的整数常量。

  class CFoo
  {
    ...

    // Methods
    public:
      ...
      CString getSignature();
      int     getVersion();

    // Data members
      ...
    protected:
      static const CString  Signature;  // object signature
      static const int      Version;    // object version
  };

对象版本在Foo.cpp中声明。

  // Static constants
  const CString CFoo::Signature = "FooObject";
  const int     CFoo::Version = 1;

接下来,我们修改serialize()方法,在序列化签名之后,序列化对象版本,然后在序列化对象的数据成员之前序列化。如果遇到较新版本,我们试图读取不支持的对象版本。在这种情况下,我们只需返回状态UNSUPPORTED_VERSION

  int CFoo::serialize
    (CArchive* pArchive)
  {
    int nStatus = SUCCESS;
    bool bSignatureRead = false;
    bool bVersionRead = false;

    // Serialize the object ...
    ASSERT (pArchive != NULL);
    TRY
    {
      if (pArchive->IsStoring()) {
         // Write signature and version
         (*pArchive) << getSignature();
         (*pArchive) << getVersion();

         // Write employee name and id
         (*pArchive) << m_strName;
         (*pArchive) << m_nId;
      }
      else {
         // Read signature - complain if invalid
         CString strSignature;
         (*pArchive) >> strSignature;
         bSignatureRead = true;
         if (strSignature.Compare (getSignature()) != 0) {
            return (INVALID_DATAFILE);
         }

         // Read version - complain if unsupported
         int nVersion;
         (*pArchive) >> nVersion;
         bVersionRead = true;
         if (nVersion > getVersion()) {
            return (UNSUPPORTED_VERSION);
         }

         // Read employee name and id
         (*pArchive) >> m_strName;
         (*pArchive) >> m_nId;
      }
    }
    CATCH_ALL (pException)
    {
      nStatus = bSignatureRead && bVersionRead ? ERROR : INVALID_DATAFILE;
    }
    END_CATCH_ALL

    return (nStatus);
  }

我们的CFoo的版本 1 包含 2 个数据成员 - 一个CStringm_strName)和一个intm_nId)。 如果我们在版本 2 中添加第三个成员(例如:int m_nDept),我们需要决定在读取旧版本的对象时将m_nDept初始化为什么。 在此示例中,我们将m_nDept初始化为-1,表示员工的部门代码为“Unknown”。

  class CFoo
  {
    ...
    // Data members
    public:
      CString  m_strName;  // employee name
      int      m_nId;      // employee id
      int      m_nDept;    // department code (-1 = unknown)
  };

我们还需要将对象版本号在Foo.cpp中增加到2

  const int CFoo::Version = 2;

最后,我们修改serialize()中读取对象的部分,以便在读取旧版本的数据文件时将m_nDept初始化为-1。请注意,该文件始终保存为最新版本。

  int CFoo::serialize
    (CArchive* pArchive)
  {
    ...
    // Serialize the object ...
    ASSERT (pArchive != NULL);
    TRY
    {
      if (pArchive->IsStoring()) {
         ...
         // Write employee name, id and department code
         (*pArchive) << m_strName;
         (*pArchive) << m_nId;
         (*pArchive) << m_nDept;
      }
      else {
         ...
         // Read employee name and id
         (*pArchive) >> m_strName;
         (*pArchive) >> m_nId;

         // Read department code (new in version 2)
         if (nVersion >= 2) {
            (*pArchive) >> m_nDept;
         }
         else {
            m_nDept = -1; // unknown
         }
      }
    }
    CATCH_ALL (pException)
    {
      nStatus = bSignatureRead && bVersionRead ? ERROR : INVALID_DATAFILE;
    }
    END_CATCH_ALL

    return (nStatus);
  }

结论

到目前为止,我们已经处理了为序列化简单对象(即那些包含易于序列化的数据类型)提供强大的支持。 在 第三部分 中,我们将看到如何序列化任何类型的对象。

© . All rights reserved.