Access violation when running plugin from a minimal VST3 host

I’m writing a minimal host for VST3 plugins.
I’m doing initialization following the VST3 host workflow:
https://steinbergmedia.github.io/vst3_doc/vstinterfaces/workflow.html

However, while LABS loads and processes, Helm (based on JUCE) crashes at the following line of code.

vst_plugin->processor->setProcessing(true)

As far as I know I’ve followed the bare basics of what VST3 requires me to provide to the plugin, is there anything I’m missing, or does helm require more than just this?

The full code of my VST3 host is below. This is compiled to a DLL and loaded from another host program that provides input/output buffers and copies the result to device output. process is called from a separate processing thread.

#include <stdio.h>
#include <stdexcept>
#include <memory>

#include <vst/hosting/module.h>
#include <vst/hosting/plugprovider.h>
#include <vst/hosting/hostclasses.h>
#include <pluginterfaces/base/funknown.h>
#include <pluginterfaces/vst/ivstcomponent.h>
#include <pluginterfaces/vst/ivstaudioprocessor.h>

extern "C" {

typedef struct RdPlugin RdPlugin;

typedef struct {
	RdPlugin* (*create)(double_t sample_rate);
	void (*destroy)(RdPlugin *plugin);
	void (*process)(RdPlugin *plugin, float_t **inputs, float_t **outputs);
} RdPluginInterface;

}

namespace Steinberg {
	Steinberg::FUnknown *gStandardPluginContext = nullptr;
}

struct VstPlugin {
	Steinberg::IPtr<Steinberg::Vst::IComponent> component;
	Steinberg::IPtr<Steinberg::Vst::IAudioProcessor> processor;
	bool processing_is_set = false;
};

RdPlugin* create(double_t sample_rate) {
	// TODO: This currently has various memory leaks in error conditions

	Steinberg::gStandardPluginContext = new Steinberg::Vst::HostApplication();

	auto plugin = new VstPlugin();

	std::string err;

	auto module = VST3::Hosting::Module::create("C:\\Program Files\\Common Files\\VST3\\Helm\\helm64.vst3", err);

	if (!module) {
		printf("Failed to load VST module: %s\n", err.c_str());
		return nullptr;
	}

	printf("Loaded VST module\n");

	auto factory = module->getFactory();

	for (auto &class_info : factory.classInfos())
	{
		if (class_info.category() == kVstAudioEffectClass)
		{
			printf("Found processor: %s\n", class_info.name().c_str());

			plugin->component = factory.createInstance<Steinberg::Vst::IComponent>(class_info.ID());

			break;
		}
	}

	if (!plugin->component) {
		printf("Couldn't initialize component\n");
		return nullptr;
	}

	printf("Created component\n");

	plugin->component->initialize(Steinberg::gStandardPluginContext);

	printf("Initialized component\n");

	Steinberg::Vst::IAudioProcessor *processor_ptr = nullptr;
	if (plugin->component->queryInterface(Steinberg::Vst::IAudioProcessor::iid, (void **)&processor_ptr) != Steinberg::kResultOk
		|| !processor_ptr) {
		printf("Component does not implement IAudioProcessor interface\n");
		return nullptr;
	}
	plugin->processor = Steinberg::shared(processor_ptr);

	printf("Retrieved IAudioProcessor\n");

	Steinberg::Vst::ProcessSetup setup; // { kRealtime, kSample32, blockSize, sampleRate };
	setup.processMode = Steinberg::Vst::kRealtime;
	setup.symbolicSampleSize = Steinberg::Vst::kSample32;
	setup.maxSamplesPerBlock = 512;
	setup.sampleRate = sample_rate;

	if (plugin->processor->setupProcessing(setup) != Steinberg::kResultOk) {
		printf("Failed to setup processing");
		return nullptr;
	}

	if (plugin->component->setActive(true) != Steinberg::kResultOk) {
		printf("Failed to set component active");
		return nullptr;
	}

	plugin->processing_is_set = false;

	return (RdPlugin*)plugin;
}

void destroy(RdPlugin *plugin) {
	VstPlugin *vst_plugin = (VstPlugin*)plugin;

	printf("Cleaning up\n");

	delete plugin;
	delete Steinberg::gStandardPluginContext;
}

void process(RdPlugin *plugin, float_t **inputs, float_t **outputs) {
	VstPlugin *vst_plugin = (VstPlugin*)plugin;

	printf("1\n");

	if (!vst_plugin->processing_is_set) {
		printf("Setting processing to true\n");

		if (vst_plugin->processor->setProcessing(true) != Steinberg::kResultOk) {
			printf("Failed to set processing to true\n");
			return;
		}

		vst_plugin->processing_is_set = true;
	}

	printf("2\n");

	Steinberg::Vst::ProcessData data;

	data.processMode = Steinberg::Vst::ProcessModes::kRealtime;
	data.symbolicSampleSize = Steinberg::Vst::kSample32;

	data.numSamples = 512;
	data.numInputs = 1;
	data.numOutputs = 1;

	printf("3\n");

	Steinberg::Vst::AudioBusBuffers input;

	input.numChannels = 2;
	input.silenceFlags = 0;
	input.channelBuffers32 = (Steinberg::Vst::Sample32 **)inputs;

	data.inputs = &input;

	Steinberg::Vst::AudioBusBuffers output;

	output.numChannels = 2;
	output.silenceFlags = 0;
	output.channelBuffers32 = (Steinberg::Vst::Sample32 **)outputs;

	data.outputs = &output;

	printf("4\n");

	vst_plugin->processor->process(data);

	printf("5\n");
}

#define DLL_EXPORT __declspec(dllexport)

extern "C" {

DLL_EXPORT bool rd_plugin_get_interface(RdPluginInterface *interface) {
	interface->create = create;
	interface->destroy = destroy;
	interface->process = process;

	return true;
}

}

As the OG author of the JUCE VST3 host and client - best of luck in these deep trenches! Steinberg’s docs are notoriously outdated, unkept, and plain misleading in most cases.

I suggest boiling down what the JUCE VST3 host code does to get a better sense of the flow as it works for most plugins now. It’s been heavily iterated on since VST3 support first came out.

I don’t see anything obvious that could be a mistake in your code. VST3PluginFormat::createPluginInstance is probably a decent place to start to compare.

Thanks, I’m really puzzled by what’s going on here.
It seems that I just can’t seem to call anything on IAudioProcessor from the processing thread. I’m a bit puzzled as to why.

Ah! Subtle error, when module went out of scope, it cleaned up a lot of stuff, meaning any further calls would cause an access violation. After resolving that, no more crashes.

2 Likes

A follow-up for the interested, it’s loading correctly now, playing audio, and launches an editor window!

1 Like

What made you decide to go down this rabbit hole? :slight_smile:

Minor frustrations with existing DAWs mainly. I figured it would be a fun project to try to make one myself.

Though I can likely not put enough time into it to make it a practical DAW, I can probably get as far as having a piano roll and multiple tracks.

I’m writing the DAW itself in Rust, so I also wanted to give a try how far I can push threading. Though, it’s somewhat limited by the VSTs themselves and the unclear threading garantuees. Another big challenge there is bridging the gap between Rust and the VST3 SDK’s C++ API, which is why the VST3 host itself is built as a DLL and loaded in separately.