Assert failure with vst2 wrapper using SDK 3.7.2

Steps to reproduce:

  1. Compile vst2 plugin using VST3 SDK 3.7.2 and the included wrapper (and use the VST2 SDK that was last released)
  2. Load 1 instance of the plugin in Reason 11… works
  3. Load another instance of the plugin in Reason 11 => fails
// from platformfactory.cpp line 30
void initPlatform (PlatformInstanceHandle instance)
{
	vstgui_assert (!gPlatformFactory); // <======== this is where it fails
	setPlatformFactory (std::unique_ptr<IPlatformFactory> (new VSTGUIPlatformFactory (instance)));
}

This code gets triggered by:

// from vstguieditor.cpp line 53
#if VSTGUI_NEEDS_INIT_LIB
#include "vstgui/lib/vstguiinit.h"
static Steinberg::ModuleInitializer InitVSTGUI ([] () {
	using namespace Steinberg;
	VSTGUI::init (getPlatformModuleHandle ());
#if SMTG_MODULE_IS_BUNDLE
	Vst::setupVSTGUIBundleSupport (getPlatformModuleHandle ());
#endif // SMTG_MODULE_IS_BUNDLE
});

It seems that loading plugins in Reason (unclear if other DAWs behave the same way) does not get done in isolation. The same plugin built with 3.7.1 does not have this problem.

It looks like instead of hard setting variables, a reference counter should be used instead ensuring that this kind of problem doesn’t happen…

To follow up on this issue, I have done the following experiment:

I modified a very simply VST2 plugin (purely VST2, not using VST3 wrapper) with the original code on github. I made the following change:

static int __staticIssue = 0;


VSTPlugin *VSTPluginMain(VSTHostCallback vstHostCallback)
{
  printf("called VSTPluginMain... \n");

  // simply create our plugin C++ class
  VSTPluginWrapper *plugin =
    new VSTPluginWrapper(vstHostCallback,
                         CCONST('u', 's', 'a', 'n'),
                         PLUGIN_VERSION, // version
                         0,    // no params
                         0,    // no programs
                         2,    // 2 inputs
                         2);   // 2 outputs

  printf("[vst.usan.M3dB] Created plugin %p / __staticIssue=%d \n", plugin, __staticIssue);

  __staticIssue++;

  // return the plugin per the contract of the API
  return plugin->getVSTPlugin();
}

I then compiled and loaded the plugin in 2 different DAWs (Reason and Maschine 2). Both of them behave in the exact same way:

// add 1 instance
called VSTPluginMain...
[vst.usan.M3dB] Created plugin 0x7fcdf51a7a60 / __staticIssue=0
// add 1 instance
called VSTPluginMain...
[vst.usan.M3dB] Created plugin 0x7fcdf5cacd70 / __staticIssue=1
// add 1 instance
called VSTPluginMain...
[vst.usan.M3dB] Created plugin 0x7fcdf51200b0 / __staticIssue=2
// delete all instances
// then add 1 instance
called VSTPluginMain...
[vst.usan.M3dB] Created plugin 0x7fcdf58aa1d0 / __staticIssue=3

So what this shows is that VST2 loading (at least in 2 different DAWs) is not isolating instances and as a result static variables end up being shared across instances.

Which creates a serious issue if you use the VST3 SDK 3.7.2 VST2 wrapper since it uses a static variable (gPlatformFactory) protected with an assert, thus crashing the DAW if 2 instances get loaded.

I am not entirely sure what 3.7.1 was doing instead (because there was no such issue) but 3.7.2 has seriously broken this code path. You can argue that DAWs are not doing the right thing. But 2 of the 2 that I tried on (so 100% of my tests) are failing… I know that Steinberg does not support VST2 anymore, but just wanted to point out that 3.7.2 has put the nail in the coffin with this “small” change…

After more hunting, I have traced down the issue to public.sdk/source/vst/vst2wrapper/vst2wrapper.cpp line 1943

SMTG_EXPORT_SYMBOL AEffect* VSTPluginMain (audioMasterCallback audioMaster)
{
	// Get VST Version of the host
	if (!audioMaster (nullptr, audioMasterVersion, 0, 0, nullptr, 0))
		return nullptr; // old version

	if (InitModule () == false) // <====== this is called always
		return nullptr;

	// Create the AudioEffect
	AudioEffect* effect = createEffectInstance (audioMaster);
	if (!effect)
		return nullptr;

	// Return the VST AEffect structure
	return effect->getAeffect ();
}

which unconditionally call InitModule() every time a plugin is instantiated. I don’t believe there is any restriction from the DAW side to do that.

I think this is broken. InitModule() should be called when the plugin/library is loaded only not every time a plugin is instantiated as documented :

// macmain.cpp L54
bool InitModule (); ///< must be provided by plug-in: called when the library is loaded
bool DeinitModule (); ///< must be provided by plug-in: called when the library is unloaded

I patched vst2wrapper.cpp in the following fashion:

extern bool InitModule ();
extern bool DeinitModule();

struct ModuleLifecycle {
  ModuleLifecycle() : fInitModuleResult(InitModule()) {}

  inline bool isInitSuccessful() const { return fInitModuleResult; }

  ~ModuleLifecycle()
  {
    if(fInitModuleResult)
      DeinitModule();
  }

private:
  const bool fInitModuleResult;
};

