March 3, 2021
Hot Topics:

C++ Tip: Versioning Serialized Files with Managed C++

  • By Tom Archer
  • Send Email »
  • More Articles »

Welcome to this week's installment of .NET Tips & Techniques! Each week, award-winning Architect and Lead Programmer Tom Archer demonstrates how to perform a practical .NET programming task using either C# or Managed C++ Extensions.

In the previous article on .NET serialization using Managed C++, I illustrated how to uniquely identify serialized files using a GUID as a class member. In this installment, I'll present a step-by-step technique for versioning serialized files so that a file's application can properly handle multiple versions of its file formats.

A Versioning Example

The following step-by-step instructions use an example class called YourClass that has gone through three (3) versions, with a data member being added with each version. In this example, you'll make sure that any of the three file versions can be successfully opened—and if necessary, converted—with your test application.

  1. Mark the class as serializable and implement the ISerializable interface. As I covered in previous columns, this gives you complete control over the serialization process. You need it here to enable the class to verify the file's format version when the file is being read. Therefore, the first step is to apply the Serializable attribute to the class and derive from (or implement) the ISerializable interface:
  2. using namespace System::IO;
    using namespace System::Runtime::Serialization;
    using namespace System::Runtime::Serialization::Formatters
    __gc class YourClass : public ISerializable
  3. Call an initialization method (implemented and explained shortly) from the class's constructor:
  4. public:
  5. Define a class member that will contain the current version supported by the application. In this example, the YourClass class supports three versions (1.00, 1.10, and the most current, 1.20) for testing purposes:
  6. protected:
      static String* fileVersion = S"1.20";    // updated each time
                                               // file format is
                                               // changed
  7. Add the class's data members. For maintenance purposes, I like to block off each version's members with comments, so that anyone maintaining my code can easily see which members were added in which version:
  8. protected:
      // version 1.00
      String* fieldA;
      // version 1.10
      String* fieldB;
      // version 1.20
      String* fieldC;
  9. Implement the initialization method that is called from the class's constructor and initialize the class's members:
  10. protected:
      void Init()
        this->fieldA = S"Version 1.00 field";
        this->fieldB = S"Version 1.10 field";
        this->fieldC = S"Version 1.20 field";
  11. Implement the ISerializable::GetObjectData method to save the desired data when the object is serialized to disk:
  12. public:
      void GetObjectData(SerializationInfo *si, StreamingContext sc)
        si->AddValue(S"fileVersion", this->fileVersion);
        si->AddValue(S"fieldA", this->fieldA);
        si->AddValue(S"fieldB", this->fieldB);
        si->AddValue(S"fieldC", this->fieldC);
  13. Now, to the main part of checking version information. The first thing to do is call the initialization routine to set any member variables. This is necessary because if the code is reading in a "1.10" formatted file, members added in subsequent versions won't be read and therefore won't have values.

    The "fileVersion" field is then read from the serialized file into a temporary variable (tempFileVersion). Then, this temporary value is compared to the class' static fileVersion value. If the file format is newer than the class's value, an exception is thrown. This is because you would never want to convert a file that was created with a newer version of the application and have the user lose data.

    From there, each version is represented. Note how the comparisons work. So that code is not duplicated, the first comparison checks for the lowest supported version and higher. Therefore, the first comparison is stating that if the file's version value is 1.00 or anything greater, read the fieldA value. The code then continues through each version until it reaches the current version, where the version number must match exactly.

    In summary, this method ensures that any current or older versions of the file format will be read successfully (and written as the current version), whereas any files with a format newer than the application will be rejected. Obviously, you might need to insert application-specific logic to determine which versions you can automatically convert based on the application's ability to initialize members introduced in later file format versions:

      YourClass(SerializationInfo *si, StreamingContext sc)
        // Check version
        String* tempFileVersion = si->GetString(S"fileVersion");
        // Make sure that the file format is not newer than the
        // application
        if (String::Compare(this->fileVersion, tempFileVersion) < 0)
          throw new Exception(S"Invalid version!");
        if (String::Compare(tempFileVersion, S"1.00") >= 0)
           // 1.00+
          this->fieldA = si->GetString(S"fieldA");
        if (String::Compare(tempFileVersion, S"1.10") >= 0)
           // 1.10+
          this->fieldB = si->GetString(S"fieldB");
        if (String::Compare(tempFileVersion, S"1.20") == 0)  // 1.20
          this->fieldC = si->GetString(S"fieldC");

Page 1 of 2

This article was originally published on May 26, 2004

Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date