Interface Technology Basics
VST 3.7
SDK for developing VST plug-in
|
VST-MA is a component model system which is used in all Steinberg host applications as the basic layer for plug-in support as well as for internal application components.
It is object-oriented, cross-platform and (almost) compiler-independent.
The basics are very much like Microsoft(R) COM, so if you are familiar with this technology, understanding VST-MA should be quite easy.
VST-MA is provided in C++ only. Interfaces in C++ are expressed as pure virtual class (which is a class with nothing but abstract methods). Unlike COM there is no support for C or other languages yet - simply because there has been no need for this so far. But all VST-MA interfaces can be transformed into different representations in case this should be inevitable some day.
It is currently available for Windows and Mac OS X.
The C++ files belonging to VST-MA are located in the following folders:
Note: The name 'VST Module Architecture' has only little relation to the 'Virtual Studio Technology' itself.
It describes the basic layer for any plug-in category supported in Steinberg hosts. VST-MA existed long before it was used as a base for VST 3 ifself.
Steinberg::FUnknown is the basic interface of VST-MA. All other interfaces are directly or indirectly derived from it.
Each interface has a unique identifier (IID) of type Steinberg::FUID. It is used to retrieve a new interface from another one (Steinberg::FUnknown::queryInterface). It is important to understand the difference between interface identifier and component identifier.
A component-ID or class-ID (CID) is used to identify a concrete implementation class and is usually passed to a class factory in order to create the corresponding component.
So a lot of different classes (with different class identifiers) can implement the same interfaces.
An interface may have a direction, meaning that the interface is expected to be implemented either in the plug-in or in the host. The nature of an interface is documented like this:
When neither of these is specified, the interface can be used in both ways.
Unlike C++ classes, interfaces do not use inheritance to express specializations of objects. Inheritance is used for versioning only. One of the strict rules is, that once an interface has been released, it must never change again. Adding new functionality to an interface requires a new version (usually an ordinal number is added to its name in this case).
A new version inherits the old version(s) of the interface, so the old and the new methods are combined in one interface. This is why specializations need to be modeled as separate interfaces! If a specialized interface were to inherit from the basic interface as well, an implementation class that needs to implement all of these interfaces would inherit the base interface twice, causing the compiler to run into ambiguities. So the specialization relation to a basic interface can only be expressed in the documentation:
You can find some example code here: Interface Versions and Inheritance
The first layer of VST-MA is binary-compatible to COM. The Vtable and interface identifier of FUnknown match with the corresponding COM interface IUnknown. The main difference is the organization and creation of components by a host application. VST-MA does not require any Microsoft(R) COM source file. You can find information about COM on pages like:
A module (Windows: Dynamic Link Library, MAC: Mach-O Bundle) contains the implementation of one or more components (e.g. VST 3 effects). A VST-MA module must contain a class factory where meta-data and create-methods for the components are registered.
The host has access to this factory through the Steinberg::IPluginFactory interface. This is the anchor point for the module and it is realized as a C-style export function named GetPluginFactory. You can find an export definition file in folder - public.sdk/win/stdplug.def which can be used to export this function.
GetPluginFactory is declared as follows:
Component modules do not require registration like DirectX. The host application expects component modules to be located in predefined folders of the file system. These folders and their subfolders are scanned for VST-MA modules during application startup. Each folder serves a special purpose:
Components
subfolder (e.g. "C:\Program Files\Steinberg\Cubase SX\Components") is used for components tightly bound to the application. No other application should use it.Any class that the factory can create is assigned to a category. It is this category that tells the host the purpose of the class (and gives a hint of which interfaces it might implement).
A class is also described with a name and it has a unique id.
The entry-point interface for any component class is Steinberg::IPluginBase. The host uses this interface to initialize and to terminate the plug-in component. When the host initializes the plug-in, it passes a so called context. This context contains any interface to the host that the plug-in will need to work.
Each plug-in category (VST 3 Effects, Project import/export Filters, Audio Codecs, etc...) defines its own set of purpose-specific interfaces. These are not part of the basic VST-MA layer.
Beginning with version 5 of Cubase and Nuendo, the internal structure of the host was modified to better support internationalization. Therefore, string handling was changed to utilize Unicode strings whenever strings are passed around. As a consequence, all the interfaces to plug-ins have changed from using ASCI to Unicode strings for call and return parameters. So in turn, all plug-in must be adapted to support Unicode. This has major consequences in that:
Writing plug-ins that are supposed to work only with Unicode hosts is easy. Use a current version of this SDK and develop a plug-in as usual. Make sure that you only ever pass Unicode UTF-16 strings to interfaces that have strings as call parameters and also be prepared that strings returned by these interfaces are always UTF-16. Therefore, to make things easier, it is recommended that Unicode strings are used throughout the plug-in's implementation, in order to avoid back and forth conversions. Also, use the Steinberg::String and Steinberg::ConstString classes from the Base module, they have been designed to work universally on both Mac and Win.
In Steinberg SDKs released before Cubase 5 the interface functions were using pointers of type char
for passing strings to and from the host. These have been changed now to using Steinberg's defined type tchar
which is equivalent to char16
, i.e. 16 bit character. In theory, there are many ways for representing 16 bits characters, but we chose to use the industry standard Unicode, so strings are expected to be encoded in UTF-16.
Accordingly, also the implementation of a plug-in needs to be adapted to deal correctly with Unicode encoded strings, as well as only ever passing Unicode strings to the host.
Technical note: Changing a function from using 8 bit to 16 bit character pointers may seem as only a minor modification, but in interface design this is a major intrusion, because an interface is a contract to the outside world that is never to be changed. Therefore, classes that are changed to use Unicode strings are distinguished and also receive a new unique class ID.
Even with the current SDK it is still possible to develop non-Unicode plug-ins. In the file pluginterfaces/base/ftypes.h, the line "#define UNICODE_OFF" is commented out, by uncommenting it you can revert all interfaces to using single byte ASCII strings. Alternatively you can also specify UNICODE_OFF as a preprocessor definition in your project file.
Also, the plug-in's factory info now does not define the Unicode flag anymore, so a Unicode host sees the compiled plug-in as non-Unicode. Also, when reverting to single byte strings the plug-in's implementation also has to be changed to behave correctly.
Technical note: When undefining Unicode, the class IDs also revert to the old ones.