22 Feb 2010

XPCOMについてざっと調べたこと

firefoxの拡張機能を作るにあたってXPCOMは避けては通れない技術です. いままではサンプルコードや他のエクステンションのコードを参考にしながら何となくやり過ごしてきたんですが, やはりいまいち理解していないコードが自分の書いたものの中にあるのは気持ち悪いので, XPCOMについて調べました.

XPCOMとは

XPCOM (Cross-platform Component Object Model) は ある機能を提供するクラス(コンポーネント)をプラットフォームに依存せずに呼び出せるようにする技術です. Mozillaで開発されています. ふつうfirefoxで拡張機能をつくるときは, UI部分をXUL, ロジックの部分をjavascriptで書きます. しかし, javascriptから直接ブラウザの履歴にアクセスしたり, ローカルのファイルに触ったりはできません. こんな時にこのXPCOMという技術を使います. 利用したい機能を実装し, それをXPCOMの形式でコンポーネントにすれば, 拡張のjsからXPCOMで提供される機能を使うことができます.

類似の技術(もとになった技術?)にCOM(Component Object Model)があります.

COMはマイクロソフト発の技術です XPCOMとCOMの比較はこちらの記事で説明されています.

インタフェース

XPCOMではコンポーネントのインタフェースが別ファイルで記述されています. ファイル名は"nsI"ではじまり, ".idl"という拡張子がついています. これを読めばそのコンポーネントがどんな機能を提供しているかわかるので, XPCOMを利用する側の拡張開発者にとってはこれが重要なファイルかもしれません.

インタフェースファイルはこんな感じで書かれています.

#include "nsISupports.idl"

[scriptable, uuid(7CB5B7A1-07D7-11d3-BDE2-000064657374)]
interface nsISample : nsISupports
{
    attribute string value;
    void writeValue(in string aPrefix);
    void poke(in string aValue);
};

MXR is retired

見た目がC++ぽくて, 何となくどんな関数があるのかわかりますね. nsISupports は全てのXPCOMインタフェースの親要素です. すべてのインタフェースは nsISupports を継承しています.

XPIDL

インタフェースの見た目はC++みたいなんですが, これはXPIDLというという言語で書かれています.

XPIDL は IDL (Interface Description Language) の1種です. IDLはその名の通りインタフェースを記述するための言語です. プログラム言語に非依存でインタフェースを記述するために考えられたようです.

XPIDLはmozilla発の技術のようですが, IDL自体は一般的な呼称です. OMG IDL というものが有名なようです.

ID

それぞれのコンポーネントにはClassIDとContractIDという識別子がふられています.

ClassID

16d222a6-1dd2-11b2-b693-f38b02c021b2

ContractID

@mozilla.org/categorymanager;1

nsCategoryManager | MDN

上記のように, ClassIDは英数字からなる文字列で, ContractIDはURIです. 拡張のjsからXPCOMをよびだすときはContractIDが必要になります.

XPCOMのコード

インタフェースは上記のようにXPIDLで書かれているんですが, 当然そのインタフェースを実装しているクラスが存在しています. このインタフェースの実装はC++やJavascriptで書くことができます. C++の場合はプラットフォーム別にそれぞれコンパイルしてバイナリを準備する必要があります.

XPCOMの呼び出し方

こんな風にインスタンスを作ります. 前述のidでインタフェースを指定しています.

var aFile = Components.classes["@mozilla.org/file/local;1"]
  .createInstance(Components.interfaces.nsILocalFile);

if (!aFile) return false;

aFile.initWithPath(sourcefile);

createInstance() というメソッドでインスタンスを作っています. 存在しないインタフェースのインスタンスを作ろうとすると, createInstance() は null を返します. インスタンスができれば, あとは普通にメソッドを呼び出すだけです.

createInstance() とは別に, getService() というメソッドを使う方法もあります.

var em = Components.classes["@mozilla.org/extensions/manager;1"]
           .getService(nsIExtensionManager);

getService() の場合はインスタンスではなくて参照を返してくれるそうです. createInstance()との使い分けがよくわからないですが, 一部のコンポーネントはサービスとよばれ, getService() をつかってアクセスする必要があるそうです.

インタフェースの調べ方

MDCにある, はじめから準備されているXPCOMインタフェースの一覧です. (まだ準備中のものが多いようです)

上記のリファレンスはまだ準備中のものが多いようなので, MXRなどで検索して直接idlファイルを読むような場合が出てくると思います.

参考