4

Intro This is an open ended question that I thought could be beneficial to the community because I have been unable to find great documentaton in regards to this. Unfortunately, I learned the hard way that implementing a DLL in Qt is different than in other languages as I will explain later

Problem Statement Implement a multithreaded DLL in Qt that can easily be used by non-Qt applications

Background Info

Qt is the tool of choice because its inherent cross-platform compatibility The API makes use of callback functions to tell the calling application when certain events have occurred

Assumptions

-The applications that will link to the Qt dll are compatible with Qt compilers (c/c++ -mingw, C# -msvc) -Signals/slots are used to communicate from main thread to worker threads (e.g. tell a worker thread to collect data) as well as from worker threads back to main threads (e.g. notify the main thread via a callback function that data collection has completed)

Problem Description

I learned the hard way that writing a multi-threaded DLL in QT is different than other languages because of Qt's architecture. Problems arise due to QT event loops that handle spwaning threads, timers, sending signals, and receiving slots. This Qt even loop (QApplication.exec()) can be called from the main application when the main app is Qt (Qt has access to QT specific libraries). However, when the calling application is not Qt, C# for example, the calling application (aka the main thread) does not have the ability to call Qt specific libraries therefore, making it necessary to design your DLL with an event loop embedded inside of it. It is important that this is considered upfront in the design because it is difficult to shoe-horn it in later because QApplication.exec is blocking.

In a nutshell, I am looking for oppinions on the best way to architect a multithreaded dll in Qt such that it is compatible w/ non-QT applications.

In Summary

  • Where does the event loop reside in the overall architecture?
  • What special considerations should you make in regards to signals/slots?
  • Are there any gotchas that the community has come across when implementing something similar to what I have described?

2 Answers2

1

[...] when the calling application is not Qt, C# for example, the calling application (aka the main thread) does not have the ability to call Qt specific libraries therefore, making it necessary to design your DLL with an event loop embedded inside of it.

This is not accurate. On Windows, you need one event loop per thread, and that event loop can be implemented using pure WINAPI, or using C#, or whatever language/framework you need. As long as that event loop is dispatching windows messages, Qt code will work.

The only Qt specific thing that needs to exist is an instance of QApplication (or QGuiApplication or QCoreApplication, depending on your needs) created from the main thread.

You must not call exec() on that instance, since native code (the main application) is already pumping windows messages. You do need to call QCoreApplication::processEvents once after you create the application instance, to "prime" it. That you do need to do so is a bug (omission) and I'm not sure that it's even necessary in Qt 5.5.

Afterwards, the gui thread in the native application will properly dispatch native events to Qt widgets and objects.

Any worker threads you create using unaltered QThread::run will spin native event loops and each of these threads can host native objects (windows handles) and QObjects alike, as well as execute asynchronous procedure calls.

The simplest way of setting it all up is to provide an initialize function in the DLL that is called by the main application once to get Qt going:

static int argc = 1;
static char arg0[] = ""; 
static char * argv[] = { arg0, nullptr };
Q_GLOBAL_STATIC_WITH_ARGS(QApplication, app, (arc, argv))

extern "C" __declspec(dllexport) void initialize() {
  app->processEvents(); // prime the application instance
  new MyWindow(app)->show();
}

The requirement for initialization not to be done in DllMain is not specific to Qt. Native WINAPI-using code is forbidden from doing mostly anything in DllMain - it's not possible to create windows, etc.

I reiterate that it's an error to do anything that could be allocating memory, window handles, threads, etc. from DllMain. You can only call kernel32 APIs, with some exceptions. Allocating a QThread or QApplication instance there is an obvious no-no. Queuing the APC call from a "current" (random) thread is the best you can do, and there's still not a firm guarantee that the thread will survive long enough to execute your APC, or that it will ever alertably wait so that the APCs can get a chance to run.


If you're feeling adventurous, according to this answer, you could queue the call to initialize() as an APC. The major problem then is that you can't ever be sure that DllMain is called from the right thread. The thread it's called from must end up in alertable wait state (say pumping the message loop). You can create a dedicated application thread then, and it's not possible to figure out if there is any particular other "main" thread that should be used instead of the new one. The thread handle must be detached, thus we must use std::thread instead of QThread.

void guiWorker() {
  int argc = 1;
  const char dummy[] = "";
  char * argv[] = { const_cast<char*>(dummy), 0 };
  QApplication app(argc, argv);
  QLabel label("Hello, World!");
  label.show();
  app.exec();
}

VOID CALLBACK Start(_In_ ULONG_PTR) {
  std::thread thread { guiWorker };
  thread.detach();
}    

BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID)
{
  switch (reason) {
  case DLL_PROCESS_ATTACH:
    QueueUserAPC(Start, GetCurrentThread(), NULL);
    break;
  case DLL_PROCESS_DETACH:
    // Reasonably safe, doesn't allocate
    if (QCoreApplication::instance()) QCoreApplication::instance()->quit();
    break;
  }
}

Generally speaking, you should never need such code. The main application must call the initialization function from a thread with an event pump (usually the main thread), and everything will work then - just as it would were it initializing a DLL using native functionality only.

Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
0

Just to provide a quick update on this so you can learn from our mistake. We ran into all types of problems when we tried to integrate our Qt written dll with non-Qt languages such as C# because of the issues listed above. While Qt is great at providing a multi-platform solution it has the drawback of not being very DLL friendly as it is very difficult to get the DLL working any language other than Qt. We are currently investigating whether or not we want to rewrite our entire DLL in standard portable C++ and scrap the Qt implementation which would be very expensive.

Fair warning, I would avoid using QT as your framework when creating DLLs.

  • I'm sorry if my answer has misled you. The solution is rather simple: On Windows, Qt simply requires that the main thread provide a native event loop that spins. The windows event dispatcher used by Qt will integrate with native event loop. That's all. No need for a dedicated gui thread - one already exists, and you never need to call `app.exec()`. You need there to exist a `QApplication` instance, ideally created via `Q_GLOBAL_STATIC`, and you need to call `processEvents` *once* after you've created it. That's it. I wrote a VS plugin that way, so as far as I can tell - it works. – Kuba hasn't forgotten Monica Sep 10 '15 at 16:37
  • Other (non-gui) threads are not special in any way, and whether you fire them off from code in a DLL or not doesn't matter. What matters is not to attempt to start a separate GUI thread - one already exists. – Kuba hasn't forgotten Monica Sep 10 '15 at 16:40