http://www.developer.com/net/cplus/article.php/3359101/C-Tip-Versioning-Serialized-Files-with-Managed-C.htm
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. 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. 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: One way to test this code is to create and save a file with a given version (fileVersion member) such as 1.10, change the fileVersion member to 1.20, and then open the "1.10" file. You'll see that your code automatically converts the "1.10" file to a "1.20" file when you save. Conversely, if you save the file as a "1.20" version, modify the fileVersion to "1.10", and then try to open the file, the code will throw an exception as designed: In the YourClass example (and as I mentioned in the description of the GetObjectData method), the code automatically converts older file formats to the current format. You've probably seen many applications that—upon opening an older-formatted file—ask you whether or not you want to convert the file. Here's an easy way to implement that: To download the accompanying source code for this tip, click here. The founder of the Archer Consulting Group (ACG), Tom Archer has been the project lead on three award-winning applications and is a best-selling author of 10 programming books as well as countless magazine and online articles.
C++ Tip: Versioning Serialized Files with Managed C++
May 26, 2004
A Versioning Example
using namespace System::IO;
using namespace System::Runtime::Serialization;
using namespace System::Runtime::Serialization::Formatters
::Binary;
...
[Serializable]
__gc class YourClass : public ISerializable
{
};
public:
YourClass()
{
Init();
}
protected:
static String* fileVersion = S"1.20"; // updated each time
// file format is
// changed
protected:
// version 1.00
String* fieldA;
// version 1.10
String* fieldB;
// version 1.20
String* fieldC;
protected:
void Init()
{
this->fieldA = S"Version 1.00 field";
this->fieldB = S"Version 1.10 field";
this->fieldC = S"Version 1.20 field";
}
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);
}
protected:
YourClass(SerializationInfo *si, StreamingContext sc)
{
Init();
// 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");
}
}
Testing the Example
__gc class YourClassSerializer
{
public:
static YourClass* Open(String* fileName)
{
YourClass* yc = NULL;
FileStream* stream = new FileStream(fileName,
FileMode::Open,
FileAccess::Read);
BinaryFormatter* formatter = new BinaryFormatter();
yc = static_cast<YourClass*>
(formatter->Deserialize(stream));
stream->Close();
return yc;
}
static void Save(YourClass* yc, String* fileName)
{
FileStream* stream = new FileStream(fileName,
FileMode::Create,
FileAccess::Write);
BinaryFormatter* formatter = new BinaryFormatter();
formatter->Serialize(stream, yc);
stream->Close();
}
};
try
{
YourClass* yc;
// create and save test file
yc = new YourClass();
YourClassSerializer::Save(yc, S"test.yc");
// open existing file
// yc = static_cast<YourClass*>
(YourClassSerializer::Open(S"test.yc"));
// save current file
// YourClassSerializer::Save(yc, S"test.yc");
}
catch(Exception* e)
{
Console::WriteLine(e->Message);
if (e->InnerException)
Console::WriteLine(e->InnerException->Message);
}
Additional Notes
Download the Code
About the Author