//-----------------------------------------------------------------------------
extern "C" {

//-----------------------------------------------------------------------------
/** Prototype of the export function main */
//-----------------------------------------------------------------------------
SMTG_EXPORT_SYMBOL AEffect* VSTPluginMain (audioMasterCallback audioMaster)
{
  static ModuleLifecycle moduleLifecycle{};

	// Get VST Version of the host
	if (!audioMaster (nullptr, audioMasterVersion, 0, 0, nullptr, 0))
		return nullptr; // old version

  if(!moduleLifecycle.isInitSuccessful())
    return nullptr;

	// Create the AudioEffect
	AudioEffect* effect = createEffectInstance (audioMaster);
	if (!effect)
		return nullptr;

	// Return the VST AEffect structure
	return effect->getAeffect ();
}

// IMPORTANT:
// note you need to remove the `DeinitModule()` call from `~BaseWrapper`

which ensures that

a) InitModule() is only called once (in a thread safe manner)
b) DeInitModule() is also called whenever, either the DAW terminates or the library is unloaded

This code will work whether the DAW loads the code only once and initialize multiple instances of the plugin (which seem to be the case for Reason and Maschine 2) as well as if the DAW loads the code for each instance of the plugin.

Finally, the reason why it works with VST3 as demonstrated by editorhost is this kind of code:

// from macmain.cpp
/** must be called from host right after loading bundle
Note: this could be called more than one time! */
bool bundleEntry (CFBundleRef ref)
{
	if (ref)
	{
		bundleRefCounter++;
		CFRetain (ref);

		// hold all bundle refs until plug-in is fully uninitialized
		gBundleRefs.push_back (ref);

		if (!moduleHandle)
		{
			ghInst = ref;
			moduleHandle = ref;

			// obtain the bundle path
			CFURLRef tempURL = CFBundleCopyBundleURL (ref);
			CFURLGetFileSystemRepresentation (tempURL, true, (UInt8*)gPath, VST_MAX_PATH);
			CFRelease (tempURL);
		}

		if (bundleRefCounter == 1) // <==== this ensures called only once!
			return InitModule ();
	}
	return true;
}
1 Like

Maybe these kind of problems are suited best in the github issue tracker, especially if you already have a fix (merge request)? Issues · steinbergmedia/vst3sdk · GitHub

Thanks for the report. We will upload a fix in github as soon as possible. This issue concerns VST2 and AAX Wrapper when using VSTGUI.

@Yvan Thank you for confirming. I opened a ticket on github for it with suggestion on how to implement.

There also seems to be another issue with vst3 on Windows10 (VSTGUI / json format). See here.

1 Like

We fix these VST2 and AAX issues in github and merge to master: steinbergmedia/vst3_public_sdk: VST 3 Implementation Helper Classes And Examples (github.com).
These fixes will be included in the next VSTSDK update.

Thanks Yvan.

If you look at my bug report about bundleEntry / bundleExit not being called on macos, I just don’t see this fix in the merge. Is it going to be fixed as well? Otherwise there is no UI on macos.

This should fix the initModule/DeinitModule issue for VST2 and AAX.

I agree that it fixes the issue that InitModule was called multiple times thus leading to a crash.

It does not fix the other issue that there is no UI on macOS because the new VSTGUI version relies on the macOS bundle reference to be set “globally” and this is exactly what bundleEntry does. If you don’t call bundleEntry or do not reimplement the work that bundleEntry does, then there is no UI. That is the reason why my suggested fix in the bug report calls bundleEntry which in turns, call InitModule only once and also set the proper bundle reference.

you´re right, we have fixed it for now only for Windows… we will check it again.

After some internal talks, here is our statement:
“On Mac VST2 plugin using the VST SDK 3.7.2 and VSTGUI 4.10 is not supported anymore!”
Mainly all DAW on Mac are supporting VST3 or AU and not only VST2.

you can decide for this use case to :

  • use an older VSTGUI version or an older SDK
  • or add locally a fix when using VSTGUI and VST2 wrapper.

Regards

So I guess you are not supporting AAX anymore either… because it is the exact same issue with AAX wrapper. If you do not call bundleEntry, then you do not setup VSTGUI properly hence it can’t work.

My submitted bug report contains the entire fix. You can simply reuse the code. Instead of calling InitModule you call bundelEntry. Instead of calling DeinitModule you call bundleExit. It is really that simple.

AAX Wrapper has a different implementation: from the host an entry func ACFStartup is called which called the bundleEntry.
Your fix add a dependency to VSTGUI…

My question is: why do you still want build VST2 plugin on mac?

I just realized this is true and I apologize for this. I am 100% with you on the fact that, because VSTGUI is an optional component of the VST SDK, there should not be such a dependency in the VST2 wrapper. The dependency is artificial though and only introduced because of a define (#if MAC) and the GetBundleEntry call which lives in VSTGUI but is not dependent on VSTGUI, so super easy to fix. I will update the bug report to remove this unnecessary dependency.

Unfortunately, my primary platform is Reason by Reason Studios, which as of this day still only supports VST2 (no AU or VST3). So I have no way to run my plugins in my DAW of choice otherwise.

If this is going to be your position, then I hate to say this, but it is probably better and easier for you to simply remove the VST2 wrapper from the next version of the SDK. I was surprised that you removed the VST2 sdk but left the VST2 wrapper in the first place. And I am not sure it makes sense to have the wrapper in it anymore. It is going to be more confusing if it works on some platform but not others, etc…

We will check with them in order to see when they will move to VST3, especially for Mac M1 support .

We still have it mainly for Windows, check the AAX Entry file, the bundleRef is extracted here and pass to the ModuleBundle… i will have some talks here internally if we could do the same for VST2 wrapper.

Yup. That does exactly what the one from VSTGUI does without depending on VSTGUI. I will port it to my local vst2wrapper then. Thanks for the pointer.

And here is the new version without the VSTGUI dependency.