Wednesday, July 28, 2010

QMetaEnum Magic - Serializing C++ enum's

Qt has a number of useful classes and utilities; among these is QMetaEnum which provides the ability to serialize and deserialize C++ enumerations through the use of moc, the meta-object compiler.

Method 1 - Enum's within a QObject (fairly common)

First, let's take a look at a common example of an enum within a class as provided in the "Secrets of Qt Full" developer days presentation:

class Person : public QObject {
    Q_OBJECT
    enum  Qualification { Student, ... };
    Q_ENUMS(Qualification)
};

When moc runs on the above and sees Q_OBJECT it adds a staticMetaObject member (that's static, big surprise) of type QMetaObject. This QMetaObject instance has an indexOfEnumerator and an enumerator member functions that make it possible to access the QMetaEnum representing the Person::Qualification enum.

The code to access the QMetaEnum member looks something like the following:

const QMetaObject &mo = Person::staticMetaObject;
int index = mo.indexOfEnumerator("Qualification"); // watch out during refactorings
QMetaEnum metaEnum = mo.enumerator(index);

We can then use the QMetaEnum object as follows:

// first, let's convert from an enum value to a string
Qualification q = Person::Student;
QByteArray str = metaEnum.valueToKey(q);
// str now contains "Student"

// second, let's convert from a string to an enum value:
int value = metaEnum.keyToValue("Student");
Qualification q = static_cast(value);

Method 2 - Without a QObject-based class (less common)

UPDATE: Based on Vladislaw's comment, I've done further research and added an additional post which demonstrates how to accomplish this in namespaces other than Qt.

Another less well-known feature is that with only minor effort you can use this same feature without needing a QObject-based class as a container. For this example, I'll use the Qt::Key enum. Since we don't want a QObject based class, rather than using Q_OBJECT we'll use the Q_GADGET macro within a container:

class Container {
 Q_GADGET
 Q_PROPERTY(Qt::Key key_enum);
public:
 Qt::Key key_enum;
};

Like Q_OBJECT, Q_GADGET creates a staticMetaObject member that we'll need to use to access the QMetaEnum:

const QMetaObject &mo = Container::staticMetaObject;
int prop_index = mo.indexOfProperty("key_enum");
QMetaProperty metaProperty = mo.property(prop_index);
QMetaEnum metaEnum = metaProperty.enumerator();

Unlike the first example, in this example I referenced the Qt::Key typed key_enum property to get a hold on the QMetaEnum object that I can now use exactly as before:

// convert to a string
Qt::Key key = Qt::Key_Down;
QByteArray str = metaEnum.valueToKey(key);
qDebug() << "Value as str:" << str;

// convert from a string
int value = metaEnum.keyToValue("Key_Up");
key = static_cast(value);
qDebug() << "key is Key_Up: " << (key == Qt::Key_Up);

Which results in the following output:

Value as str: "Key_Down" 
key is Key_Up:  true

And there you have it... an easy way to leverage Qt to serialize C++ enums.

Friday, April 30, 2010

The leak that wasn't a bug (i.e. one reason programming is hard)

Bugs are a pain, they eat time, destroy quality, and seem to almost always happen at inopportune times. What's even worse is when the bug is in a 3rd party library or somebody else's code.

My application needed to monitor a directory for changes to files. Unfortunately, Java 6 doesn't yet have support for directory monitoring, so I started looking around for different libraries. I found two that were good candidates, JNotify and JPoller but ultimately decided to use JPoller because it was a pure Java implementation and was fairly well documented.

It didn't take long to integrate JPoller into my application. Although it wasn't designed for testability in mind, I expected the tests to go fairly quickly, and they did. Everything looked good. After some refactoring I started to implement my next feature. Something was wrong. My file wasn't picked up. The first and second times I just assumed I placed the files in the wrong directory... but I hadn't.

I started looking through the JPoller source. I learned a few things, made some changes and continued the bug hunt. But, to no avail. Despite the logic looking perfectly sound it wasn't working right all the time. It was a race condition.

As luck would have it, I had a startling insight. What if the timestamps were wrong?

After quite a bit of investigation I discovered the insight was correct. Despite the files having a timestamp in milliseconds, it was only accurate up to one second. This allowed the file to be created at 850 milliseconds after some second and to have a timestamp of 850 milliseconds earlier.

The Law of Leaky Abstractions

I had just been struck by the Law of Leaky Abstractions. On a Windows NTFS file system the resolution was accurate to 100 nanoseconds, but on Linux it was accurate to one second. And Windows FAT filesystems were only accurate to two seconds (for modification time).

Despite the cross-platform nature of Java, I had to know and understand file system specifics in order to hunt down and kill this bug that wasn't a bug. Instead, it was a leak, and one that had to be carefully worked around in order to maintain the cross-platform nature of Java.

Programming is hard. It's never enough to know a single language or a single platform. Sooner or later a leaky abstraction forces you into other realms.