Building a DLL on macOS for a non Qt application
-
Hello,
I've built myself a dynamically linked library that will be used in a non Qt application. Creating the QApplication and calling exec() in another thread works well on Windows but fails on macOS.
I've seen people refer to an issue with Cocoa but I haven't been able to find a solution that doesn't either block the original application's main thread or require changing the original application (can't be done).
So how does one use Qt on mac in a dll ?
Below is a bit of sample code as an example of what i'm trying to achieve:
QtModuleDemo::QtModuleDemo() : running_(false) { QApplication::setQuitOnLastWindowClosed(false); appThread_ = std::thread(&QtModuleDemo::run, this); } void QtModuleDemo::run() { int argc = 0; app_ = new QApplication(argc, nullptr); // fails here on macOS // App code goes here. widget_ = new Custom3DView; widget_->show(); running_ = true; app_->exec(); running_ = false; }
Thank you !
-
The only surprise is that such a dubious setup works on Windows at all. AFAIK all GUI systems want only one main event loop to run, and it has to run on the "main" thread. So even if you create a
QApplication
later in the program's lifecycle, you don't want toexec
it. There are other solutions if a Qt event loop is needed on a side thread.Specifically for your case, do you have any console logs from when creating the
QApplication
instance for context? I suspect that this the Qt Cocoa integration trying to install its' application delegate on the globalNSApplication
which is almost surely not something allowed not on the main thread, but could be something more mundane too.Also, are you familiar with https://developer.apple.com/documentation/dispatch/1453057-dispatch_async?
-
@IgKh unfortunately there is no console log given. I am curious about the other solutions for having a Qt event loop on a side thread. Would you happen to have any documentation regarding this ?
I am not familiar with dispatch_async but i will definetly look into it, thanks.
-
@Basile-Sens said in Building a DLL on macOS for a non Qt application:
unfortunately there is no console log given.
How do you know that it failed at the point you marked then? Did it crash? If so - the stack trace should be instructive.
But basically, and without having ever tried to do something like what you describe and without knowing more about it, you'll need to use Grand Central Dispatch from your plugin's entry point to schedule an Objective-C++ block to the main thread that will initialize the
QApplication
and create/show the top-level widgets inside AppKit's event loop. Any background processing should be done via the worker QObject pattern on a sideQThread
, with any resulting updates to the UI again being posted to the main thread.P.S You'll probably want to look into the
Qt::AA_PluginApplication
application attribute. The Objective C runtime is very dynamic, and Qt's Cocoa integration plugin takes advantage of it to inject all kinds of things that make some behaviors more consistent with how Qt behaves on other platforms; that could mess up your host application.I am curious about the other solutions for having a Qt event loop on a side thread.
QEventLoop
for when you need a sub-loop that will block a thread while still processing Qt and UI system events, and the built-in event loop ofQThread
. -
@IgKh said in Building a DLL on macOS for a non Qt application:
The only surprise is that such a dubious setup works on Windows at all
Well, it appears that Win32 actually allows exactly that; different thread can independently own different windows and process events for them. That's normally a strange architecture to run, but looks like it exactly fits OP's case. However, AppKit is indeed much less accommodating.
-
@IgKh said in Building a DLL on macOS for a non Qt application:
How do you know that it failed at the point you marked then? Did it crash? If so - the stack trace should be instructive.
Here is the full stack trace of the crash, basically Appkit is not happy that we are not in the main thread:
Thread 24 Crashed: 0 libdispatch.dylib 0x18ad16a60 _dispatch_assert_queue_fail + 120 1 libdispatch.dylib 0x18ad169e8 dispatch_assert_queue + 196 2 HIToolbox 0x196441bb4 islGetInputSourceListWithAdditions + 160 3 HIToolbox 0x196439940 isValidateInputSourceRef + 92 4 HIToolbox 0x196439e14 TSMGetInputSourcePropertyWithSetter + 48 5 libqcocoa.dylib 0x11f9d03f0 QCocoaInputContext::updateLocale() + 68 (qcocoainputcontext.mm:144) 6 libqcocoa.dylib 0x11f9d0334 QCocoaInputContext::QCocoaInputContext() + 248 (qcocoainputcontext.mm:55) 7 libqcocoa.dylib 0x11f9d1460 QCocoaIntegration::QCocoaIntegration(QList<QString> const&) + 1140 (qcocoaintegration.mm:129) 8 libqcocoa.dylib 0x11f9bb994 QCocoaIntegrationPlugin::create(QString const&, QList<QString> const&) + 112 (main.mm:29) 9 QtGui 0x15504be7c QPlatformIntegration* qLoadPlugin<QPlatformIntegration, QPlatformIntegrationPlugin, QList<QString> const&, int&, char**&>(QFactoryLoader const*, QString const&, QList<QString> const&, int&, char**&) + 80 (qfactoryloader_p.h:100) [inlined] 10 QtGui 0x15504be7c QPlatformIntegrationFactory::create(QString const&, QList<QString> const&, int&, char**, QString const&) + 204 (qplatformintegrationfactory.cpp:23) 11 QtGui 0x15501769c init_platform(QString const&, QString const&, QString const&, int&, char**) + 1716 (qguiapplication.cpp:1291) 12 QtGui 0x155016e4c QGuiApplicationPrivate::createPlatformIntegration() + 1404 (qguiapplication.cpp:1580) 13 QtGui 0x155018f7c QGuiApplicationPrivate::createEventDispatcher() + 36 (qguiapplication.cpp:1599) 14 QtCore 0x1236dc984 QCoreApplicationPrivate::init() + 1588 (qcoreapplication.cpp:925) 15 QtGui 0x1550130c4 QGuiApplicationPrivate::init() + 76 (qguiapplication.cpp:1627) 16 QtGui 0x155013f1c QGuiApplication::QGuiApplication(int&, char**, int) + 140 (qguiapplication.cpp:647) [inlined] 17 QtGui 0x155013f1c QGuiApplication::QGuiApplication(int&, char**, int) + 168 (qguiapplication.cpp:646) 18 QtModuleDemo.usr-macos64uni 0x11cc82a3c QtModuleDemo::run() + 104 (QtModuleDemo.cpp:142)
-
@IgKh said in Building a DLL on macOS for a non Qt application:
P.S You'll probably want to look into the
Qt::AA_PluginApplication
application attribute.Thanks for the tip, this solved my next issue where AppKit would not let me have an open window for more than 10 seconds before crashing in NSPersistentUIRequiresSecureCoding. Had no effect on the main issue though. Anyways I've refactored my code to this and it works somewhat:
QtModuleDemo::QtModuleDemo() // always called from the app's main thread { int argc = 0; QGuiApplication::setAttribute(Qt::AA_PluginApplication); QGuiApplication::setQuitOnLastWindowClosed(false); app_ = new QGuiApplication(argc, nullptr); // App code goes here. widget_ = new Custom3DView; widget_->show(); }
The 3D renderer works and i can move the camera around. So do i really need a call to exec() ? If I was to call QGuiApplication::ProcessEvents() periodically from the main thread would it have the same effect ? What in Qt requires an event loop ?
I know that we are moving out of scope on this one, I might create a new topic instead.
-
@Basile-Sens said in Building a DLL on macOS for a non Qt application:
So do i really need a call to exec() ? If I was to call QGuiApplication::ProcessEvents() periodically from the main thread would it have the same effect ? What in Qt requires an event loop ?
No, you don't. A lot of things in Qt require an event loop to be running, but Qt doesn't actually have an event loop (well it has, but it is not often used). Each platform integration plugin has an implementation of
QAbstractEventDispatcher
that is responsible for picking up events that are relevant to Qt from the native windowing system and letting its' event loop know about events that are posted from within the QApplication and async IO/timers requested by Qt classes.So as long as something is running a native event loop and the platform integration plugin is initialized (by creating the
QApplication
instance), all event dispatching and queued signal/slot connections should just work.QApplication::exec
is just a way to start an event loop, but you don't want that if something else has already started it.Had no effect on the main issue though
Can you, just for sake of clarity, state the remaining issues?
-
@IgKh Thank you for your precious help, things are a lot clearer now. Further tests have shown that most of the features I need still work without a call to exec(). I still can't quite understand what in my example started the event loop or if Qt just plugs in the existing NSApplication's event loop.
@IgKh said in Building a DLL on macOS for a non Qt application:
Had no effect on the main issue though
Can you, just for sake of clarity, state the remaining issues?
The main issue i refered to was me wanting to have a separate thread with a QApplication in it and the Qt::AA_PluginApplication attribute had no effect on this. The other issues I have are not within the scope of this topic though.
Thank you very much !
-