The power of qobject_cast

One feature we wanted to implement in Tepee3D was a Services Manager that would allow various classes to interact with Services provided through shared libraries. Those services are axed toward database management, web services access and platform specific interactions such as posting notifications on Android and so on. That mechanism allows us to add new features during the course of the developement without having to modify the main application’s structure or void any previous work.

When the application is launched, the Service Manager looks for shared libraries in a given directory. For each service library found, it is initialized and then waits for subscribers to connect to it.

The code below illustrates how libraries are loaded from the local libraries directory of the application.


void Services::ServicesManager::loadServicesLibraries()
{
 QDir serviceDirectory = QCoreApplication::applicationDirPath();

 // GO TO LIB DIRECTORIES
#if defined(Q_OS_WIN)
 if (serviceDirectory.dirName().toLower() == "debug" || serviceDirectory.dirName().toLower() == "release")
 serviceDirectory.cdUp();
#elif defined(Q_OS_MAC)
 if (serviceDirectory.dirName() == "MacOS")
 {
 serviceDirectory.cdUp();
 serviceDirectory.cdUp();
 serviceDirectory.cdUp();
 }
#endif
 serviceDirectory.cd(SERVICE_LIBRARIES_DIRECTORY);

// LOAD ALL SERVICES LIBRARIES FOUND IN DIRECTORY
 foreach (const QString &filename, serviceDirectory.entryList(QDir::Files))
{

QPluginLoader loader(serviceDirectory.absoluteFilePath(filename));
 ServiceInterface* service = qobject_cast<ServiceInterface *>(loader.instance());
 if (service)
 {
 this->services.push_back(service);
 service->initLibraryConnection(this);
}
 else
 {
 qCritical() << "ERRORS : "<< loader.errorString();
}
 }
}

All services libraries must implement a common interface that allows the Service Manager to ask a service to register with a user but without having to know any specifics about the service in itself. This interface is described below :


#ifndef SERVICEINTERFACE_H
#define SERVICEINTERFACE_H

#include <QObject>
#include <QtPlugin>

namespace Services
{

class ServiceInterface
{

public :
 virtual void initLibraryConnection(QObject *parent) = 0;
 virtual bool connectServiceToUser(QObject *user) = 0;
 virtual bool disconnectServiceFromUser(QObject *user) = 0;
 virtual QObject* getLibraryQObject() = 0;
};

}
Q_DECLARE_INTERFACE(Services::ServiceInterface, "com.tepee3d.Services.ServiceInterface/1.0")

#endif // SERVICEINTERFACE_H

Each service must define an interface that subscribers have to implement in order to receive  services callbacks or any other function that is necessary for the service to interact properly with a subscriber.

Let’s say we want a class to be able to use the database service, therefore it must inherit from DatabaseServiceUserInterface interface and implement the methods of that interface that define how SQL results are transmitted once a query has been executed.


#ifndef DATABASEUSER_H
#define DATABASEUSER_H

#include <QSqlRecord>
#include <QList>

namespace Services
{
// YOU NEED TO IMPLEMENT THIS INTERFACE IN ORDER TO RECEIVE SQL RESULTS
class DatabaseServiceUserInterface
{
public :
 virtual void receiveResultFromSQLQuery(QList<QSqlRecord> result, int id, void *data) = 0;
// SIGNAL
 //void executeSQLQuery(const QString& query, QObject *sender, int id, const QString &dbName, void *data);

};
}
Q_DECLARE_INTERFACE(Services::DatabaseServiceUserInterface, "com.tepee3d.Services.DatabaseServiceUserInterface/1.0")

#endif // DATABASEUSER_H

The commented signal declaration shows how you can perform a SQL query from your class. Unfortunately, as of now there is no way of putting signals definitions in interfaces as Qt signals can only be implement in classes inheriting for QObject and having the Q_OBJECT macro in their definition. Why not make the interface inherit QObject then ? Simply because you cannot inherit multiple times from QObject which could be the case if you were to implement more than one interface.

For our class to be able to use a given service, it then must ask the ServiceManager to be connected to the services. In turn, for each service, the Service Manager will ask the service to connect with our class by calling the connectServiceToUser method of the service and passing the subscriber as parameter. To maintain a generic behavior, the Service Manager doesn’t differentiate between services libraries.

However, our class should only be able to subscribe to services it can interact with, in other words only to services whose interfaces has been implemented.

This means that the connect to service method of our Service must check that the subscriber is of the right type. One way to do it would be to use a dynamic_cast and check whether it returns NULL or not. If not than proceed with the connection.

Unfortunately, the behavior of using dynamic_cast when using shared libraries is undefined, meaning that the cast might sometimes work and sometimes not, even though our subscriber implements the right interface.

That’s where qobject_cast comes into play. Unlike a dynamic_cast, qobject_cast doesn’t rely on runtime type checking (rttc), all takes place at compile time. That allows qobject_cast to work even across libraries boundaries which is great when you need to check plugin instances or libraries for a give type.

For it to work, your class has to inherit from QObject and declare the interfaces it implements using Q_INTERFACES. Similarly the interface declaration must be followed by the Q_DECLAR_EINTERFACE statement.That way, the Qt precompiler can perform the necessary checks at compile time instead of the runtime.

As an example here is the connectServiceToUser method of the database service library and a class implementing the DatabaseServiceUserInterface so that it can be registered to it.


