Microsoft & .NETVisual C#C++ Tip: Uniquely Identifying Serialized Files with Managed C++

C++ Tip: Uniquely Identifying Serialized Files with Managed C++

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

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:
  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
      }
    

    (Note that the Guid member needs to be boxed via the __box keyword as it is a value type.)

  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.

Download the Code

To download the accompanying source code for this tip, click here.

About the Author

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.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories