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

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

  • Live Event Date: August 19, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT You deployed your app with the Bluemix PaaS and it's gaining some serious traction, so it's time to make some tweaks. Did you design your application in a way that it can scale in the cloud? Were you even thinking about the cloud when you built the app? If not, chances are your app is going to break. Check out this upcoming webcast to learn various techniques for designing applications that will scale successfully in Bluemix, for the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds