Windowsランタイム・コンポーネントとしてコンパイルできないコードとコンパイルできるコード
前述の制約の例として、次のリストに示すようなWinRTClassクラスをWindowsランタイム・コンポーネントとして公開したいとしよう。このクラスに付随するクラスやenumを、その内部にネストしてpublic定義すると、コンパイル時に初めて「Type ... is a nested type. Nested types cannot be exported to the Window Runtime.」というエラーとなる。
namespace Sample
{
public sealed class WinRTClass // ← Windowsランタイム・コンポーネントクラスはpublic sealedとする必要がある
{
public enum Foo // ← ネストしたpublic定義なのでエラー(internalやprivateなら問題ない)
{
A,
B,
}
public delegate string FooToString(Foo foo); // ← エラー
public sealed class ResultObject // ← エラー
{
public string Value { get; set; }
}
public ResultObject FooBar(FooToString fts)
{
return new ResultObject
{
Value = fts(Foo.A),
};
}
}
}
前述した注意点のうち、コード再利用の観点から問題となりそうな点について補足しよう。
4.の「publicクラスは、publicフィールドを含んではならない」については、良いコーディング・スタイルを使っていれば特に問題となることはないだろう。クラスにpublicなフィールドを定義することは“悪”である。従って、既存コードでpublicなフィールドを多用しているならば、この機会にプロパティに変更しよう。
5.の「public構造体は、publicフィールドのみを含められる」については、構造体の役割から、そういうものだと割り切るしかない。上でも説明したがWindowsランタイム型として扱われる構造体は単なるメモリ・ブロックなので、そこにメソッド定義や可視性がないフィールドを含めることはできない。もし再利用したいコードが、メソッド(当然プロパティを含む)やpublic以外のフィールドの定義を持つpublic構造体を利用している場合は、Windowsランタイム・コンポーネントとして公開する必要がなければinternal以下の可視性に変える(Windowsランタイム・コンポーネント内部ではそうしたクラスや構造体も利用可能である。ただ、Windowsランタイム型として外部には公開できないという話だ)。もし公開する必要があるのであればpublic sealed classに書き換えるのがよいだろう。
6.の「publicメソッド(プロパティ)は、Windowsランタイム互換型のみをパラメータや戻り値に利用できる」ことが問題となるのは、筆者の経験では、関数をパラメータや戻り値に取る場合だ。JavaScript用にAPIを提供するとなれば、当然、関数を扱うメソッドを作りたくなるのだが、このときにActionやFuncは利用できず、必ずdelegateを定義しなければならない。これは現時点でジェネリクスをサポートしているのが、コレクション・インターフェイスに限定されているからだ。もっとも、関数を返す場合はラムダ式を利用できるので、手間がかかるのはインターフェイスの定義に限定される。従ってそれほど問題とはならないだろう。
以上の点に気を付ければ、Windowsランタイム・コンポーネントだからといって特別に変わったことが必要となるわけではない。上記の制約に対応して、Windowsランタイム・コンポーネントとしてビルドできるようにしたコードを次に示す。
namespace Sample
{
public sealed class WinRTClass // ← 公開するクラスはpublic sealedとする
{
public ResultObject FooBar(FooToString fts) // ← publicで定義したクラスなどはメソッドの引数や戻り値に利用可能(この場合は戻り値のResultObject構造体と、仮引数のFooToStringデリゲート)
{
return new ResultObject
{
Value = fts(Foo.B)
};
}
public FooToString FooBar() // ← オーバーロードしたメソッドは引数の数のみが呼び分けの対象となる
{
return foo => foo.ToString(); // ← delegateとしてラムダ式を利用可能
}
}
public enum Foo // ← enumもWindowsランタイム型として定義できる
{
A,
B,
}
public delegate string FooToString(Foo foo); // ← delegateによって関数を定義可能
public struct ResultObject // ← public構造体はpublicフィールドのみを定義可能
{
public string Value;
}
}
上記のようにインターフェイスに利用するenum、デリゲート、クラスを名前空間(この場合はSample)の直下に定義することで、JavaScriptから以下のように呼び出すことが可能なWindowsランタイム・コンポーネントが生成できる。
なお、上のリストでは利用していないが、同数のパラメータを取るメソッドがオーバーロード定義されている場合は、DefaultOverloadAttribute属性(Windows.Foundation.Metadata名前空間)を付けた既定のメソッドを指定する必要がある。JavaScriptはパラメータの型にかかわらず、この属性を付けたメソッドを呼び出す。ただ、JavaScriptコードからの呼び出しを前提とするのであれば、バグの原因以外の効用がないので、メソッドはオーバーロードすべきではない。
上記のWindowsランタイム・コンポーネントを利用するJavaScriptコードの例を次に示す。
var wrc = new Sample.WinRTClass();
div.innerHTML = wrc.fooBar(function(foo) { return foo.toString(); }).value; // ← 「1」がinnerHTMLに設定される
div2.innerHTML = wrc.fooBar()(Sample.Foo.b); // ← 関数を返すメソッドの戻り値の呼び出し。「B」がinnerHTMLに設定される
上のJavaScriptの呼び出し例から分かるように、JavaScriptプロジェクトにWindowsランタイム・コンポーネントを取り込むと、自動的にメソッド名とプロパティ名、enum値が小文字始まりの名前に変換される。IntelliSenseには変換後の名前が表示される。
もう1点、上のJavaScriptの例で興味深いのは、enumオブジェクトはJavaScriptに渡った時点では単なるJavaScriptのNumberオブジェクトに変換されていることだ。そのため、2行目のtoString関数の呼び出しは、元のenumオブジェクトに対する呼び出し結果の「B」ではなく、Numberオブジェクトの「1」に対する呼び出しとなるため「1」と出力される。
Windowsランタイム・コンポーネントを利用するJavaScriptプロジェクトを作成し、上記のコードを実行した画面を次に示す(HTMLなど詳細なコードは割愛する)。
なお、Windowsランタイム・コンポーネントに限らず、C#やVBでWindowsストア・アプリを開発する時は、必ずusingステートメントを利用して、参照する名前空間を取り込んでおくことを勧める。というのは、Windowsストア・アプリ用.NETでは、多数の拡張メソッドが提供されているからだ。拡張メソッドによって追加されたメソッドをIntelliSenseに反映させるには、あらかじめusingステートメントによって名前空間を取り込んでおく必要がある。
ここまではC#を例に取り、Windowsランタイム・コンポーネント作成時に注意すべき点について見てきた。本連載ではこれ以降、C言語で記述された既存コードの再利用の例としてmrubyを取り上げ、これをWindowsランタイム・コンポーネントとしていく。以下ではまず、mrubyをコンパイルして、Windowsランタイム・コンポーネントに取り込めるようにするための準備をしよう。
Copyright© Digital Advantage Corp. All Rights Reserved.