WrenからCを呼び出す
Wrenの世界の中にいるとき、外側のCの世界は「外部」です。VMへ外部の機能を持ち込みたい理由は、二つあります。
- Cで書いたコードを実行したい。
- 生のCデータを保存したい。
Wrenはオブジェクト指向なので、振る舞いはメソッドに置きます。そのため、前者には外部メソッドがあります。同様に、データはオブジェクトに置くので、後者には外部クラスを定義します。このページでは、前者の外部メソッドを扱います。次のページでは、外部クラスを説明します。
Wrenから見ると、外部メソッドは通常のメソッドと同じです。Wrenのクラスで定義し、名前とシグネチャを持ち、呼び出しは動的にディスパッチします。唯一の違いは、メソッドの本体をCで書くことです。
外部メソッドは、Wrenで次のように宣言します。
class Math {
foreign static add(a, b)
}
foreignキーワードは、メソッドadd()をMathで宣言しますが、Cで実装することを、Wrenへ伝えます。静的メソッドもインスタンスメソッドも、外部メソッドにできます。
外部メソッドの束縛 #
外部メソッドを呼び出すとき、Wrenは、どのC関数を実行するか知る必要があります。この処理を束縛と呼びます。VMは、必要になったときに束縛します。外部メソッドを宣言するクラスを実行する時点、つまりclass文自身を評価する時点で、VMはホストアプリケーションに、その外部メソッドで使うC関数を問い合わせます。
この処理には、最初にVMを設定するときに渡す、bindForeignMethodFnコールバックを使います。このコールバックは、外部メソッド自身ではありません。アプリが外部メソッドを探すための、束縛用の関数です。
そのシグネチャは、次のとおりです。
WrenForeignMethodFn bindForeignMethodFn(
WrenVM* vm,
const char* module,
const char* className,
bool isStatic,
const char* signature);
外部メソッドを最初に宣言するたび、VMはこのコールバックを呼び出します。クラス宣言を含むモジュール、メソッドを持つクラスの名前、メソッドのシグネチャ、静的メソッドかどうかを渡します。上の例なら、次のような情報を渡します。
bindForeignMethodFn(vm, "main", "Math", true, "add(_,_)");
VMを設定するとき、指定した外部メソッドに適した関数を探し、そのポインターを返すCのコールバックを渡します。例えば、次のようにします。
WrenForeignMethodFn bindForeignMethod(
WrenVM* vm,
const char* module,
const char* className,
bool isStatic,
const char* signature)
{
if (strcmp(module, "main") == 0)
{
if (strcmp(className, "Math") == 0)
{
if (isStatic && strcmp(signature, "add(_,_)") == 0)
{
return mathAdd; // C function for Math.add(_,_).
}
// Other foreign methods on Math...
}
// Other classes in main...
}
// Other modules...
}
この実装はかなり面倒ですが、考え方は分かるでしょう。ホストアプリケーションでは、もっと気の利いた方法を使ってかまいません。
重要なのは、その外部メソッドに使うC関数のポインターを返すことです。Wrenがこの束縛を行うのは、クラス定義を最初に実行したときの一度だけです。その後は、返された関数ポインターを保持し、そのメソッドに対応付けます。これにより、外部メソッドの呼び出しが速くなります。
外部メソッドの実装 #
外部メソッドのためのC関数は、すべて同じシグネチャを持ちます。
void foreignMethod(WrenVM* vm);
Wrenからの引数は、Cの引数として渡すわけではなく、メソッドの戻り値も、Cの戻り値ではありません。代わりに、お察しのとおり、スロット配列を経由します。
Wrenから外部メソッドを呼び出すと、VMは、レシーバーと引数を使ってスロット配列を準備します。CからWrenを呼び出す場合と同じく、レシーバーオブジェクトはスロット0に置き、引数は、その後の連続したスロットに置きます。
スロットAPIで引数を読み取り、Cで必要な処理を行います。外部メソッドから値を返したいなら、スロット0へ入れます。次のように行います。
void mathAdd(WrenVM* vm)
{
double a = wrenGetSlotDouble(vm, 1);
double b = wrenGetSlotDouble(vm, 2);
wrenSetSlotDouble(vm, 0, a + b);
}
外部メソッドの実行中、VMは完全に中断しています。外部メソッドが戻るまで、ほかのファイバーは実行しません。外部メソッドの中からwrenCall()やwrenInterpret()を呼んでVMを再開しようとしてはいけません。VMは再入可能ではありません。
外部の振る舞いは、これで扱えます。それでは、外部の状態はどうするのでしょうか? そのためには、外部クラスが必要です。