このページの内容

スロットとハンドル

wrenInterpret()でコードを実行できますが、そのコードだけでは、特に面白いことはできません。既定では、VMは外の世界から隔離されているので、ノートパソコンを膝を温めるものにするくらいしかできないのです。

Wrenコードを役立つものにするには、VMが外の世界と通信する必要があります。Wrenでは、VMとの間でデータを渡すために、統一した一組の関数を使います。これらの関数は、スロットとハンドルという二つの基本概念に基づきます。

スロット配列 #

Wrenへデータを送り、Wrenから読み取り、一般にCからWrenオブジェクトを操作するときは、スロットの配列を経由します。VMとCコードが、相手に処理してもらうメッセージを残す、共有の掲示板だと考えてください。

配列はゼロから始まり、各スロットには、どの型の値でも格納できます。大きさは動的に変わりますが、使う前に十分な個数のスロットがあることを確保するのは、利用者の責任です。次の関数を呼び出して行います。


wrenEnsureSlots(WrenVM* vm, int slotCount);

これは、指定した個数のスロットが利用できるよう、必要に応じてスロット配列を拡張します。すでに十分な大きさなら、何もしません。通常は、Wrenへ送るデータをスロットへ格納する前に、一度呼び出します。


wrenEnsureSlots(vm, 4);
// Can now use slots 0 through 3, inclusive.

スロット配列を確保した後、その存在を保証できるのは、Wrenへ制御を戻すまでです。wrenCall()やwrenInterpret()を呼ぶこと、外部メソッドから戻ることも、制御を戻す操作に含まれます。

有効性を確保していないスロットを読み書きした場合、何が起きるかをWrenは一切保証しません。利用者のコンピューターから煙と羽が飛び出したといううわさを聞いたこともあります。

スロット配列の大きさを知りたい場合は、次を使います。


int wrenGetSlotCount(WrenVM* vm);

これは、配列内のスロット数を返します。その数は、確保した個数より多い場合がある点に注意してください。Wrenは可能なら、この配列のメモリーを再利用します。そのため、大きな配列が残っていた場合は、必要なものより大きな配列が得られることがあります。

WrenがCコードを呼び出し、データを渡すときは、渡すオブジェクトに十分な個数のスロットを確保します。

スロットへの書き込み #

スロットが得られたら、wrenSetSlot<type>()という名前の一連の関数を使って、データを保存します。<type>は、データの種類を表します。単純なものから始めましょう。


void wrenSetSlotBool(WrenVM* vm, int slot, bool value);
void wrenSetSlotDouble(WrenVM* vm, int slot, double value);
void wrenSetSlotNull(WrenVM* vm, int slot);

これらは、それぞれCの基本的な値を受け取り、対応するWrenの値へ変換します。(Wrenの基本の数値型はdoubleそのものなので、実際には大した変換は行いませんが、考え方は分かるでしょう。)

文字列データをWrenへ渡すこともできます。


void wrenSetSlotBytes(WrenVM* vm, int slot,
                      const char* bytes, size_t length);

void wrenSetSlotString(WrenVM* vm, int slot,
                       const char* text);

どちらも、バイト列を、Wrenのガベージコレクターが管理する新しいStringオブジェクトへコピーするため、呼び出した後は、手元のコピーを解放できます。二つの違いは、wrenSetSlotBytes()が明示的な長さを受け取ることです。Wrenの文字列には、ヌルバイトを含む任意のバイト値を入れられるので、それらも渡せます。通常の文字列でも、長さが分かっているなら、こちらを使う方が少し速くなります。後者は、strlen()を使って文字列の長さを計算します。

スロットの読み取り #

もちろん、スロットからデータを取り出すこともできます。単純なものは、次のとおりです。


bool wrenGetSlotBool(WrenVM* vm, int slot);
double wrenGetSlotDouble(WrenVM* vm, int slot);

これらは、対応する型のWrenの値を受け取り、生のC表現へ変換します。文字列については、次の関数があります。


const char* wrenGetSlotString(WrenVM* vm, int slot);
const char* wrenGetSlotBytes(WrenVM* vm, int slot,
                             int* length);

これらは、文字列の最初のバイトへのポインターを返します。長さも必要なら、後者は、lengthが指す変数に、その長さを保存します。どちらも、Wrenが管理するバイト列への直接のポインターを返します。このポインターを長く保持するべきではありません。Wrenは、そのデータを移動したり解放したりしないことを保証しません。

これらの関数では、動的型付けのWrenデータから、静的型付けのCへ移ります。値を正しい型で読み取ることを保証するのは、利用者の責任です。現在、文字列を格納しているスロットから数値を読み取ると、困ったことになります。

幸い、通常はスロット内のデータの型が分かっています。分からない場合は、次のように問い合わせられます。


WrenType wrenGetSlotType(WrenVM* vm, int slot);

これは、スロット内の値の型を表す列挙値を返します。対象は、C APIが対応する基本的な値だけです。範囲やクラスのインスタンスなどは、WREN_TYPE_UNKNOWNとして返ります。この種類のデータをWrenとCの間で移動したい場合は、まず、オブジェクトを単純な基本値へ分解するか、外部クラスを使う必要があります。

