このページの内容

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は再入可能ではありません。

外部の振る舞いは、これで扱えます。それでは、外部の状態はどうするのでしょうか? そのためには、外部クラスが必要です。

Cデータの保存 →← CからWrenを呼び出す