I have a customer who gets a crash on quitting Wavelab 10.0.30 on OS X if multiple vst3 instances of my plugin is inserted when doing a montage session. He contacted Steinberg about this and they blame my plugin for the problem (naturally). After a few attempts I managed to recreate the crash on my own machine using a debug build. Thankfully, Wavelab allows attaching the XCode debugger.
Then I switched to the Juce GainPlugin demo and without any changes to any code, the exact same crash happens. To reproduce, load two audio files into Wavelab, create a Montage, insert a juce-built vst3 plugin on both tracks. Then save the montage :). Now quit Wavelab and after a few second the crash happens (most of the time):
My conclusion is that the problem is either in Juce or Wavelab - my feeling is that Wavelab is to blame as otherwise weād see the same issue in more vst3 hosts. Itās also curious that this does not happen when inserting the same plugin on the master section.
Has anyone else seen that and maybe found a workaround?
Iām still looking into this using the unaltered GainPlugin example code. The crash happens when a member of the JucePluginFactory gets destroyed, namely
ComSmartPtr<Vst::IHostApplication> host;
I donāt quite know how to go on debugging this. I tried stepping through the JucePluginFactory destruction in the XCode debugger and maybe the problem is related to getting two JucePluginFactory destructor calls in very quick succession (for two instances). I see the globalFactory static variable is already nullptr on the second call, but the ComSmartPtr<Vst::HostApplication> host gets destroyed again - which feels wrong and does crash. Should I really get two dtor calls? The combination of smartPointers and ref counted singletons surely is confusing.
And btw. the same crash happens when using two VST3 instances as master insert plugins. So itās not limited to the montage section. All it seems to take is loading the same JUCE vst3 plugin twice and then quitting Wavelab.
I think I found the bug and it appears to be in the JUCE VST3 wrapper.
Unfortunately I am not very familiar with those COM interfaces, but I think the JUCE VST3 wrapper fails to increment the reference count on that host smartpointer. When Wavelab is shut down, the host gets detroyed and the non incremented smart pointer crashes when it is destroyed. Many hosts just donāt worry about proper cleanup when shutting down and maybe thatās why this crash only happens in Wavelabā¦?
Anyway hereās the trail of the problem.
in JucePluginFactory::setHostContext (FUnknown* context) this happens:
I think the issue comes from not incrementing the refCount in this loadFrom(). The smart-pointer gets cast to (void**)ā¦! Therefore the queryInterface cannot increment the refCount, but the destruction of the factory will for sure decrement it and thus the crash happens as the host object can already be destroyed before the plugin. Adding an ->addRef() there solves the crash, but it could also be added also be added in setHostContext(). not sure whatās better as I donāt understand what loadFrom() is supposed to do in the first place. Thatās a weird method name. Assigning to a smart-pointer using a cast to (void**) seems to be the real issue, but of course the loadFrom is used elsewhere in the code where maybe thatās desired behaviour?
Arghā¦ now I realize QueryInterface is supposed to call AddRef before returning any object - not sure where this leaves things.
I looked at the vst3 wrapper code some more. First I found this:
//==============================================================================
// no leak detector here to prevent it firing on shutdown when running in hosts that
// don't release the factory object correctly...
JUCE_DECLARE_NON_COPYABLE (JucePluginFactory)
Obviously something goes wrong on shutdown in WaveLab. I then looked at the vst3 sdk sample code to see how the examples deal with that smart pointer to the host in their pluginfactory class.
Surpriseā¦ they donāt deal with it at all and just return kNotImplemented and then use nullptr as the context in the factory createInstance method - which is the only place that smart pointer is ever used. Based on the sample code I am not sure it is ever neededā¦ doesnāt seem to be. I donāt understand why the JUCE VST3 wrapper needs to store it, based on the comment above it is clear that holding onto a smart pointer might be a bad choice if the class is expected to not be released properly. So here comes the fix. Just donāt keep it around in WaveLab. I have no idea whether there is a host that needs it, but WaveLab does dandy without it and all other hosts probably work just fine as well - otherwise they wouldnāt work with the VST3 SDK examples. Here is my diff so others can avoid the annoying debug session:
+++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp**
@@ -3072,6 +3072,10 @@ struct JucePluginFactory : public IPluginFactory3
tresult PLUGIN_API setHostContext (FUnknown* context) override
{
+ // WaveLab 10 on OS X (and maybe other WaveLabs): if the context object gets smart referenced here,
+ // a crash happens on shutdown in case multiple instances were active.
+ if (getHostType().isWavelab()) return kNotImplemented;
+
host.loadFrom (context);
It would make my day if someone with more VST3 experience could explain to me why the setHostContext() even exists.
No my one-liner pull request never got merged - or even looked at . I believe it is mainly a bug in WaveLab, but JUCE could easily work around it by not storing that pointer that is not needed anyway. I gave up on submitting PR after thisā¦
Navigate to the āPlayā tab and create two instances of JUCEās AudioPluginDemo
Attach a debugger to Doricoās āVST Audio Engineā process
Quit Dorico. The first time we enter the destructor of JucePluginFactory, the object managed by the VSTComSmartPtr<Vst::IHostApplication> host; data member is invalid (already freed?), and the process crashes when we try to call release on it.
@rhansen I believe this is a bug in Dorico, please can you confirm?