なんとなくLogic Expressを買ってみたら、なんとなくAudio Unitを作りたくなってしまいました。
Audio Unitの実態は、Carbonコンポーネントっていうやつで、Cocoaバンドルとは異なる構造になっています。
Cocoaバンドルでは、主要クラスのオブジェクトを入り口として、ObjCの動的なメソッド呼び出しによりバンドルを操作します。
Carbonコンポーネントでは、ComponentRoutineProcPtr型のコールバック関数を入り口として、コンポーネントを操作します。ComponentRoutineProcPtrには、ComponentParameters構造体*cpとcomponentStorageの2つの引数があり、cp->whatがコマンドを表し、cp->paramsがコマンドに対するパラメータになっているので、switch - caseで処理を分岐させるのがオーソドックスなやり方です。
ComponentResult entryPoint(ComponentParameters *cp, Handle componentStorage)
{
switch(cp->what)
{
case kAudioUnitGetPropertySelect:
return function1(componentStorage,
cp->params[4],
cp->params[3],
cp->params[2],
cp->params[1],
cp->params[0]);
case kAudioUnitSetPropertySelect:
return function2(componentStorage,
cp->params[4],
cp->params[3],
cp->params[2],
cp->params[1],
cp->params[0]);
case ...
}
return badComponentSelector;
}しかし、AudioUnitの場合コマンドは20個ぐらいあり、コマンドの数だけcase文を並べると、少々ソースコードが見づらくなります。また、あるコマンドが実装されているかどうかを要求してくるコマンドもあるので、重複したコードを書くことにもなってしまいます。
そこで、コマンドとそれに対応する関数ポインタのテーブルを予め作っておき、そこから動的に関数を呼び出す方法を考えてみた。
ComponentResult entryPoint(ComponentParameters *cp, Handle componentStorage)
{
void* function = findFunction(cp->what);
if(function == NULL)
{
return badComponentSelector;
}
register int result asm("eax");
int paramSize = (int)cp->paramSize;
int param = (int)cp->params;
asm
{
mov eax, paramSize
test eax, eax
jz L2
mov ecx, eax
mov edx, param
L1:
sub esp, 4
mov eax, [edx]
mov [esp], eax
add edx, 4
sub ecx, 4
ja L1
L2:
push componentStorage
call function;
pop ecx
add esp, paramSize
}
return result;
}まず、findFunction()が、テーブルを参照してコマンド番号から関数ポインタを返す関数です。
そして、その次に来るのが、関数を動的に呼び出すアセンブラコードです。このように、C言語のソース内にアセンブラのソースを埋め込むことをインライン・アセンブラと言います。
変数の宣言で、registerと書くとその変数をCPUのレジスタに割り当てることをコンパイラに要求することができます。ここで、さらにasm("eax")などと書くと、割り当てるレジスタを指定することもできます。asm{ ... }内には生のアセンブラを書く事ができますが、見てのとおりC言語の変数や引数を直接指定できるので、C言語とアセンブラ間での値のやり取りが簡単にできるようになっています。
それで、なぜここでいきなりアセンブラが出てきたかと言うと、C言語で上記のような処理を書くことが不可能だからです。
C言語でも関数ポインタを使って動的な関数の呼び出しはできますが、その際にはtypedefを使って呼び出し先の関数のシグネチャを定義しておく必要があります。シグネチャが不確定な関数は呼び出せません。また、どんなシグネチャにも対応できるように、func(...)などと可変長引数を使った場合でも、C言語には可変長引数を参照することはできても、設定することができないため、どうにもならないのです。
ちなみに、Objective-Cでメソッドを動的に呼出すobjc_msgSend()関数も、同じような理由からアセンブラで書かれています。