« [userChrome.js] Vertical Toolbar | 【つづき】 JavaScript 製 XPCOM で配列構造・列挙構造のデータをメソッドの戻り値にする » |
JavaScript 製 XPCOM で配列構造・列挙構造のデータをメソッドの戻り値にする
JavaScript 製 XPCOM コンポーネントではメソッドの戻り値に JavaScript の配列 (Array オブジェクト) をそのまま使用することができない *1 。その理由は、 XPCOM のインタフェースは各言語固有の仕様に依存しない仕組みであるからだろう。 なんとか配列構造(もしくは列挙構造)のデータを戻り値にする方法はないものかと Firefox のソースなどを探ったところ、以下のような方法があることがわかった。
nsIArray / nsIMutableArray インタフェースで配列構造のデータを受け渡す
nsIArray は、配列構造のデータから個々の要素を取り出すためのインタフェースである。また、 nsIMutableArray は、個々の要素から配列構造のデータを組み立てるためのインタフェースであり、 nsIArray を継承する。
これら2つのインタフェースを利用すると、 JS 製 XPCOM 側のメソッド内にて戻り値を nsIMutableArray 型オブジェクトにしてやり、 XPCOM を利用して戻り値を受け取る側の JS から nsIArray インタフェースを介して配列データおよび個々の要素を取り出すことが可能となる。
まず、IDL には以下のような感じでメソッドを定義する。
nsIArray getFruitsArray();
次に、JS 製 XPCOM で以下のような感じでメソッドを実装する。 nsIMutableArray#appendElement メソッドによって3つの nsISupportsString 型オブジェクトを nsIMutableArray オブジェクトへ追加している。
getFruitsArray: function() { var createStringObject = function(aStr) { var obj = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); obj.data = aStr; return obj; }; var items = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); items.appendElement(createStringObject("apple"), false); items.appendElement(createStringObject("orange"), false); items.appendElement(createStringObject("banana"), false); return items; },
すると、以下のような感じで XPCOM を利用する側の JS から配列を受け取って個々の要素を取り出すことができる。 nsIArray#queryElementAt メソッドは JavaScript の Array オブジェクトで言うところの [i] のように、指定したインデックスの要素を取り出すことができる。ただし、 nsIArray の IDL は未凍結であるためか IDL に記載された仕様と実際の仕様が異なることに注意。
var svc = Cc["********"].getService(Ci.********); var arr = svc.getFruitsArray(); for (var i = 0; i < arr.length; i++) { var elt = arr.queryElementAt(i, Ci.nsISupportsString); alert(elt.data); }
別の取り出し方として、 nsIArray#enumerate メソッドを使い、 nsISimpleEnumerator インタフェースを介して列挙構造として個々の要素を取得することも可能。
var enum = arr.enumerate(); while (enum.hasMoreElements()) { var elt = enum.getNext().QueryInterface(Ci.nsISupportsString); alert(elt.data); }
nsISimpleEnumerator インタフェースで列挙構造のデータを受け渡す
インデックス付きの配列構造ではなく、単純な列挙構造でよければ、 nsISimpleEnumerator インタフェースを介してデータを受け渡しする手もある。ただ、 nsIMutableArray とは違って列挙構造を組み立てるためのインタフェースというのが存在しないらしく、必要となるメソッドを自前で実装しなければならない。
まず、IDL には以下のような感じでメソッドを定義する。
nsISimpleEnumerator getFruitsEnumerator();
次に、JS 製 XPCOM で以下のような感じでメソッドを実装する。
getFruitsEnumerator: function() { var createStringObject = function(aStr) { var obj = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); obj.data = aStr; return obj; }; var items = []; items.push(createStringObject("apple")); items.push(createStringObject("orange")); items.push(createStringObject("banana")); return new ArrayEnumerator(items); },
最後の return 時に使用する ArrayEnumerator クラスは、内部的なデータとして JavaScript の配列オブジェクトを保持し、 nsISimpleEnumerator インタフェースが持つふたつのメソッド (hasMoreElements と getNext) を実装することで列挙も可能なクラスであり、 nsMicrosummaryService.js を参考に別途実装する。
XPCOM を利用する側の JS からの取り出し方は前述の nsIArray#enumerate メソッドを使用した手順とほぼ同じ。
var svc = Cc["********"].getService(Ci.********); var enum = svc.getFruitsEnumerator(); while (enum.hasMoreElements()) { var elt = enum.getNext().QueryInterface(Ci.nsISupportsString); alert(elt.data); }
リンク先エントリのコメントでnasanoさんが例を書いてくださってますが、こういう方法もあります。(以下はnasanoさんが書かれた例)
/* IDL */
void getArray(out unsigned long aCount,
[array, size_is(aCount), retval] out long aArray);
/* JS (実装) */
getArray: function getArray(aCount) {
var array = [10, 20, 30];
aCount.value = array.length;
return array;
},
/* JS (呼び出し) */
var array = component.getArray({});
使用時にはメソッドの引数に配列の個数を渡すためのオブジェクトを必ず渡さないといけないという制限ができますが、配列をそのままreturnできるので、場合によっては楽チンではあります。
XUL/Migemoでも一部メソッドでこれを使ってます。
まちがえた。
nasanoさんじゃなくてnanto_viさんでした。
nsIVariant型を使うという手もあります。FUELのApplication.windowsやApplication.extensions.allなどで使われている手法です。
/* IDL */
#include “nsISupports.idl”
interface nsIVariant;
[scriptable, uuid(…)]
interface myIComponent : nsISupports {
nsIVariant getArray();
};
/* JS (実装) */
getArray: function () {
return [“apple”, “orange”, “banana”];
},
/* JS (呼び出し) */
alert(component.getArray());
// apple,orange,banana
Piroさん、nanto_viさん、ありがとうございます。
> void getArray(out unsigned long aCount, [array, size_is(aCount), retval] out long aArray);
out、size_is、retvalとかの意味がさっぱりわからないもので、僕にはちょっと難しそう…。そこらへんのキーワードの説明一覧とかがあればうれしいのですが。
> nsIVariant getArray();
これは単純ですぐに利用できそうです。
> out、size_is、retvalとかの意味がさっぱりわからないもので、
XPConnect and XPIDL FAQ の4および11あたりが参考になるかと思います。
inがデータを渡す(コンポーネントを使う側から見て)のに対しoutはデータを受け取るための引数、retvalが指定された引数はXPConnect経由だと返り値として扱われる、size_is(aCount)は返された配列の長さはaCountで受け取れるということです。
小なり・大なり記号でくくったらURLが消えてしまいました ^^;
XPConnect and XPIDL FAQ http://www.mozilla.org/scriptable/faq.html です。
nantoさんありがとうございます。
JavaScriptではポインタを引数とすることができないので、代わりにoutキーワードを付けて戻り値として返す、という感じでしょうか。なんとなく理解できました。