October 11th, 2011

What's a Hoosier?

I spent some time in the basement this evening working on the copy of "What's a Hoosier?", the album by Juanita Coulson and Michael Longcor that I'd pulled off my DAT master a while back. First, I had to perform a sample rate conversion, because the DAT had been at 48 KHz -- not nearly so useful as 44.1 KHz for making a CD. Then I had to cut the single WAV file up into the component songs and fix up the cuts at the beginning and end of each track. Finally, I burned a CD to listen to.

I still need to get a cover. I still need to find and mix down the two bonus tracks for the album.

But it's progress.

Serialization and MFC CMap

One of the nice things about MFC's serialization routines is that if you write your code correctly, you should be able to take a file that was written out by a 32-bit application and successfully read it into a 64-bit version of the same application. The trick is not doing things that will cause the serialization code to break as you move between the 32-bit and 64-bit compilers.

Enter CMap. CMap is a lovely little template class that allows you to take a key of arbitrary type and locate a value of some other arbitrary type. You can even serialize your CMap objects out to your archive and read them back in.

The problem occurs when you're trying to read and write a complex type, like an MFC class, as either your key or your value. CMap uses the SerializeElements function in order to persist the keys and values and -- by default! -- writes an exact copy of the bits that existed in your object to the archive.

This is almost certainly not what you wanted to happen if your key or value is an MFC class, especially if it contains any virtual functions -- which it will, if you derived it from CObject, or any pointers. See, one of the things that's hidden inside your class is a pointer to the virtual function table for the class. I cannot imagine any good outcomes from reading in a previously stored value of that pointer.

But perhaps you're fortunate and don't actually call any of the virtual functions that you might try to find via that pointer. You're still going to get clobbered when you try to move between a 32-bit and a 64-bit version of your application, because the pointer that had been four bytes long is now eight bytes long and when the default implementation of SerializeElements takes the sizeof( YourClass), you get a different value on the two systems. Hilarity -- and a CArchiveException -- ensues.

The happy news is that you can easily write a templated version of SerializeElements that will call CArchive::SerializeClass() to write out the class header for your key and/or value followed by a call to serialize the underlying object. Or if your CMap stores a pointer to your key or value class, you can use the insertion/extraction operators, which is even simpler. And then your CMap classes will serialize correctly whether you're on a 32-bit or a 64-bit system.

Isn't that easy?