Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Event Loop in Qt-based DLL in a non-Qt application

I was searching the whole web for an answer but didn't find the solution for my problem. Or maybe I did but because I am a beginner to C++/programming/Qt I didn't understand them. The closest thing was a question here Using a Qt-based DLL in a non-Qt application. I tried to use this method but so far unsuccessfully.

I try to create a DLL, it's the API for our USB device. The library should work on non-Qt applications too. I have PIMPL-ed all Qt stuff and private classes so the code below is one layer under the public classes. I am using QSerialPort and a lot of SIGNAL/SLOT so I need the QCoreApplications event loop. The ReaderSerial is where Qt stuff begins it also instantiate another class where the QSerialPort running in a different QThread.

At this moment my problem is the whole thing crashes on error: “QTimer can only be used with threads started with QThread”

I guess my Qt-based Classes like ReaderSerial don't "see" the QCoreApp event loop or something like that. So my question is how to provide the QCoreApplication event loop to my DLL so all Qt-based classes I created will work and I will be able to call methods from my DLL.

Thank you very much for answers.

reader_p.h

class ReaderPrivate
{
public:
   ReaderPrivate();
   ~ReaderPrivate();

   void static qCoreAppExec();

   ReaderSerial *readerSerial;

   void connectReader(std::string comPort);
   void disconnectReader();
};

reader.cpp

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

ReaderPrivate::ReaderPrivate()
{
    std::thread qCoreAppThread(qCoreAppExec);
    qCoreAppThread.detach();

    readerSerial = new ReaderSerial;
}

ReaderPrivate::~ReaderPrivate()
{

    delete readerSerial;

}

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

void ReaderPrivate::connectReader(std::string comPort)
{
    readerSerial->openDevice(comPort);
}

void ReaderPrivate::disconnectReader()
{
    readerSerial->closeDevice();
} 

Based on @Kuba Ober answer I created a shared library. It took me some time to understand what's going on and how to make it work, but it still doesn't do what it should. So I am now asking for advice how to make this code work.

apic.h

#include "Windows.h"

extern "C"
{
    __declspec(dllexport) void WINAPI kyleHello();
}

apic.cpp

#include "apic.h"
#include "appthread.h"

void WINAPI kyleHello()
{
    worker->hello();
}

BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID)
{
    static AppThread *thread;

    switch (reason)
    {
    case DLL_PROCESS_ATTACH:
        thread = new AppThread;
        thread->start();
        break;
    case DLL_PROCESS_DETACH:
        delete thread;
        break;
    default:
        break;
    }

    return TRUE;
};

appthread.h

#include <QThread>
#include <QCoreApplication>
#include <QPointer>

#include "worker.h"

static QPointer<Worker> worker;

class AppThread : public QThread
{
public:
    AppThread();
    ~AppThread();

    // No need for the Q_OBJECT
    QPointer<QCoreApplication> m_app;

    void run() Q_DECL_OVERRIDE
    {
        std::cout << "\n AppThread::run";

        int argc;
        char *argv;

        QCoreApplication app(argc, &argv);

        m_app = &app;

        std::cout << "\nAppThread::run before Worker";
        Worker worker_;
        worker = &worker_;

        std::cout << "\nAppThread::run before app.exec";
        app.exec();
    }

    //using QThread::wait(); // This wouldn't work here.
};

appthread.cpp

#include "appthread.h"

AppThread::AppThread()
{
    std::cout << "\n AppThread::ctor";
}

AppThread::~AppThread()
{
    std::cout << "\n AppThread::dtor \n";
    m_app->quit();
    wait();
}

worker.h

#include <QObject>
#include <QDebug>
#include <iostream>

class Worker : public QObject
{
    Q_OBJECT
    Q_INVOKABLE void helloImpl()
    {
        std::cout << "I'm alive.";
        //qDebug() << "I'm alive.";
    }

public:
    Worker();

    void hello();
};

worker.cpp

#include "worker.h"

Worker::Worker()
{
    std::cout << "\nWorker::ctor";
    hello();
}

void Worker::hello()
{
    std::cout << "\nWorker::hello()";
    // This is thread-safe, the method is invoked from the event loop
    QMetaObject::invokeMethod(this, "helloImpl", Qt::QueuedConnection);
}

The output from this is usually:

AppThread::ctor  
Worker::hello()  
AppThread::dtor  

sometimes:

AppThread::ctor  
Worker::hello()  
AppThread::run  
AppThread::dtor  

