Uniquely Identifying Serialized Files with Managed C++

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 my two previous .NET Tips & Techniques articles, I illustrated how to serialize objects and specific members and how to implement custom serialization. In this installment, I present a very important issue when using serialization in a production environment: correctly identifying the files your application has serialized. This is especially important if your user interface allows the user to select the files to be opened. However, even if your application controls this type of file selection, forces outside your application's control still could tamper with or somehow corrupt the file—forces such as the end-user!

Defining a Guid Class Member to Identify Serialized Files

The technique I favor for uniquely identifying a serialized file is the rather obvious choice of a GUID (Globally Unique Identifier). The .NET System namespace even provides a structure (System::Guid) that provides overloaded operators for easy comparisons between Guid structures. The following basic steps demonstrate how to implement a Guid value as a unique file ID member into a C++ class.

  1. Implement the ISerializable interface. As I covered in the article on custom serialization, implementing this interface gives you complete control over the serialization process, which you need here so that the class can verify the file ID during serialization and throw an exception if the file ID is not correct. The first step is to apply the Serializable attribute to the desired class and to specify that the class derives from (or implements) ISerializable:
  2. using namespace System::IO;
    using namespace System::Runtime::Serialization;
    using namespace System::Runtime::Serialization::Formatters
                          ::Binary;
    ...
    [Serializable]
    __gc class YourClass : public ISerializable
    {
    public:
      YourClass() {}
    ...
    }
    
  3. Generate a GUID value.

    1. This can be done from the Visual Studio .NET Tools menu (Create Guid menu option). Alternatively, you can run the guidgen.exe application from the Windows Start/Run prompt.
    2. When GuidGen starts, it automatically generates a new GUID. Click the Registry Format radio button, and then click the Copy button to copy that format to the Clipboard.
    3. Paste the new GUID value into your class as the value for a static member of your class called something like fileId. Here's an example:
    4. __gc class YourClass : public ISerializable
      ...
      protected:
        static Guid fileId = S"{FA054804-553A-4455-8CA0-
                                3A21167FE8A9}";
      ...
      
  4. Implement the ISerializable::GetDataObject method, the only method defined in the ISerializable interface. It allows you to specify which members are saved and how. It also allows for pre- and post-processing should that need arise. In this case, you simply need to save the Guid member so that the file can be verified when read at a later time. (Note that the Guid member needs to be boxed via the __box keyword as it is a value type.)
  5. __gc class YourClass : public ISerializable
    ...
    public:
      void GetObjectData(SerializationInfo *si, StreamingContext sc)
      {
        si->AddValue(S"fileId", __box(this->fileId));
        // Call AddValue for your other class members
      }
    
    
  6. Verify the Guid value when reading the serialized file. All custom serialized classes should implement the protected constructor that the .NET formatter calls when opening the file. Here, your code will be able to read the saved Guid value and compare it to the class' static file ID value:
  7. __gc class YourClass : public ISerializable
    ...
    protected:
      YourClass(SerializationInfo *si, StreamingContext sc)
      {
        // Verify that the file is the correct format
        String* tempFileId = si->GetString(S"fileId");
        Guid tempGuid(tempFileId);
        if (this->fileId != tempGuid)
        {
          throw new Exception(S"Invalid file type!");
        }
       
        // If we get this far, the file checks out. Read in other
        // members
      }
    ...
    

    The example code above presents two notable issues. First, while the SerializationInfo class provides several methods (GetString, GetBoolean, GetByte, and so forth) to read different types, it offers none for reading a Guid structure. Therefore, you'll need to read it as a String, construct a Guid object from that String, and then use the Guid structure's overloaded equality operator to compare the value.

    A second, less obvious, issue is that the exception being thrown will actually be caught by the .NET Formatter object doing the serialization. When the Formatter object catches the exception thrown from the constructor, it wraps that exception (as the inner exception) with its own exception object. You'll see how this comes into play shortly.

  8. At this point, the class is complete. Now, you need only write two very simple functions to read and write your serialized files. I personally put these into a helper class with static methods called Open and Save. Unfortunately for those of you accustomed to MFC serialization and having the class serialize itself, that won't work with .NET serialization because the Formatter object does the actual serialization and instantiates a new object during the read. This is different from MFC, where the class can simply open the CArchive object and perform its own insertion and extraction. Hence the need for another class—or the client code—to make the calls to perform the serialization:
  9. __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();
      }
    };
    
  10. Finally, you need only call the two static helper class functions to serialize your files. Note the reference to the Exception::InnerException object, per my earlier remarks. One easy way to test this is to create and save a file, modify the Guid member, and then try to open the saved file. If everything works correctly, you'll get an exception stating that the file is not the correct format:
  11. 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);
    }
    

Uniquely Identify Your Serialized Files

In this latest article on .NET serialization using Managed C++, I covered using a GUID to uniquely identify your serialized files to ensure the integrity of your application. In the next article, I'll show you another extremely important issue with using serialization in a production environment—versioning.



About the Author

Tom Archer - MSFT

I am a Program Manager and Content Strategist for the Microsoft MSDN Online team managing the Windows Vista and Visual C++ developer centers. Before being employed at Microsoft, I was awarded MVP status for the Visual C++ product. A 20+ year veteran of programming with various languages - C++, C, Assembler, RPG III/400, PL/I, etc. - I've also written many technical books (Inside C#, Extending MFC Applications with the .NET Framework, Visual C++.NET Bible, etc.) and 100+ online articles.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

  • Live Event Date: September 19, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT In response to the rising number of data breaches and the regulatory and legal impact that can occur as a result of these incidents, leading analysts at Forrester Research have developed five important design principles that will help security professionals reduce their attack surface and mitigate vulnerabilities. Check out this upcoming eSeminar and join Chris Sherman of Forrester Research to learn how to deal with the influx of new device …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds