I am trying to use JUCE as a library to add plugin hosting (VST/AU/LADSPA) support to our application. I can build JUCE as a static library, link to it, and it “sort of” works. I can call JUCE code from our app, list available plugins, load plugins, and load plugin UI.
However, I quickly run into all sorts of hard-to-debug problems (crashes, hangs), that appear to be related to JUCE’s message manager and / or how it is initialized, and / or how it interacts with our existing code.
Is it possible to run VST hosting without the message manager being initialised…? I’m guessing not, but this would be useful (even if it involves hacks) if only for debugging purposes
On what thread should I be calling initialiseJuce_GUI() ? Should I be calling it on our main thread, which our GUI also runs on, or should I be running the JUCE message manager in its own thread? I’ve been trying with the main thread, but depending on where in our app’s initialisation process it’s called results in different problems occurring.
Is it safe to call initialiseJuce_GUI() more than once… for example in a background thread at startup that scans for plugins, and then again in the “main” thread?
Many thanks for any advise on this.
UPDATE (17/5/21): It appears that the problems I’ve been getting are a result of JUCE code being called from threads other than the one that initialiseJuce_GUI() was called on, so I need to fix that. Still, it would be useful to have answers to the questions above.
I think this is very unlikely. VSTPluginInstance derives from AsyncUpdater, so at least some functionality will be degraded/broken if there is no active message loop. Running plugins without a message loop also isn’t a use-case that we support or test, so it’s possible that a future change will cause breakage, even if it appears to work today.
Doing this on the main thread is probably safest. On Windows, it looks like this call may create a new HiddenMessageWindow instance. As I understand it, the Windows message queue must then be pumped on the same thread that initially created this window. For this reason, it’s probably best to call initialiseJuce_GUI from the same thread that will own the message loop.
The docs for ScopedJuceInitialiser_GUI (which calls initialiseJuce_GUI in its constructor) say:
Be careful with your threading though - to be safe, you should always make sure that these objects are created and deleted on the message thread.
I believe that it is, although you should be careful to only call shutdownJuce_GUI once, when the application is quitting. If you accidentally call it before you’re completely done with JUCE, it will clear singleton instances which may still be in use. You may also see false-positive leak detector warnings, because the leak detector could be destroyed before some other JUCE objects which are still in use.
If I take VST hosting out of the equation, and just create an empty DocumentWindow I can see that the window gets added to the Desktop, and if setUsingNativeTitlebar() is set to true then the native title bar is added with a close and minimize button. resize() is also called on the window.
However… paint() is not called on the window, which explains why no content is drawn!
As far as I can tell, repaints are supposed to be triggered by LinuxRepaintManager, but this does not seem to be happening.
It sounds like you might have built the static library with different config flags to the application, specifically the JUCE_USE_XSHM flag in juce_gui_basics that controls whether the Xshm extension is used for managing repaints on Linux. Can you check the value of the flag for both and see if they match up?
@ed95 It’s enabled in the Projucer for the static lib, and I’ve now defined it for the application, but still no joy. Is there a breakpoint I can set somewhere to confirm it’s running…?
Note: I can see that LinuxRepaintManager gets created when addToDesktop() is called for the window but performAnyPendingRepaintsNow() is never called, so the Timer for the repaint manager never starts.
It sounds like the message loop isn’t running. Try sticking a breakpoint in the dispatchPendingEvents() method of the internal Linux run loop and see if it’s hit. Unfortunately without seeing any of the code I’m not able to provide much more insight than that…
Thanks @ed95. I can confirm the message loop isn’t running. I feel I’m bit further forward now I at least know what the problem is, so thanks for your help, much appreciated.
As for the code, I’m not currently doing anything besides calling initialiseJuce_GUI() on our application’s main thread, and trying to add a DocumentWindow to the desktop, just to get a proof-of-concept going.
Our application is using GTK if that makes any difference.
If you sleep the main thread as you are doing in your test program then the timerCallback() won’t get called. Pumping the message thread with something like the following gets timer callbacks for me on both Linux and macOS:
What do you mean by GUI vs CLI app in this context? The type of Projucer project, or something else?
I mean an external “native” program (not JUCE) that links to a JUCE static library.
On Mac we are using Apple’s native frameworks
On Windows are using Microsoft’s native frameworks
On Linux we are using GTK
On Mac and Windows, everything “just works”. I call juce::initialiseJuce_GUI() on our main thread and JUCE behaves as expected. I can create windows, Component::paint() gets called, Timer::timerCallback() gets called etc. This is without calling MessageManager::runDispatchLoop().
With GTK it does not work.
If I call juce::initialiseJuce_GUI() on our main thread (same code as Mac and Windows) then I do not get calls to Component::paint() or Timer::timerCallback()
If I call MessageManager::runDispatchLoop() as in the above CLI example, it just blocks the main thread and of course, the UI stops responding
This begs some questions:
Why does the same code work on Windows and Mac but not on Linux?
Am I doing something wrong? Should I be running JUCE on a background thread and using MessageManager::runDispatchLoop() on all platforms?
Should I be repeatedly calling juce::MessageManager::runDispatchLoopUntil() in our application’s main event loop?
If it would be helpful, I can post a simple GTK linking to JUCE on GitHub to illustrate the problem.
Unlike macOS or Windows, on Linux there’s no concept of an application’s main “run loop” for your external program to hook into. I’m not too familiar with GTK but if it expects to run the main loop then you’ll need to register the JUCE input event file descriptors and handlers. You can retreive these with the getFdReadCallbacks() method. Take a look at the VST3 wrapper code to see how we handle registering JUCE’s input handlers with an external run loop -