変数を探す #

スロットへデータを入れたり、取り出したりする、ほかの補助関数もあります。最初は、次の関数です。


void wrenGetVariable(WrenVM* vm, const char* module,
                     const char* name, int slot);

これは、指定した名前のモジュールで、指定した名前のトップレベル変数を探し、その値を指定したスロットへ保存します。クラスも、変数に保存したオブジェクトにすぎないため、名前でクラスを探すことにも使えます。クラスの静的メソッドを呼び出す際に便利です。

文字列を扱うほかのメソッドと同様、この関数は少し遅くなります。名前をハッシュ化し、モジュールの文字列テーブルで探す必要があります。性能が重要な、頻繁に実行するループの中では、呼び出しを避けたいでしょう。代わりに、ループの外で一度だけ変数を探し、ハンドルでオブジェクトへの参照を保存する方が速くなります。

リストの操作 #

スロット配列は、固定個数のオブジェクトをWrenとCの間で移動するのに適しています。しかし、ときには、より多くの、または大きさが動的に変わるデータのまとまりを渡す必要があります。それにはListオブジェクトが適しているため、C APIでは、それを直接操作できます。

Cから、新しい空のリストを作成するには、次を使います。


void wrenSetSlotNewList(WrenVM* vm, int slot);

これは、作成したリストを指定したスロットへ保存します。Cで作ったものでも、Wrenで作ったものでも、スロット内のリストへ要素を追加するには、次を使います。


void wrenInsertInList(WrenVM* vm, int listSlot, int index,
                      int elementSlot);

intパラメーターがたくさんあります。

  • listSlotは、リストオブジェクトを格納しているスロットです。変更するのは、このリストです。Cでリストを作成したなら、wrenSetSlotNewList()へ渡したスロットになります。

  • indexは、要素を挿入するリスト内のインデックスです。Wren内と同様、負の数で末尾から数えられるため、-1は、リストの末尾へ追加します。

  • elementSlotは、リストへ挿入したい値があるスロットを指定します。

このAPIでは、Cの値をリストへ入れる操作は二段階になります。まず値をスロットへ移し、続いて、そのスロットから取り出してリストへ挿入します。少し面倒ですが、これにより、各基本型の値をスロットへ移動するために、同じ一組の関数を使えます。そうしなければ、wrenInsertInListDouble()やwrenInsertInListBool()などが必要になります。

ハンドル #

スロットは、CとWrenの間で基本的なデータを渡すのに適していますが、二つの制限があります。

  1. 有効期間が短い。さらにWrenコードを実行すると、スロット配列は直ちに無効になります。あるオブジェクトを永続的に追跡するために、スロットを使うことはできません。

  2. 基本型だけに対応する。スロットには、どの型の値でも格納できますが、ここまでで見たC APIでは、単純な基本値でないものに対して、何かを行うことはできません。例えば、あるクラスのインスタンスへの参照を得たい場合は、どうすればよいでしょうか?

これらへ対応するため、ハンドルがあります。ハンドルは、文字列、数値、クラスのインスタンス、コレクションなど、どの種類のオブジェクトへの参照でも包みます。次を使って作成します。


WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot);

これは、指定したスロット内のオブジェクトを受け取り、それを包む新しいWrenHandleを作って、そのポインターを返します。次を呼び出すと、そのオブジェクトをWrenへ戻せます。


void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle);

この操作は、WrenHandleを無効にしない点に注意してください。引き続き使えます。

ハンドルの保持と解放 #

ハンドルは、どの型のオブジェクトでも包む不透明なラッパーですが、同じくらい重要なのは、永続的であることです。WrenがWrenHandleへのポインターを渡したなら、そのポインターが有効であり続けることを保証します。必要な限り、保持できます。ガベージコレクションが起きても、ハンドルと、それが包むオブジェクトを安全にメモリー内へ保持します。

内部では、Wrenは、作成したすべてのWrenHandleのリストを保持します。これにより、ガベージコレクション中に、それらをすべて見つけ、オブジェクトを解放しないようにできます。しかし、もう保持したくなくなった場合はどうするのでしょうか? Cは手動のメモリー管理に依存するため、WrenHandleも同じです。使い終わったら、次を呼び出して、明示的に解放する必要があります。


void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle);

これは、包んでいるオブジェクトを直ちに削除するわけではありません。同じオブジェクトへの参照が、プログラム内のほかの場所にもあるかもしれないためです。無効になるのは、WrenHandleラッパー自身だけです。呼び出した後は、そのポインターを再び使えません。

VMを終了する前に、作成したすべてのWrenHandleを解放する必要があります。解放しなければ、どこかで資源がリークしている可能性があるため、Wrenは警告します。

WrenとCの間で値を渡す方法は分かりましたが、その値で何かを実際に行う方法は、まだ分かりません。次は、スロットを使って、CからWrenのメソッドへパラメーターを渡す方法を学びます。

CからWrenを呼び出す →← はじめに