VST3ホストを作ろう（９） 〜VST-MA の概要〜

[目次](https://qiita.com/hotwatermorning/items/5ab2a687a4cbc73a0f56)

## はじめに

　VST3 規格はその設計のベース部分に、`VST Module Architecture (VST-MA)` という技術を採用しています。

　今回から三回にわたって、VST-MA について解説していきます。

## VST-MA とは

　VST-MA は Steinberg 社が開発している技術で、 Microsoft 社の `Component Object Model (COM)` というバイナリインターフェースに関する規格のサブセットのようなものです。 VST-MA の技術は VST3 SDK 以外にも、 Steinberg 社が開発しているホストアプリケーションの内部でも使われています。[^who-use-vst-ma]

[^who-use-vst-ma]: https://steinbergmedia.github.io/vst3_doc/base/index.html#piVstMa

　VST-MA は COM によく似ているため、 COM を利用した経験があれば VST-MA を理解するのは難しくないでしょう。とはいえ、もし COM を利用したことがなかったとしても、最低限 COM の概要と VST-MA の作法について理解すれば、十分に VST-MA を利用できるようになります。

* [VST-MA のドキュメント](https://steinbergmedia.github.io/vst3_doc/base/index.html)

### COM について

　VST-MA の解説に先立って、そのもとになった COM について簡単に紹介します。

　COM は Microsoft 社が開発した `Component Object Model (COM)` というバイナリインターフェースの規格です。[^link-to-com]

[^link-to-com]: https://ja.wikipedia.org/wiki/Component_Object_Model

　COM が定める仕様に従って定義されたクラス（正確にはインターフェースと呼ばれるもの）は、バイナリ境界を超えて利用できます。例えば通常の C++ プログラムでは、ある DLL 内で定義されたクラスを、その DLL をロードした実行ファイル側に受け渡して利用することはできず、一度 C 言語で扱えるデータ構造に変換してからデータを受け渡しする必要があります。[^about-cpp-abi-problem] COM を利用すればそのような変換を行う必要なく、通常のクラスを受け渡しするのに近い形で、 DLL 内のクラスを利用できます。

[^about-cpp-abi-problem]: これは、C++ のクラスのオブジェクトがメモリ上でどのように表現されるかが、コンパイラの種類、バージョン、ビルド設定などによって変わりうるためです。このため、もし異なる種類のコンパイラやビルド設定でビルドされた実行ファイルと DLL の間でクラスのポインタを受け渡して利用した場合、お互いに想定しているメモリ上での表現が違っているために、アクセス違反や予期せぬエラーを引き起こす可能性があります。

　また、 COM は特定のプログラミング言語によらない規格として定義されていて、C++ 以外にも C／Visual Basic／Java／C# などさまざまな言語で COM を利用できます。

　COM は規格なので、その実装が複数存在します。
　もっとも一般的なものは Microsoft 社が実装している COM で、 Microsoft COM とも呼ばれます。これはあくまで Windows のプラットフォーム上でのみ利用可能です。
　他にはクロスプラットフォームな COM の実装として、 Mozilla が開発していた XPCOM があります。[^link-to-xpcom]
　また、 Apple の CoreFoundation の中でも COM のサブセットが利用されています。[^link-to-core-foundation]

　以降では、 Microsoft 社が開発した COM の規格とその実装である Microsoft COM を特に区別することなく、単に COM と呼ぶこととします。

[^link-to-xpcom]: https://ja.wikipedia.org/wiki/XPCOM
[^link-to-core-foundation]: https://en.wikipedia.org/wiki/Core_Foundation

### VST-MA と COM の関係について

　COM は周辺技術まで含めると非常に巨大な規格です。 VST-MA は COM の仕組みの中心である「インターフェース」とそれを実装した「コンポーネント」という二種類のクラスを利用する仕組みだけを抽出したサブセットのようなものになっています。「インターフェース」と「コンポーネント」については後ほど詳しく記載します。

　VST-MA と COM の大きな違いとして、次の点が挙げられます。

* COM と異なり、 VST-MA では現状 C++ 向けの実装のみを提供していて、他の言語からの利用はサポートされない。
    * これは、他の言語から VST-MA を利用する需要がいまはないため。
* COM と異なり、 VST-MA ではコンポーネントの情報をレジストリに登録しない。  
    * 代わりに VST-MA では、モジュール[^about_module]の中にコンポーネントの情報を埋め込んでおき、プラグインファクトリというクラスを使用してコンポーネントの情報を取得できる仕組みを提供している。
    * ホストアプリケーションは、プラグインファクトリを使用してモジュールに含まれるコンポーネントの情報を列挙できる。
* COM と異なり、 VST-MA では、インターフェースを定義するための IDL ファイルを使用しない。
    * インターフェースは、VST3 SDK に予め定義されているものを使うことになるため、 IDL ファイルを使用して独自のインターフェースを定義することはないため。[^use-case-of-idl-file]
* COM と異なり、 VST-MA は、クロスプラットフォームで利用できる。
    * COM（厳密にはMicrosoft COM）は、 Windows 環境でのみ利用可能だが、 VST-MA は Win, Mac, Linux 環境で利用できる。（Linux サポートは今の所まだベータ扱い）

[^use-case-of-idl-file]: もしかすると、ホストアプリケーションとプラグインを両方作っている組織の場合は、それらの間で受け渡しする独自のインターフェースを定義したい場合があるかもしれません。しかしその場合でも、 VST-MA 用に IDL ファイルをコンパイルするコンパイラが存在しないので、結局手動でインターフェースを定義することになるでしょう。

[^about_module]: モジュールについては https://qiita.com/hotwatermorning/items/86c255aa874705c56453 を参照。

## VST-MA で使われる用語について

　VST-MA で使われる用語とその意味を以下に記載します。

### インターフェース

　VST-MA におけるインターフェースは、 COM におけるインターフェースとほぼ同義で、 C++ の抽象基底クラスのようなものです。実際に VST-MA では、インターフェースを C++ の抽象基底クラスとして実装します。

　あるインターフェースが提供する機能は純粋仮想関数として定義されます。この純粋仮想関数は、インターフェースを継承するコンポーネントによってオーバーライドされます。

　インターフェースは他のインターフェースから派生することもできて、インターフェース同士でクラス階層を形作ります。この階層の最も基底に存在するのが [`FUnknown`](https://steinbergmedia.github.io/vst3_doc/base/classSteinberg_1_1FUnknown.html) インターフェース[^about-funknown]です。ほかのすべてのインターフェースは、直接的ないし間接的に `FUnknown` を継承しています。[^interface-hierarchy]

[^interface-hierarchy]: 実際には、ほとんどのインターフェースは直接 `FUnknown` を継承していて、他のインターフェースを継承する例は多くありません。これは、 `FUnknown` 以外のインターフェース同士に継承関係があると、インターフェースの新しいバージョンを定義するときに障害となることがあるためです。このあたりの詳細は ["Versioning and inheritance"](https://steinbergmedia.github.io/vst3_doc/base/index.html#version) や次々回の記事を参照してください。

[^about-funknown]: COM における [IUnknown](https://docs.microsoft.com/en-us/windows/win32/api/unknwn/nn-unknwn-iunknown) クラスのようなもの。

![IEditController.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/944/8c4bf3ce-2621-c2af-f283-62d1963979ff.png)
*VST3 SDK に定義されている `IEditController` インターフェースの継承関係を表した図。 `FUnknown` インターフェースを `IPluginBase` インターフェースが継承し、それをさらに `IEditController` インターフェースが継承している。 （https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IEditController.html から抜粋）*

　インターフェースは、自身の型を識別するための ID を static メンバ変数として持っています。この ID は IID （a.k.a. Interface-ID）と呼ばれ、16 バイトの UUID で表されます。
　IID は、あるコンポーネントが継承しているいくつかのインターフェース同士のうち、どのインターフェースを利用するかを指定するために使用されます。詳しくは次々回の「[VST-MA の作法](https://qiita.com/hotwatermorning/items/7eaba9a544a361e17d42)」の記事にて解説します。

　インターフェースは、それを実装したコンポーネントがあって初めて利用可能になりますが、あるインターフェースのコンポーネントをホストアプリケーションとプラグインのどちらで定義すべきかが決まっている場合があります。例えば、 `IComponent` というプラグインとしての基本的な機能を定義したインターフェースは、それを継承したコンポーネントをプラグイン側で定義する必要があります。（そしてホストアプリケーションはプラグインのモジュールから `IComponent` インターフェースを受け取り、プラグインの処理を呼び出すことになります。）
　あるインターフェースのコンポーネントがホストアプリケーション側とプラグイン側のどちらで定義されるものかは、インターフェースのドキュメントに書かれた以下の記述によって判別します。

* [host imp]
    * このインターフェースのコンポーネントがホストアプリケーション側で実装されることを表す。
* [plug imp]
    * このインターフェースのコンポーネントがプラグイン側で実装されることを表す。

　このどちらも記述がない場合、そのインターフェースのコンポーネントはホストアプリケーション／プラグインどちらでも実装する可能性があることを表します。

### コンポーネント
    
　VST-MA におけるコンポーネントは、 COM におけるコンポーネント（a.k.a. COM オブジェクト、 COM クラス）とほぼ同義で、なんらかのインターフェースを継承し、そのインターフェースに定義された機能を実装した具象クラスです。

　ひとつのコンポーネントが複数のインターフェースを継承することもできます。その場合は、それぞれのインターフェースが定義している仮想関数をコンポーネント側ですべてオーバーライドする必要があります。

　コンポーネントは、自身の型を識別するための ID を定義していることがあります。この ID は CID（a.k.a. Component-ID or Class-ID）と呼ばれ、16 バイトの UUID で表されます。
　CID は、コンポーネントのクラス定義を知らない状態で、そのコンポーネントを指定して構築するために使用します。詳しくは次々回の「[VST-MA の作法](https://qiita.com/hotwatermorning/items/7eaba9a544a361e17d42)」の記事にて解説します。

　インターフェースが必ず IID を static メンバ変数として持っているのと異なり、すべてのコンポーネントが CID を持っているとは限りません。 VST3 SDK 内では、ホストアプリケーションからどのコンポーネントを構築するか指定できる必要があるコンポーネント[^selectable-component]や、テスト用に用意されたコンポーネントなど、一部のコンポーネントのみが CID を持っています。[^cid-in-component]

[^selectable-component]: つまり、 `DEF_CLASS`系マクロによって、プラグインファクトリに自身の情報を設定しているコンポーネント。

[^cid-in-component]: ホストアプリケーションとしては、モジュールから CID のリストが取得でき、そのリストに含まれる CID を指定してコンポーネントを構築できさえすればいいので、本質的にはコンポーネントが static メンバ変数として CID を持っている必要はありません。VST3 SDK 内で定義している一部のコンポーネントが CID を static メンバ変数として定義しているのは、クラス定義と CID を紐付けて扱いやすいので便宜的にそうしているだけです。実際に VST3 SDK のサンプルに含まれる "AGainSimple VST3" プラグインでは、自分自身をプラグインファクトリへ登録するときにその場で CID を渡し、そこで設定した CID を自分自身では管理しない仕組みになっています。（[その部分のソース](https://github.com/steinbergmedia/vst3_public_sdk/blob/c04e9bd690f50893742e2a082abab47af2c3efc8/samples/vst/again/source/againsimple.cpp#L689)）

## おわりに

　今回は VST-MA の概要について解説しました。[次回](https://qiita.com/hotwatermorning/items/17beecebaf833ed7407d)は、 VST-MA のインターフェースとコンポーネントがソースコード上でどのように定義されているかについて解説します。