Using a Qt-based DLL in a non-Qt application

darron picture darron · Jan 27, 2010 · Viewed 12.8k times · Source

Am I doing it right?

A client of mine has a group where I'm developing Qt-based client-server stuff with a lot of fun widget stuff and sockets.

Another group within the company wants to use a wrapped version of the QTcpSocket-based client data provider classes. (Which does basically what it sounds like, provides data from the server to the client displays)

However, that group has a huge application built mostly with MFC, and that is simply not going to change any time soon. The Qt-based DLL is also delay-loading so that it can be deployed without this feature in certain configurations.

I've got it working, but it's a little hacky. Here's my solution at the moment:

The DLL wrapper class constructor calls QCoreApplication::instance() to see if it's NULL or not. If it's NULL, it assumes it's in a non-Qt app, and creates a QCoreApplication instance of it's own:

if (QCoreApplication::instance() == NULL)
{
    int argc = 1;
    char* argv[] = { "dummy.exe", NULL };
    d->_app = new QCoreApplication(argc, argv);  // safe?
}
else
    d->_app = NULL;

It then will set up a windows timer to occasionally call processEvents():

if (eventTimerInterval > 0)
{
    // STATE: start a timer to occasionally process the Qt events in the event queue
    SetTimer(NULL, (UINT_PTR)this, eventTimerInterval, CDatabaseLayer_TimerCallback);
}

The callback simply calls the processEvents() function using the timerID as a pointer to the class instance. The SetTimer() docs say when HWND is NULL it ignores the timerID, so this appears to be perfectly valid.

VOID CALLBACK BLAHBLAH_TimerCallback(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
    ((BLAHBLAH*)idEvent)->processEvents(); // basically just calls d->_app->processEvents();
}

I then destroy the QCoreApplication instance as the very last thing in the destructor.

BLAHBLAH::~BLAHBLAH()
{
    .. other stuff

   QCoreApplication* app = d->_app;
   d->_app = NULL;
   delete d;
   if (app != NULL)
       delete app;
}

If the hosting application wishes to time the calls to processEvents() itself, it can pass 0 in for eventTimerInterval and call BLAHBLAH::processEvents() itself.

Any thoughts on this? Porting that app to Qt is not an option. It's not ours.

It appears to work, but there are probably several assumptions being broken here. Can I just construct a QCoreApplication with dummy arguments like that? Is the event queue safe to operate in this manner?

I don't want this blowing up in my face later. Thoughts?

Answer

dashesy picture dashesy · May 3, 2012

Studying the Qt code it seems QCoreApplication is needed to dispatch system-wide messages such as timer events. Things like signal/slots and even QThreads do not depend on it unless they are related to those system-wide messages. Here is how I do this in a shared library (in a cross platform way using Qt itself) and I actually do call exec, because processEvents() alone does not process everything.

I have a global namespace:

// Private Qt application
namespace QAppPriv
{
    static int argc = 1;
    static char * argv[] = {"sharedlib.app", NULL};
    static QCoreApplication * pApp = NULL;
    static QThread * pThread = NULL;
};

I have an OpenApp method in a QObject (that is moc'ed) like this:

// Initialize the app
if (QAppPriv::pThread == NULL)
{
    // Separate thread for application thread
    QAppPriv::pThread = new QThread();
    // Direct connection is mandatory
    connect(QAppPriv::pThread, SIGNAL(started()), this, SLOT(OnExec()), Qt::DirectConnection);
    QAppPriv::pThread->start();
}

And here is OnExec slot:

if (QCoreApplication::instance() == NULL)
{
    QAppPriv::pApp = new QCoreApplication(QAppPriv::argc, QAppPriv::argv);
    QAppPriv::pApp->exec();
    if (QAppPriv::pApp)
        delete QAppPriv::pApp;
}

So far it seems to be working fine, I am not sure if I need to delete the app at the end, I will update my answer if I find something.