sometimes:

AppThread::ctor  
Worker::hello()  
AppThread::dtor  
QMutex: destroying locked mutex

GitHub repo: https://github.com/KyleHectic/apic.git

like image 909
emKaroly Avatar asked Jul 29 '14 21:07

emKaroly


2 Answers

First of all, if you need QCoreApplication, it will always be your QCoreApplication. You should not attempt any sort of dynamic linking of Qt in your DLL, in case it would end up picking up Qt from the application that is your consumer. There are no guarantees of binary compatiblity between those Qt libraries - this would force your consumer to use the exact same compiler version, and a binary compatible build of Qt. That is, generally speaking, a fantasy.

So, the idea that you need to test for QCoreApplication's presence simply doesn't fit your use model. You will always need it. All you have to do is to fire up a thread and start the core application there. That's it.

QPointer<Worker> worker;

extern "C" {
  __declspec(DLLEXPORT) WINAPI VOID kyleHello() {
    worker->hello();
  }
}

class Worker() : public Q_OBJECT {
  Q_OBJECT
  Q_INVOKABLE void helloImpl() { qDebug() << "I'm alive."; }
public:
  void hello() {
    // This is thread-safe, the method is invoked from the event loop
    QMetaObject::invokeMethod(this, "helloImpl", Qt::QueuedConnection);
  } 
  Worker() { hello(); }
};

class AppThread : public QThread {
  // No need for the Q_OBJECT
  QPointer<QCoreApplication> m_app;
  void run() Q_DECL_OVERRIDE {
    int argc; 
    char * argv;
    QCoreApplication app(argc, &argv);
    m_app = &app;
    Worker worker_;
    worker = &worker_;
    app.exec();
  }
  using QThread::wait(); // This wouldn't work here.
public:
  AppThread() {}
  ~AppThread() { m_app->quit(); wait(); }
}

BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID) {
  static AppThread * thread;
  switch (reason) {
  case DLL_PROCESS_ATTACH:
    thread = new AppThread;
    thread->start();
    break;
  case DLL_PROCESS_DETACH:
    delete thread;
    break;
  default:
    break;
  }
  return TRUE;
}

The API exposed to your consumer comes in several kinds:

  1. Write-only APIs that don't wait for a result. Internally you simply post an event to any of your QObjects. You can also use QMetaObject::invokeMethod with a Qt::QueuedConnection - it ends up simply posting a QMetaCallEvent to the target object. Events can be posted to any QObject from any thread, non-QThread-started-threads included.

  2. Foreign-thread callbacks: Dedicate a separate thread in which the consumer-provided callbacks execute. They'd be invoked by one or more QObjects living in that thread.

  3. Client-thread callbacks: Use platform-specific asynchronous procedure calls that execute a callback in the context of any thread - typically the thread where the callback registration function was called from. Those callbacks execute when the thread is in an alertable state.

    If you wish to limit yourself to a subset of alertable states where the message pump is running (GetMessage is called), you can create a message-only, invisible window, post messages to it, and issue the consumer callbacks from the window's callback function. If you're clever about it, you can pass QEvent pointers via those messages and pass them to QObject::event in the callback. That's how you can make a QObject effectively live in a thread with a native event loop and no Qt event loop running.

  4. Blocking APIs that effectively synchronize the calling thread to your thread: use QMetaObject::invokeMethod with Qt::BlockingQueuedConnection. The caller will wait until the slot finishes executing in the receiving thread, optionally passing a result back.

  5. Blocking APIs that use fine-grained locking. Those also synchronize the caller thread to your thread, but only at the level of locking certain data structures. Those are useful mainly to read parameters or extract data - when the overhead of going through the event loop would dwarf the small amount of work you perform.

What APIs you offer depends on the design criteria for your API.

All the APIs must be extern C and must not use C++. You can only offer C++ APIs if you plan on building the DLL using multiple VS versions (say 2008, 2010, 2012, 2013) - even then you must not expose Qt to the consumer, since the consumer may still use a binary incompatible version.

like image 124
Kuba hasn't forgotten Monica Avatar answered Nov 17 '22 17:11

Kuba hasn't forgotten Monica


QtWinMigrate solves the Qt in Win32 or MFC event loop problem. One of the answers to the question you reference mentions this.

For a Qt DLL that needs to link up the event loop in DllMain, simply use QMfcApp::pluginInstance

like image 37
Matthew Avatar answered Nov 17 '22 16:11

Matthew