Need help understanding VST3 extensions

I’m about to try out the recently added VST3ClientExtensions class, my goal is to implement the Presonus::IContextInfoHandler interface. This already worked a while by a patch in the juce codebase that predated the VST3ClientExtensions. But to be honest, my understanding of this VST3 (com?) interface stuff is a bit limited, I think I get the rough idea, but I’m lacking some understanding of how all the pieces work together.

What I found out so far is that VST3ClientExtensions::queryIEditController is called quite a few times, always passing in iids and then in case the iid passed matches the iid of the extension interface that I want to implement I set the pointer to the instance of my interface and return. Looking at the call stack to that function all the stuff that the VST3 wrapper does looks more or less senseful to me. And this all works great in reaper and I can see Presonus::IContextInfoHandler::notifyContextInfoChange callbacks arriving after doing so :tada:

Now what’s confusing to me is, that Presonus::IContextInfoHandler is derived from Steinberg::FUnknown, so I need to override tresult PLUGIN_API queryInterface to make it compile. Setting a breakpoint into the overridden function shows me that it’s never called – at least when testing with Reaper. The function signature looks nearly identical to VST3ClientExtensions::queryIEditController.

Looking at the ReaperEmbeddedViewPluginDemo code I see that there, both functions are overridden and basically both do the same internally but both are implemented at a different hierarchy level.

So, could someone explain to me under which circumstances queryInterface might be called by whom and if it’s a valid assumption that it’s just fine to put all my logic into that function and just forward the call to queryIEditController to that? Or am I mixing up concepts here or showing a massive lack of understanding how the VST3 API is intended to work?

Bonus question:

In the ReaperEmbeddedViewPluginDemo example, EmbeddedUI implements addRef and release which increment/decrement an atomic counter. However, that counter is never accessed. So what’s the purpose of that variable?

queryInterface will probably not be called on an extension class, but it’s probably a good idea to implement the function so that it does the right thing (i.e. increment the refcount and return a valid pointer to the current instance if the IID matches), just in case the host does something unexpected and needs to sanity check the interface.

That’s fine if you only need to implement a single interface. If you are implementing multiple interfaces, you might wish to implement them all as separate classes, in which case queryIEditController will need to forward to each of those classes in turn.

The purpose is just to return a consistent value from addRef. If we were to do things absolutely properly, we would probably place an EmbeddedUI on the heap and manage its lifetime using refcounting. This felt like overkill for the demo, where the implementation is fairly lightweight and it’s safe to call handleEmbeddedUIMessage at any point as long as the plugin is alive. If the implementation were more expensive to keep alive, it might make more sense to implement the refcounting properly so that the interface could be destroyed once the host is done using it.

Thanks, that clears up most of my questions.

Indeed, after throwing all into a single class at first where both member functions looked quite redundant, I went this way in the end with my VST3ClientExtensions subclass holding a pimpl-like member to the actual extension implementation, which now seems a lot cleaner and also keeps all the VST and extension headers away from the public interface.

So queryInterface should increment the ref count? This wasn’t clear to me from reading the example code, where the ref count is not incremented if I don’t get it totally wrong :thinking: Am I overlooking something in the example or is there any good reason why the example doesn’t increment the ref count? But yes, the Steinberg code documentation says so too…

And while we are there, is there any special reason that the internal atomic counter is an int but return values are always casted to a uint32_t? I probably would have made the counter variable itself an atomic uint32_t if the FUnknown interface uses that data type.

Yeah, I think you’re right, I’ll fix this in the demo.

No, not really, I’ll fix that too. Thanks for the feedback!

1 Like

Great, thank you too for your responsiveness :slight_smile:

1 Like

The suggested changes are now on develop, thanks again!