9

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

rgettman
  • 176,041
  • 30
  • 275
  • 357
emKaroly
  • 756
  • 1
  • 10
  • 22
  • I am not sure if you really need the event loop. How about calling what is now in qCoreAppExec() from a method when user inits the library, then run a thread (QThread) and instantiate ReaderSerial in that thread? you should have an init for the library that is called before it can be used. – dashesy Jul 29 '14 at 22:04
  • Ok so if i do something like: readerSerial->moveToThread(&readerSerialThread); readerSerialThread.start(); How i call the ReaderSerial methods so they going to block? – emKaroly Jul 29 '14 at 22:31
  • One more thing, i call the qCoreAppExec() from ctor and to use the api the user have to instantiate the Reader class so i think its something like initialisation, but i am not sure. – emKaroly Jul 29 '14 at 22:38
  • 1
    constructor will do but readerSerial should not be in ctor then, instead some user-called init should create a QThread and inside it readerSerial = new ReaderSerial; – dashesy Jul 29 '14 at 22:53
  • moveToThread almost always in not necessary BTW, to test create a thread and print something to see if thread itself works fine. – dashesy Jul 29 '14 at 22:54

2 Answers2

5

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.

Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Thank you for your answer, i will take a look on that QMetaObject::invokeMethod with Qt::BlockingQueuedConnection but you suggest to put the qcoreapp in thread and isn't that what i am doing in my code? – emKaroly Jul 30 '14 at 08:31
  • Regarding to API, i want it as simple as possible. You create a reader object call connect than you just call method which send packet and wait for response than it parse and return it to user. Sometimes its needed to handle incoming packet without calling method, these i handle with callbacks. Until now it worked that way but only in Qt and now i want to make work without Qt too(but with qt benefits under the hood). – emKaroly Jul 30 '14 at 08:58
  • If Worker worker is my ReaderSerial than i have problem, because i need to call methods from readerserial and if i call app.exec i cant do that – emKaroly Jul 30 '14 at 11:44
  • The ReaderPrivate is the 2nd layer of the API there is a Reader class where is no Qt stuff. Can i use Qt and C++ under the hood and somehow PIMPL the rest so the API from outside will be pure C? – emKaroly Jul 30 '14 at 14:32
  • @KyleHectic As I've shown, yes. You can and should use Qt for the implementation. – Kuba hasn't forgotten Monica Jul 30 '14 at 15:37
  • Thank you for your effort. I going through your code, its hard to understand for me because i am beginner. At this moment i try to make this thing work.I put the whole thing in C++ Library project in Qt and removed the Qt export macros so its just a C++ Library Project. I also included Windows.h and QtCore but i have still problems to make it work. – emKaroly Jul 31 '14 at 08:44
  • I changed: class Worker() : public Q_OBJECT to class Worker : public QObject on that line: QPointer worker; i get the following errors: 'worker' : unknown size 'QPointer' : no appropriate default constructor available – emKaroly Jul 31 '14 at 09:09
  • I am a bit concerned about this line: `~AppThread() { m_app->quit(); wait(); }`. Is it thread-safe to just call `quit()` slot on a `QCoreApplication` object that was created in a different thread? Maybe we should have a signal `appQuit`, then connect it with `Qt::AutoConnection` to the `QCoreApplication::quit` slot and change the line to: `~AppThread() { emit appQuit(); wait(); }`? – daniel.kish Apr 20 '22 at 09:18
1

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

Community
  • 1
  • 1
Matthew
  • 1,264
  • 1
  • 11
  • 20