Inheritance Overview
The Controller is a composite of multiple interface implementations. At first glance, it might seem complicated, but there's a lot of repetition and very few implementations.

The implementations are in ComponentBase and EditController. It's good to become familiar with them. The documentation in the code is decent.
Call Sequence
Let's look at the sequence of events. The order isn't guaranteed, but this is what you can typically expect.

Design
The real complexity of a plugin is in the design and organization of it. Your UI will need access to state provided by the Controller (e.g., serializing and deserializing models that outlive the UI but are controlled by the UI, safe UI access to the Controller's MPI, one source of truth between the Processor and Controller, communicating without interfering with the real-time audio thread/process).
A Simple & Practical Approach
- Create a ControllerContext that gets passed into the IPlugView implementation whenever createView() gets called. This context contains objects, such as models and states, that need to outlive the UI while also being a requirement of the UI.
- Serialize and Deserialize the parts of that ControllerContext that you want to be loaded whenever a project is restored.
- Use interfaces to wrap many of the components
- As an example, here's a simplified way to manage handing off an MPI to the UI components safely:
This is a trimmed-down version of what a ControllerContext might look like. We're going to focus solely on a messenger:
class ControllerContext final
{
public:
void setMidiMessenger( IMidiMessenger * midiMessenger )
{
m_midiMessenger = midiMessenger;
}
IMidiMessenger * getMidiMessenger() override
{
return m_midiMessenger;
}
private:
IMidiMessenger * m_midiMessenger { nullptr };
Let's set up an interface that exposes only the functionality that we want the UI to have for communicating with the Processor:
struct IMidiMessenger
{
virtual ~IMidiMessenger() = default;
// let's assume that we want our users to be able to click
// on a widget and play a midi note while the mouse button is pressed down
virtual bool sendLiveMidiMessage( const LiveMidiEvent_t& midiEvent ) = 0;
};
Now we need to implement this. We don't need it exposed to other components, so let's hide it within our Controller (all put in the header for reading convenience):
class VSTController final :
public Steinberg::Vst::EditController
{
class MidiMessengerImpl final : public IMidiMessenger
{
public:
// Remember that ComponentBase implements IConnectionPoint
explicit MidiMessengerImpl( ComponentBase * messageBus )
: m_messageBus( messageBus )
{}
~MidiMessengerImpl() override
{
LOG_DEBUG( "shutting down messenger" );
}
bool sendLiveMessage( const LiveMidiEvent_t& midiEvent ) override
{
// are we connected?
if ( !m_messageBus )
{
LOG_ERROR( "unable to send message. no peer connected." );
return false;
}
// this is a helper for sending binary messages
return MessageHelper::sendBinaryMessage(
m_messageBus,
LiveMidiEvent_t::MSG_ID,
LiveMidiEvent_t::MSG_ATTR,
&midiEvent,
sizeof( LiveMidiEvent_t ) );
}
ComponentBase * m_messageBus { nullptr };
};
// ... the rest of the Controller's implementation elided ...
private:
// this is us. as long as we're valid, then it's valid.
MidiMessengerImpl m_midiMessenger { this };
// here's the origin of our context. we'll update the Messenger
// as it becomes available. the host will give us that info.
ControllerContext m_stateContext;
};
And all we need to do now is reference ourselves on the connect() and disconnect() Controller calls:
/////////////////////////////////////////////////////////
/// PUBLIC
tresult VSTController::connect(IConnectionPoint *other)
{
m_stateContext.setMessenger( &m_midiMessenger );
return EditController::connect(other);
}
/////////////////////////////////////////////////////////
/// PUBLIC
tresult VSTController::disconnect(IConnectionPoint *other)
{
m_stateContext.setMessenger( nullptr );
return EditController::disconnect(other);
}
And now in the createView, we can do something like the following:
/////////////////////////////////////////////////////////
/// PUBLIC
IPlugView * VSTController::createView(FIDString name)
{
if (FIDStringsEqual (name, Vst::ViewType::kEditor))
{
// pass our ControllerState to our view.
// also note that we do NOT own the pointer. the
// host will manage it for us.
return ViewFactory::createView( m_stateContext );
}
return EditController::createView(name);
}