残り2つの方法の1つ目はWindowsランタイム・コンポーネントをプロキシとして作成する方法だ。この場合のプロキシとは、mrubyが提供するCレベルAPIに相当するAPIをWindowsランタイム・コンポーネントに用意して、コンポーネントの利用側からはこれらのAPIを呼び出(して、プロキシがさらにmrubyを呼び出)すことを意味する。
例として、mrubyのStringオブジェクトを作成し、capitalizeメソッドを呼び出す場合を考えてみよう。
mrubyを利用するには、最初にmrb_open関数を呼び出してmrb_state*オブジェクトを取得する(mrb_openはmrubyスクリプトを実行する仮想マシン=RiteVMを作成する関数であり、mrb_state*オブジェクトはmrubyの関数で操作対象のRiteVMを示すディスクリプタの役割を持つ)。したがって、以降の呼び出しでは、ここで取得したmrb_state*オブジェクトを関数に与える。
mrubyオブジェクトはmrb_valueと型定義された識別子で表現される。また、オブジェクトのメソッド呼び出しにはmrb_funcall関数を利用する。すると、Stringオブジェクトを作成して、capitalizeメソッドを呼び出すコードは次のように書ける。
mrb_state* mrb = mrb_open();
mrb_value orbstr = mrb_str_new_cstr(mrb, "hello world !");
mrb_value crbstr = mrb_funcall(mrb, orgstr, "capitalize", 0);
上記のコードを、Windowsランタイム・コンポーネントを利用するJavaScriptコードでも記述できるような、Windowsランタイム・コンポーネントのAPIを考える。
まず、mrb_state*オブジェクトはランタイム・コンポーネント・オブジェクトが保持すればよい(以下のコード例では、Mrbオブジェクト)。
すると、JavaScriptコードからは次のように呼び出すことになるだろう。
var mrb = new Mrb();
var orbstr = mrb.str_new_cstr('hello world !');
var crbstr = mrb.funcall(orgstr, 'capitalize', 0);
上のように記述できるように、mrubyのmrb_value型をラップするMrbValueクラスと、mrb_state*オブジェクトを保持するとともに各種のAPI(ここではstr_new_cstrメソッドとfuncallメソッド)を提供するMrbクラスをC++/CXで定義してみよう。まずはMrbValueクラスから見てみよう。
ref class Mrb; // 前方宣言
// mrb_valueをラップするクラス
// 生存中は内包するmrb_valueをGCから保護する
public ref class MrbValue sealed // Windowsランタイム型とするにはpublic ref class クラス名 sealedとして宣言する
{
friend Mrb; // Windowsランタイム・コンポーネントではpublicに強い制限があるため、friendを多用することになる
public:
~MrbValue()
{ // 前方参照となるので実装はソース・ファイルへ記述する必要があるが、ここでは簡略化のため直接記述する
// GC防御用ハッシュからオブジェクトを削除しGC可能とする
if (!mrb_fixnum_p(v) && !mrb_float_p(v) && !mrb_nil_p(v) && !mrb_symbol_p(v))
{
mrb_hash_delete_key(mrb->mrb, mrb->protect_values, value);
}
}
Platform::String ToString()
{
mrb_value rstr = (!mrb_respond_to(mrb->mrb, value, mrb_intern(mrb->mrb, "inspect")))
? mrb_any_to_s(mrb->mrb, value)
: mrb_funcall(mrb->mrb, value, "inspect", 0);
size_t len = RSTRING_LEN(rstr);
char* p = RSTR_PTR(rstr);
wchar_t* wptr = (wchar_t*)mrb_calloc(mrb->mrb, sizeof(wchar_t), len + 1);
// MSVCRTのエンコーディング変換関数は利用できないのでWin32 APIを使う
int cb = MultiByteToWideChar(CP_UTF8, 0, p, len, wptr, (len + 1) * sizeof(wchar_t));
*(wptr + cb) = 0;
Platform::String^ ret = ref new Platform::String(wptr);
mrb_free(mrb, wptr);
return ret;
}
private:
// C/C++の世界
MrbValue(Mrb^ m, mrb_value v) // mrb_valueはWindowsランタイム型ではないのでprivateにする
{ // 前方参照となるので実装はソース・ファイルへ記述が必要だが、ここでは簡略化のため直接記述する
mrb = m;
value = v;
if (!mrb_fixnum_p(v) && !mrb_float_p(v) && !mrb_nil_p(v) && !mrb_symbol_p(v))
{
mrb_hash_set(mrb->mrb, mrb->protect_values, v, mrb_nil_value()); // GC防御用ハッシュへ保存
}
}
mrb_value value;
Mrb^ mrb;
};
ここでは、コンストラクタとデストラクタ、オブジェクトの文字列化を行うToStringメソッドだけを定義している。publicなインターフェイスではWindowsランタイム型が使われるとともに、前回説明をしたABI(アプリケーション・バイナリ・インターフェイス)に適合するようにクラスの型が宣言されていることに注意。privateなメンバやToStringメソッド内部では、Windowsランタイム型以外の型を使えるが、外部に公開できるのはABIに適合するものだけだ。
ToStringメソッドではmrubyが提供するAPIやWin32 APIを使用して文字列化を行った結果から、最終的にPlatform::String型のオブジェクトを作成して、これを戻り値としている。
次にMrbクラスを見てみよう。
// mrb_state*を保持するオブジェクト
// GC保護用のオブジェクト配列を保持する
public ref class Mrb sealed
{
friend MrbValue;
public:
// C++/CX(WindowsランタイムABI)の世界
Mrb()
{
mrb = mrb_open();
protect_values = mrb_hash_new(mrb);
mrb_gc_protect(mrb, protect_values); // GC防御用ハッシュを確保する
}
~Mrb()
{
mrb_close(mrb);
}
MrbValue^ str_new_cstr(Platform::String^ str) // Windowsランタイム型のポインタは^で示す
{
char* pcs = Convert(str);
mrb_value v = mrb_str_new_cstr(mrb, pcs);
mrb_free(mrb, pcs);
return ref new MrbValue(this, v); // Windowsランタイム型のオブジェクト(ref class)はref newで生成する
}
MrbValue^ funcall(Platform::String^ func, int argc, const Platform::Array<MrbValue^>^ args)
{ // Platform::Arrayなどのコレクション型はジェネリックスを利用可能
……
}
// TODO
private:
// C/C++の世界
char* Convert(Platform::String^ str)
{
// Unicode文字列をutf-8のchar*に変換する
}
mrb_state* mrb;
mrb_value protect_values;
};
Mrbクラスでは、mrubyのmrb_open関数をコンストラクタにラップし、mrubyの実行環境である仮想マシンを表すmrb_state*オブジェクトを内部的に保持する。これ以外に、文字列作成と関数呼び出しを行うmrubyのAPIのプロキシ、ヘルパ関数を実装している。ここでも外部に公開するインターフェイスには、Windowsランタイム・コンポーネントのABIに準拠する型だけを使うようにしている。
このように、元のCライブラリのAPIをどのように変換すれば、Windowsランタイム・コンポーネントABIを満たせるかをいくつかの例で考え、必要となるヘルパ関数(上の例ではConvert関数)やヘルパ・オブジェクト(上の例ではMrbValueクラス)を定義する。
後は、元のCライブラリのヘッダ・ファイルから関数を抽出し、引数や戻り値の型に応じてヘルパ関数を適用して元の関数へ委譲するソース・コードを生成するプログラムを作成し、それを実行して、上のリストの「// TODO」で示した箇所を埋めることでプロキシを完成させる。
Copyright© Digital Advantage Corp. All Rights Reserved.