Tuesday, 4 November 2014

QMetaType in plugins

I've learned that for some uses QMetaType just does not work.

Imagine that you want to read types of parameters of some signal or slot of QObject. You also know that all parameters to this method are pointers to QObject-based types. The best way to do that is to:
  1. register your type with qRegisterMetaType<QObjectDerivedType>
  2. read parameter meta type id with QMetaMethod::parameterType(int)
  3. get QMetaObject of this parameter with QMetaType::metaObjectForType(int)
In most cases it works. But not when QObjectDerivedType lives inside a plugin that is dynamically loaded and unloaded several times during program execution (and this whole sequence is run on each plugin load).

On first load everything works perfectly. On second (or third or next, it is rather random and probably depends on allocation patterns in application) your QMetaObject from point 3 is invalid and using it causes access violation. Why? QMetaType remembers address of QMetaObject for each QObject based class pointer per program execution. On load a new QMetaObject is constructed for each type that lives in plugin. And on unload its memory is freed. In result the stored pointer just must to be invalid.

Maybe I missed something, but I was not able to get it to work properly. My solution is to use custom paramter-QMetaObject mapping instead of relying on QMetaType. It is based on QMetaMethod::parametrTypes() method that returns type name and a map of type-name to QMetaObject pointer that is created on each plugin load and passed around. I'm not very happy with this solution, because it is prone to namespace issues and it does not allow usage of types that are no preregistered in plugin (on the other side, QMetaType has the same problem). Hopefully C++ reflection will become real some day.

But there is one more thing that makes me happy - replacing first solution with second in my dependency framework Injeqt took some time and required non-trivial changes in library. But API and ABI remained the same - there was no need to recompile Kadu 2 to work with new version. It means that my usage of private and public interfaces, pimpls and anonymous namespaces is at least proper. This also closed last bug that blocked 1.0 release. This means I can now prepare beta 0.9 and focus on developing Kadu (also to test if Injeqt is really ready to be released).

7 comments:

  1. This issue has been discussed a few times before, the usual response is "don't unload plugins".

    http://thread.gmane.org/gmane.comp.lib.qt.user/13464

    ReplyDelete
    Replies
    1. That does not seem like a proper solution to me. In Kadu users are deciding when to load/unload plugins. And what is the point of having plugins that cannot be unloaded?

      Delete
    2. Users should be enabling and disabling plugins, not loading and unloading plugins. Disabled plugins should not be unloaded.

      Delete
    3. You are right, what users do is enabling/disabling plugins (by making checkboxes checked/unchecked, for example). But what is the reason (except the issue with QMetaType) to keep disabled plugins loaded? I don't see any. So, when a user disables a plugin it may be implicitly unloaded after performing all cleanup stuff.

      Delete
    4. Unloading plugins allows replacing and upgrading parts of program during run time, that is nice. Also, there is nothing in Qt documentation about not-unloading and QMetaType problems: http://qt-project.org/doc/qt-5/qpluginloader.html#unload

      Delete
    5. Seems like a great idea. Unloading a plugin does not work, then do not do it. It's not a bug, it is a feature.

      Delete
  2. This comment has been removed by the author.

    ReplyDelete