bool DatabaseThread::connectServiceToUser(QObject *user)
{
 qDebug() << "Connecting user to DatabaseServices";
 // SQL
 if (qobject_cast<Services::DatabaseServiceUserInterface*>(user) != NULL)
 return QObject::connect(user, SIGNAL(executeSQLQuery(const QString &, QObject *, int, const QString&, void *)),
 this, SIGNAL(executeSQLQuery(const QString&,QObject*,int, const QString &, void *)));
 qWarning() << "Object does not implement DatabaseServiceUserInterface";
 return false;
}

If the user if of the DatabaseServiceUserInterface type, the executeSQLQuery signal is connected to the database service library and the user can perform SQL queries by simply emitting that signal.


namespace Room
 {
 class RoomManager;

 class RoomLoader : public QObject, public Services::DatabaseServiceUserInterface
 {
 Q_OBJECT
 Q_INTERFACES(Services::DatabaseServiceUserInterface)

 private:
 RoomLoader(QObject *parent = 0);

 static RoomLoader* instance;

// ...

 public :
 static RoomLoader* getInstance(QObject *parent = 0);
 ~RoomLoader();

// ...

 void receiveResultFromSQLQuery(QList<QSqlRecord> result, int id, void *data);

 signals :
 void executeSQLQuery(const QString &query, QObject *sender, int id, const QString &dbName, void *data = NULL);
 };
 }

Unfortunately it also has a few drawbacks, one of which is that all instances your are going to cast must inherit from QObject at some point. That means it cannot be used to cast an instance that implements the interface but who’s not a QObject. Not really annoying but you’ll find yourself having to add methods that return the QObject* instance of your class just to perform a qobject_cast.

Besides that, it is cleaner and faster to use than a dynamic_cast and should be favored over it when developing with Qt.

Maybe in the near future, we will be able to add signal declarations in interfaces as well and it would make for a really nice way of having user know they can use those without having to put it as a comment in the code.

In any case, all the source code above can be found at :

https://gitorious.org/tepee3d/

For the ServiceManager more precisely at :

https://gitorious.org/tepee3d/tepee3d/trees/master/Tepee3DEngine/Services

A sample database service implementation at :

https://gitorious.org/tepee3d/tepee3d/trees/master/ServicesLibraries/ManageDatabaseService

And the various headers and interfaces at :

https://gitorious.org/tepee3d/tepee3d/trees/master/Tepee3DEngine/DeveloperAPIFiles/Services

Comments, critics and advice are welcome to improve my posts.

Advertisements

2 thoughts on “The power of qobject_cast

  1. Interesting approach !
    I didn’t think of making the connect calls inside the libraries.

    What do you think of connecting the libraries directly in Services::ServicesManager::loadServicesLibraries() (in your case) ? Have you considered this option, and if yes what made you decide for your solution ?

    I have a case where I need to connect two libraries (plugins) and I actually prefer to handle these connections from the class loading everything – I find it cleaner.
    But of course, this means I need a QObject instance of my subclasses – I created a function returning the QObject of every plugin I load.
    (Just a short : QObject *MyClassInterface::obj() { return this; } does the trick)

    Anyway, great post ! Clear, concise with useful information. I didn’t know qobject_cast isn’t done at runtime ! Good to know 🙂

  2. Hey !
    Thanks for the comment.

    You’re right that would have been cleaner to do it in loadServicesLibraries but that would have required us to know each of the services libraries we want to use (at least their interface).

    In the project we work on, we first have services libraries that are loaded at runtime and have to implement the ServiceInterface and can provide various features from database access to camera access. Then we have another set of plugin libraries that are visual widgets we load in rooms that implement the PluginBase interface. These widgets can be developed by outside developers but in order to ease their development, they can implement services’ interfaces. For example if I want database access in my widget, I have to implement DatabaseServiceUserInterface, if I want to implement web services I have to implement WebServicesUserInterface and so on.

    So when we load our Services Libraries, we have no way to know how many widgets will implement a given service and we don’t know if they implement all or none of the interfaces associated to the services libraries.

    So by letting each service library handle the connections we can do a loop :
    for each widget :
    for each service:
    service->connectServiceToUser(widget)

    The register method in the DatabaseService library would do a check like this :

    if (qobject_cast(widget) != NULL)
    QObject::connect(widget, SIGNAL(…) …)
    else
    qDebug() << "Widget doesn't implement DatabaseServiceUserInterface"

    and in the WebService library :

    if (qobject_cast(widget) != NULL)
    QObject::connect(widget, SIGNAL(…) …)
    else
    qDebug() << "Widget doesn't implement WebServiceUserInterface"

    but from the application's point of view all services are ServicesInterface instances and all widgets are PluginBase.

    That way if a widget doesn't implement the interface needed to use a service, the service library doesn't connect it and the program goes on.

    So in short, by letting each library handle the connections, services libraries just need to implement the ServiceInterface and because we don't know if a subscriber implements a given service's interface, letting the service itself check a generic way of doing it.

    In your case, if your two plugins implement the same interface/signals and assuming you're not connecting plugins to other plugins, it is cleaner to do it in the method loading the plugins.

    I hope I was clear enough in my explainations. Otherwise, please let me know and I'll try to be clearer.

    By the way, I also use the "return this" trick !

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s