mozilla

Introduction to XPCOM for the DOM

Fabian Guisset <fguisset@softhome.net>

注意: この文書はまだ DOM の権威の校正を経ておりませんので、正確でない部分があるかもしれません。

この文書は DOM コードのコンテキストで XPCOM を使用する方法の入門書です。文書に記述されています XPCOM や nsCOMPtr の使用法は DOM コードを読んだり、書いたりするために必要な知識の 8 割をカバーしています。XPCOM の上級向けの内容については XPCOM のページをご参照ください。

この文書のバグの追跡は バグ 99592 で可能です。

対象読者: DOM コードに C++ と XPCOM を使用するための手っ取り早い入門書が必要な読者。DOM ハッキングガイド の残りの部分を読むには、この文書の内容を理解している必要があります。

重要な注意事項: この文書は C++ の知識、特にそのオブジェクト指向の部分の理解を前提としています。Stanley Lippman と Josee Lajoie による「C++ Primer」を読んで、私は C++ を学んでから、DOM コードをあれこれ試してみました。この本は、その分野に関わるすべての初心者にお勧めできます。

入門への手引き

DOM は XPCOM を広く使います。事実、 DOM を実装して使用するには XPCOM が必要になります。しかし、コードを読んだり既存のフレームワークに何かするだけならば、詳細まで知る必要はありません。nsCOMPtr も含めて DOM が提供する数多くのマクロや機能はとても便利なものです。この章では、できるだけ枝葉末節の解説を避けて、 XPCOM のうち DOM で最も広く使われている範囲をカバーしたいと思います。そして nsCOMPtr のインターフェイス、参照カウント、 nsISupports インターフェイスを解説します。新しいインターフェイスの追加方法についてのチュートリアルや、最後に C++ におけるクラスの継承についての詳細の議論についても解説します。

インターフェイス

オブジェクト指向のプログラミングはクラス間の継承の使用を基本にしています。さらに、メソッドを宣言しても実装しないメソッドがある場合、クラスは「抽象クラス」として宣言することができます。この概念を最大限に推し進めると、メソッドを実装せずに宣言したクラスは「完全に仮想的」になります。そのようなクラスはインターフェイスと呼ばれます。インターフェイスの目的は、ひとつのインターフェイスに・・・実装の詳細内容を心配することなく、(普通クラスとして表される) オブジェクトを操作する一セットのメソッドを持たせることです。もしあるクラスが、あるインターフェイスを実装しているならば、そのままインターフェイスのメソッドを使うようにしてください。そうすれば、インターフェイスの実装 (具象クラス) に変更があっても、気にする必要がなくなります。XPCOM はこの考えを究極まで推進しています。純粋な仮想メソッドが以下の構文で宣言されます:

virtual nsresult FunctionFoo() = 0;

インターフェイスはこのようにすべてのメンバ関数が純粋に仮想関数として宣言される、単に C++ のクラスです。インターフェイスはまた XPIDL を使って定義されます。この件は、 1.E の節「新しいインターフェイスの追加方法」で解説されます。

インターフェイスは実装を備えていないので、非抽象クラス (具象クラス) によって実装される必要があります。その際、インターフェイスが nsIFoo 、クラスが nsFoo である場合、「nsFoo は nsIFoo を実装する」あるいは「nsFoo は nsIFoo を継承する」と言います。C++ クラスのコードは次のようになります:

class nsFoo : public nsIFoo

オブジェクト指向の C++ を知らない人のために言うと、nsFoo は nsIFoo からの「派生」として宣言されることになります。そしてこの時、nsIFoo は nsFoo の「基底クラス」になります。この入門書では、オブジェクト指向の C++ についてもっと多くを知る必要はありません。しかし、1.F 節ではその件でさらに詳しい議論があり、DOM コードのほとんどを理解することが必要になります。

(オブジェクトと呼ばれる) クラスのインスタンスは (ヒープや空き領域に)、下記の構文を使って動的に割り付けられます。

nsFoo *fooptr = new nsFoo;

その時オブジェクトは、 fooptr 経由でのみ操作されます。この時、nsFoo クラスが nsIFoo インターフェイスを実装するならどうなるかを考えてみましょう。nsFoo オブジェクトを fooptr 経由で直接操作可能ですね。しかし、コードはあまり頑健ではありません。事実、使われているメソッドの名前やシグニチャーを変更する者がいると、全コードに渡りこれらのメソッドへの呼び出しをすべて変えなければなりません。具象クラスとは反対に、インターフェイスは時が経過しても安定しているはずです。事実、Mozilla コードの多くのインターフェイスは凍結されて (インターフェイス定義の最初に @Frozen コメントで指示されて) います。これは、それらのインターフェイスがこの先変わることがないという意味です。メリットは、こうして作られたコードが将来永遠に (最高の条件で) 存在し続けることです。反対に欠点は、そのようなインタフェースを改善する方法を見つけなければならないということと、それらを凍結すると実装者に新しいインタフェースを作らせるのを強いることになってしまう、ということです。要するに、必要な時はいつでも実装ではなくインターフェイスを操作することです。nsFoo によって実装される nsIFoo インターフェイスへのポインタは以下のように宣言されます。

nsIFoo *fooptr = new nsFoo;

fooptr のようなポインタはその時、「nsFoo クラスのインスタンスによって実装される nsIFoo インターフェイスへのポインタ」とか、それを短縮して「nsIFoo へのポインタ」と呼ばれます。今後私が「インターフェイスへのポインタ」について言う時は、「C++ 具象クラスのインターフェイスによって実装されたインターフェイスへのポインタ」を事実上意味します。注意すべき重要点は、nsIFoo へのポインタのみが nsIFoo インターフェイスやその親で定義されたメソッドを呼び出せることです。たとえば、nsFoo が nsIFoo と nsIBa というふたつの異なるインターフェイスを実装するなら、nsIFoo へのポインタは nsIBa で定義されたメソッドを呼び出せません。その逆もありません。

注意: 前の段落は大変重要です。もしそれが完全に理解できていないならば、これ以上読み進める必要はありません。

参照カウントの基本

XPCOM は参照カウントのメカニズム(短縮形 refcount )を使って、インターフェイスのポインタがオブジェクトを指している間にオブジェクトが削除されていないことを確認します。事実、削除されたオブジェクトを指し示すポインタを間接参照することは悪い影響を及ぼす可能性があります。そんな訳で、インターフェイスへのポインタがオブジェクトのアドレスを代入される都度、そのオブジェクトのカウントを一つ増やす必要があります。これを実行する関数は、”AddRef”と呼ばれ、nsISupports インターフェイスで定義されます。ポインタがすでにオブジェクトのアドレスを保持しない時、そのオブジェクトの参照カウントを一つ減じる必要があります。これには同じく、nsISupports インターフェイスで定義された”Release”関数を使用します。オブジェクトの参照カウントが 0(zero) に達するとオブジェクトは自分自身を削除します。こんなわけで、各々のオブジェクトの参照カウントの記録を確保することは大変重要になります。最初のケースでは、もし AddRef を使ってオブジェクトのカウントを増やすのを忘れると、私たちがポインタを使用して何かする前にオブジェクトは自分自身を削除するかもしれません。そのために、間接参照される時に、クラッシュを引き起こすことがあります。次のケースでは、もしオブジェクトを Release 関数を使って開放することを忘れると、オブジェクトは自分自身を削除しません。それで「メモリリーク」を発生させ、つまり、不要になってもオブジェクトを確保しているので、メモリがシステムに返されることはありません。どちらのケースも不都合なので、参照カウントには細心の注意を払わなくてはなりません。

幸運なことに、とっても便利な nsCOMPtr があります。

nsCOMPtr

Scott Collins は私たちに nsCOMPtr を授けてくれたので、すぐにそれを使用しましょう。 nsCOMPtr は C++ auto_ptr の拡張機能で、参照カウントの操作を管理します。そして、比較、初期化などに対していくつかの機能を提供します。nsCOMPtr は大抵の場合インターフェイスへの通常のポインタのように使われます。nsIFoo インターフェイスへのポインタは、通常下記のように宣言されます:

nsIFoo *fooptr;

同じ nsIFoo インターフェイスへの nsCOMPtr は下記のように宣言されます:

nsCOMPtr<nsIFoo> fooptr;

nsCOMPtr は次の節で解説されます。nsCOMPtr についての詳細情報は ユーザの手引き を読んでください。

QueryInterface()

再び、nsIFoo と nsIFoo2 の 2 つのインターフェイスを実装する nsFoo クラスを考えてください:

class nsFoo : public nsIFoo, public nsIFoo2

ともかく nsFoo のインスタンスが作られることを前提としましょう (この断定はほとんど常に正しいです)。そうすると、nsIFoo インターフェイスで定義されたメソッドを使ってそのオブジェクトを操作したくなるでしょう。目的は nsIFoo インターフェイスへポインタを確保することです。そのために、主な 2 つのテクニックが存在し、コンテキストによってするべきことが分かります。最初のテクニックは「Getter」を使うことであり、次のテクニックは「this」ポインタで static なキャストを使うことです。

getter は、グローバルな領域やクラスの領域で定義され、要求されたインターフェイスへのポインタを「返す」関数です。一般的に getter は以下のように機能します:最初にインターフェイスへのポインタである ifooptr を代入なしに宣言します。それから、getter 関数へポインタのアドレスを渡します。この時 getter 関数は、ポインタへ正確なアドレスを代入し、要求されたインターフェイスへのポインタへ QueryInterface します。ifooptr はすでに、実際のオブジェクトのアドレスを代入されたインターフェイスへのポインタとなっています。このようにして、ifooptr 経由で nsIFoo で定義されたメソッドをすぐに呼び出せます。以下にコードの例を示します。

nsCOMPtr<nsIFoo> ifooptr;
GetInterfaceIFoo(getter_AddRefs(ifooptr));
ifooptr->FunctionOfnsIFoo();

独特の構文である getter_AddRefs(pointer) は、通常の「&」(address-of) C++ 演算子に対して nsCOMPtr が対応するものと同じです。すなわち、Getter メソッドは AddRef メソッドを呼び出します。これは、「このオブジェクトに参照を追加しません。もうすでにあなたが追加していますからね」という呼び出し元と「このオブジェクトへは私が参照を追加しますので、追加しないでください」という呼び出し先との間の契約になります。もし、呼び出し元と呼び出し先が両方で AddRef を実行すると、どちらかの参照は解放されないので、オブジェクトはメモリリークとなります。

すべての Getter 関数は返されたポインタを AddRef しなければならないことに注意してください。それでも今 Getter 関数 を使用しているとしても、心配することはありません。XPCOM 所有の手引き で詳しい情報を参照できます。

フレームやビューのように、インターフェイスには参照カウントされないものがあることにも注意してください。これらに対しては生のインターフェイスポインタを使用しなければなりません。マニュアルで参照カウントする必要はありません。

今 nsIFoo2 インターフェイスで宣言され、nsFoo で実装された関数を実行したいとしましょう。しかしこの時、ifooptr 経由ではその関数にアクセスできません。それは 1.B の節で述べた理由のために ifooptr が nsIFoo へのポインタだからです。XPCOM には便利なメソッドがあり、別のインターフェイスへのポインタがある時、インターフェイスへのポインタを持てます。そして、2 つのインターフェイスは同じオブジェクトによって実装されます。この時のメソッドが QueryInterface() です。それは nsISupports インターフェイスで定義されます。Mozilla の各々のインターフェイスは nsISupports を継承しています。このことは、XPCOM の主要規則の 1 つです。目的はこうです。オブジェクト (クラスのインスタンス) が所定のインターフェイスを実装するかどうかを知る。これが QueryInterface() の働きです。インターフェイスとインターフェイスを保持するポインタのアドレスを QueryInterface() に渡します。その時インターフェイスがオブジェクトによって実装されていれば、渡されたポインタはオブジェクトのアドレスを代入されます。もし実装されていなければ、 QueryInterface() は NS_NOINTERFACE を返し、渡されたポインタは null になります。

QueryInterface() はユーザへオブジェクトの実装を隠蔽するのに便利です。 QueryInterface() をただ呼び出してから、インターフェイスのメソッドを呼び出します。その外は知る必要がありません。それでは、nsIFoo へのポインタがある時、どのように QueryInterface() を使って nsIFoo2 へのポインタを得るのでしょうか? ifooptr を再利用できない以上、nsIFoo2 への新ポインタである ifooptr2 を作ります。以下の構文が (nsCOMPtr とだけ使う場合) 推奨されます:

nsCOMPtr<nsIFoo2> ifooptr2 (do_QueryInterface(ifooptr));

この構文は nsCOMPtr の宣言と初期化を同時にするのに推奨されます。あとで、別のアドレスをそこへ代入する必要があるならば、簡単にこうできます:

ifooptr2 = do_QueryInterface(another_pointer);

しかしこの構文は、単に実際の関数への便利なショートカットにすぎません。以下の構文は生のポインタで QueryInterface() を使用する方法を表します。

nsIFoo2 *ifooptr2;
ifooptr->QueryInterface(NS_GET_IID(nsIFoo2), (void **)&ifooptr2);

NS_GET_IID は引数の IID に対して評価をするマクロです。2 つのインターフェイスを比較して同一性を調べるのに便利です。nsCOMPtr への getter_AddRefs() の働きについてはすでに見たとおりです。今回は、ifooptr2 のアドレスを渡します。nsFoo は nsIFoo2 を実装するので、ifooptr2 は nsFoo の現在のインスタンスのアドレスを代入されます。すると、ifooptr2 経由で nsIFoo2 で定義されたメソッドを呼び出せます:

ifooptr2->FunctionOfnsIFoo2();

ところで、 nsFoo で実装されないインターフェイスへ QueryInterface を試みるならば、渡されたポインタは null となります。そんな訳で、自分の処理を本当に確実に明白に間違いないと思われないなら、nsCOMPtr が null になっていないかどうか必ず確認してください。以下のサンプルを参考にしてください。

nsCOMPtr<nsINotImplemented> iptr(do_QueryInterface(ifooptr));
if(!iptr) {
  // nsFoo は nsINotImplemented を実装しない。 iptr はそのため null である
  return NS_OK;
}

QueryInterface が null-safe であることに注意することも大切です。たとえば前の例では、ifooptr がnull であっても、不都合は発生しません。さらに、QueryInterface の呼び出しの戻り値は正当な理由がない限りは返されるべきではありません。NS_NOINTERFACE を返すことが心配ならば、前のサンプルが示すように NS_OK を返してください。

この節の始めに、インターフェイスへのポインタを取得する 2 つ目の方法について話しました。これは、getter 関数が有効でない場合に使われるはずです。おそらくご存知でしょうが、オブジェクトの”this”メンバはそのオブジェクトへのポインタです。そこで必要なインターフェイスへ static に”this”を単にキャストできます。しかしキャストする前にご自分の処理が絶対間違いようにしてください。間違いがあれば参照カウントで問題が発生します。

最近遭遇した問題の例を紹介します: nsHTMLAnchorElement クラスのメンバ関数で、nsHTMLAnchorElement オブジェクトによって実装された nsIContent インターフェイスへのポインタを取得する必要がありました。しかし、getter 関数がなかったので 2 つ目の方法を使わなければなりませんでした:

nsCOMPtr<nsIContent> content = getter_AddRefs(NS_STATIC_CAST(nsIContent*, this));
// または自分自身をカウントしたいならば、
nsIContent *content = NS_STATIC_CAST(nsIContent*, this);

この 2 つめの構文は注意して扱われる必要があり、XPCOM プログラマーの上級者だけに推奨されます。

ここまでに解説しました XPCOM と nsCOMPtr の使用方法はコードを読んだり少しでも書くのに必要な知識の 8 割をカバーしています。引き続き do_GetService へ進んで、QueryInterface などの実装の説明は可能ですが、そこまでする必要があると思いません。そこで XPCOM のさらに詳しい内容については XPCOM プロジェクトのページ を参照してください。

次の節は Mozilla DOM へ新しいインターフェイスを追加する方法に関するチュートリアルであり、ビルドの解説やその他を含みます。最後の節はオブジェクト指向の C++ 、インターフェイスの継承、その他面白いトピックに関する詳細の内容についての議論になっています。

チュートリアル: 新しいインターフェイスの追加方法

始めに

このチュートリアルの目的は、DOM に新しいインターフェイスを追加しそれを実装する方法を解説することです。チュートリアルを理解するために前節をよく理解してください。新しいインターフェイスを追加したくなるのにはいくつかの理由があります。新しい DOM のオブジェクトを追加したり、最終的な「インターフェイスの凍結”を順守するためです。最初に XPIDL を調べて、それがどのようにインターフェイスの定義を支援できるか見てみましょう。次にビルドシステム、makefiles、などを解説します。最後に、 nsIDOMFabian インターフェイス (Fabian は私の名前なのですが ;-) を通じてこれらの新しいインターフェイスの実装を観てみましょう。

XPIDL

XPIDL は Cross-Platform Interface Definition Language の頭文字です。 C++ でインターフェイスを直接コーディングせずに、 XPIDL を使うことが可能です。それを使用するとインターフェイスの定義のタスクを簡略化し、文書の自動生成や XPT の生成などの面白い機能をいくつか提供します。最初にするべきことは、私たちのインターフェイスである nsIDOMFabian が何をするか決めます。この文書のために、私は nsIDOMFabian という新しい HTML インターフェイスを実装することにしました。そのインターフェイスは nsHTMLDocument クラスによって実装されます。

XPIDL の構文は簡単です:


 #include "domstubs.idl";
 
 [scriptable, uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)]
 interface nsIDOMFabian : nsISupports
 {
   void fabian();
   readonly attribute boolean neat;
 };
 

これが nsIDOMFabian インターフェイスの定義です。インターフェイスの uuid は一意の識別子であり、すべてのインターフェイスに 1 つ必要です。ウインドウで guidgen を使うか、また irc.mozilla.org の #mozilla で「mozbot uuid」コマンドを発行して uuid を生成できます。

コンパイル時に、XPIDL コンパイラはこのインターフェイスの定義を実際の C++ コードに変換しますが、これは純粋な抽象クラスを備えたヘッダファイルです。このクラスは以下のようになっています:


 #define NS_IDOMFABIAN_IID \
   {0xxxxxxxxx, 0xxxxx, 0xxxxx, \
     { 0xxx, 0xxx, 0xxx, 0xxx, 0xxx, 0xxx, 0xxx, 0xxx }}
 
 class nsIDOMFabian : public nsISupports {
  public:
 
   NS_IMETHOD Fabian(void) = 0;
   NS_IMETHOD GetNeat(PRBool *aNeat) = 0;
 
 };
 
 #define NS_DECL_NSIDOMFABIAN
 #define NS_FORWARD_NSIDOMFABIAN(_to)
 #define NS_FORWARD_SAFE_NSIDOMFABIAN(_to)
 

見て分かるとおり、自動生成のヘッダは私たちのインターフェイスの IID を備えており、「純粋な抽象クラス」は正しく定義されます。XPIDL コンパイラは IDL メソッドと属性を以下の規則に従って C++ の関数へ変換します:

インターフェイスのメソッドは C++ でも同じ名前を保持します。しかし IDL ではいわゆる「interCaps」モデルを使用しなければなりません。つまり最初の文字は小文字になり、そのあとの新しい単語の最初の文字は大文字になります。たとえば、IDL では getElementById と書くので、C++ では GetElementById に翻訳されることになります。

NS_IMETHOD は基本的に「virtual nsresult」を意味するマクロです。ここでは解説していませんが、規則に従って引数のリストと戻り値の型は正しい C++ の型へ変換されます。

インターフェイスの属性は 2 つの関数になります: getter と setter です。私たちの例では、属性は read-only で宣言されるので、getter だけが定義されます: GetNeat です。引数は自動的に XPIDL によって生成される PRBool 型のオブジェクトへのポインタです。賢いですね。同じ interCaps のモデルが同様に IDL の属性へ適用されることに注意してください。

1.E.d で 3 つのマクロが詳細に解説されます。次のステップは新しいインターフェイスをビルドすることです。


ビルドシステム

ここはとても簡単なところです: すでにあるものをコピーするだけですから。最初にどのディレクトリへインターフェイスを置くか決めなければなりません。最も論理的選択は、すべての HTML インターフェイスが存在する dom/public/idl/html です。次に、このディレクトリのすべての makefiles へ nsIDOMFabian.idl を追加しなければなりません。これには必要であれば、「MANIFEST」「makefile.win」「Makefile.in」などが含まれます。単に nsIDOMFabian の既存のエントリをコピーしてください。注意: Makefiles では、TABS と ホワイトスペースが混在します。他のエントリはかならず正確にコピーしてください。

次にインターフェイスをビルドするために dom/ へ「make」とタイプしてください。すべてうまく行けば、 nsIDOMFabian.h ファイルが dom/public/idl/html/_xpidlgen/ にあり、インターフェイスに対して C++ コードを保持しているはずです。私の経験ではインターフェイスを機能させる前に、残念ながら「distclean」をビルドしなければならないことがあります。

インターフェイスの実装

インターフェイスの実装方法をさらに詳細に観てみましょう。 C++ の具象クラスによってインターフェイスが実装されなければならないことをすでに知っています。このクラスは複数のインターフェイスを直接または継承によって実装できます (継承の議論は次の節を参照してください)。また私たちは XPIDL で定義されたインターフェイスがメソッドと属性を保持し、これらは XPIDL コンパイラによって C++ の関数に変換されることも知りました。インターフェイスを実装するクラスは明示的に、インターフェイスで定義された各メソッドを実装し、各属性の setter と getter を実装しなければなりません。もし属性が read-only であればもちろん getter だけが必要になります。

nsHTMLDocument.h で定義されている nsHTMLDocument クラスに nsIDOMFabian を実装することにしました。するべきことが 3 つあります: クラスの宣言を変更すること、クラスの本体を変更すること、インターフェイスで宣言された関数をコード化することです。

クラス宣言とクラス本体の変更:


 #include "nsIDOMFabian.h"
 class nsHTMLDocument: public ... ,
                       public nsIDOMFabian
 {
   // ...
   NS_DECL_NSIDOMFABIAN
   // ...
 };
 

最初に nsIDOMFabian を継承するように、nsHTMLDocument クラスを宣言する必要があります。それからクラスの public なインターフェイスで、マクロの NS_DECL_NSIDOMFABIAN を使ってインターフェイスの実装に必要なメソッドを宣言します。このマクロは XPIDL コンパイラによって自動生成されることを憶えてください。 それは nsIDOMFabian インターフェイスのためのクラスで実装されるメソッドをすべて宣言します。典型的な NS_DECL_NSIFOO マクロは以下のようになっています:


 #define NS_DECL_NSIFOO \
   NS_IMETHOD GetBar();
 

このマクロはクラスの定義で使われる必要があり、nsFoo クラスは nsFoo::GetBar() メソッドを備えることになります。関数が宣言されたので、最後にコード化が可能になります。

関数を実装する様々な可能性があります。最初の方法は最も簡単です。実装しなければならない関数は nsHTMLDocument::Fabian() と nsHTMLDocument::GetNeat() です。それでは、コード化してみましょう。

NS_IMETHODIMP nsHTMLDocument::Fabian(void)
{
  printf("Hello from the nsIDOMFabian interface\n");
  // 任意のコード...
  return NS_OK;
}

NS_IMETHODIMP nsHTMLDocument::GetNeat(PRBool *aNeat)
{
  if(!aNeat) {
    return NS_ERROR_NULL_POINTER;
  }

  nsresult rv = Fabian();
  if( rv == NS_OK ) {
    *aNeat = PR_TRUE;
  } else {
    *aNeat = PR_FALSE;
  }
  return NS_OK;
}

このコードはもちろん nsHTMLDocument.cpp で書かれます。関数はとても簡単であり、ただ概念を説明しているだけです。2 番めの可能性は「インターフェイス転送マクロ」を使うことです。このマクロはまた XPIDL コンパイラによって自動生成されます。以下はインターフェイス転送の背景にある理論で、そのあとに来るのは nsIDOMFabian のサンプルです。

理論

実際のクラス nsBar を継承する実際のクラス nsFoo があると仮定しましょう。また nsFoo が nsIFoo インターフェイスを実装するとも仮定しましょう。nsFoo が nsIFoo を実装する可能性の 1 つは、 nsIFoo インターフェイスのメソッドを、クラス nsBar のこれらの同じメソッドの実装へ転送することです。

// XPIDLでの nsIFoo インターフェイス (最低限の実装)
interface nsIFoo {
    attribute type prop;
    void meth();
};

class nsBar {
    NS_IMETHOD GetProp();
    NS_IMETHOD SetProp();
    NS_IMETHOD Meth();
};

class nsFoo : public nsIFoo,
              public nsBar {
// nsFoo クラスの定義
};

nsFoo::GetProp() { return nsBar::GetProp(); }
nsFoo::SetProp() { return nsBar::SetProp(); }
nsFoo::Meth() { return nsBar::Meth(); }

このようなコードが機能するのに nsBar はもちろん GetProp、 SetProp、 Meth を実装しなければなりません。注意が必要なのは、nsBar は nsIFoo インターフェイスのメソッドを実装しますが、nsIFoo インターフェイスを継承しません。 そしてこの場合だけインターフェイス転送を使用できます。

この時 3 つのメソッドをタイプして nsBar へ送らなくても、代わりに「インターフェイス転送マクロ」の NS_FORWARD_NSIFOO を使用できます。


 #define NS_FORWARD_NSIFOO(_to)                       \
   NS_IMETHOD GetProp() { return _to GetProp(); }  \
   NS_IMETHOD SetProp() { return _to SetProp(); }  \
   NS_IMETHOD Meth() { return _to Meth(); }
 

このマクロの意味を理解するのは簡単です。 _to クラスの実装に対して nsIFoo インターフェイスのすべてのメソッドを転送します。

nsIDOMFabian のアプリケーション: nsDocument で 2 つの関数をコード化し nsHTMLDocument から nsDocument へ nsIDOMFabian を転送できます。こうすることで例えば、 nsXMLDocument で nsDocument のコードを再利用することもできます。この技術はすでに大抵のドキュメントメソッドで使われています。

// File nsDocument.h:
class nsDocument : public ...
{
  // ...
  NS_IMETHOD Fabian(void);
  NS_IMETHOD GetNeat(PRBool *aNeat);
  // ...
}

// File nsDocument.cpp:
nsDocument::Fabian()
{
  // ...
}
nsDocument::GetNeat(PRBool *aNeat)
{
  // ...
}

// File nsHTMLDocument.h:
class nsHTMLDocument : public ... ,
                       public nsIDOMFabian
{
  // ...
  NS_FORWARD_NSIDOMFABIAN(nsDocument::)
  // ...
}

// Nothing needed in nsHTMLDocument.cpp

これは「インターフェイス転送」の簡単な例です。これらの2つの方法は DOM でインターフェイスを実装する最も一般的方法です。もう少し複雑な他の方法がありますが、ここでは扱いません

重要な注記: どの DOM クラスによって実装される nsISupports インターフェイスも、転送マクロや宣言マクロを使って実装されません。nsISupports の実装には特別なマクロが提供されます

新しいインターフェイスの追加方法についてのチュートリアルはここで終わりです。ただ全面的に rebuild してください。distclean でビルドするといいでしょう。しかし、JavaScript からメソッドを有効にすることはできません。というのは、nsIDOMFabian は Class Info. にないからです。その追加方法に関しては Class Info の使い方 を参照してください。

インターフェイスの継承 (上級者向け)

Mozilla における継承モデルは当然 C++ クラスの継承モデルと同じです。オブジェクト指向のプログラミングに詳しい方であれば、この議論の理解には問題はないでしょう。

生のインターフェイスの継承

最初の概念を理解するのは難しくありません。それは、「インターフェイスの継承」です。XPIDL や header にインターフェイスの定義があれば、そのインターフェイスは必ず他のインターフェイスを継承しています。たとえば、 nsIDOMHTMLAnchorElement インターフェイスにとって以下の「連鎖」が備わっています:
nsISupports -> nsIDOMNode -> nsIDOMElement -> nsIDOMHTMLElement -> nsIDOMHTMLAnchorElement

すなわち、クラスが連鎖にあるインターフェイスの 1 つを実装すると、そのクラスは実装されたインターフェイスのすべての祖先を実装しなければならないということです。たとえば、実際のクラスが、nsIDOMElement を実装するなら、そのクラスは nsIDOMNode と nsISupports をも実装しなければならないのです。

理論

インターフェイスの継承がどういうことであるかを理解できたので、もっと一般的なケースを調べてみましょう。最初にごく理論的に考えて、次に nsHTMLAnchorElement のサンプルを使って議論を解説します。

実際のクラス nsFoo によって実装されている DOM オブジェクト Foo があると仮定しましょう。nsIFoo1、nsIFoo2、nsIFoo3 の 3 つのインターフェイス に加えて別の実際のクラス nsBar もあるとしましょう。この状況は以下のようになります:

nsBar <- nsIFoo1
  |
  V
nsFoo <- nsIFoo2 <- nsIFoo3

この状況で上記に記されているように nsIFoo2 インターフェイスは nsIFoo3 インターフェイスを継承します。 nsFoo は nsIFoo2 を実装しその結果 nsIFoo3 も実装します。nsBar は nsIFoo1 を実装します。実際のクラス nsFoo は別の実際のクラス nsBar を継承します。継承を説明する規則は以下のようになっています:

  • nsFoo は自動的に nsIFoo1 を実装します。というのは、すでに nsIFoo1 を実装しているクラスを継承しているからです。
  • nsBar は nsIFoo1 を実装しなければなりません
  • nsFoo は、nsIFoo2 と nsIFoo3 が nsBar へ転送されない限りは、nsIFoo2 と nsIFoo3 を実装しなければなりません。インターフェイスの転送については 1.E.d の節で解説されています。

これらの規則はとても簡単であり DOM のコードで広く使われています。クラスやインターフェイスが増えるにつれてコードはより複雑になりますが、上記の方法を使えば必ず問題を低減することができます。

サンプル

簡単なサンプルである HTML Anchor Element を見てみましょう。 最初に、インターフェイスの継承規則を解説しましょう。nsIDOMHTMLAnchorElement(HTML Anchor Element のために W3C によって定義されたメソッドやプロパティを備えているが)を見れば、それが別のインターフェイスである nsIDOMHTMLElement を継承してるのが分かります:

interface nsIDOMHTMLAnchorElement : nsIDOMHTMLElement

すなわち、nsIDOMHTMLAnchorElement インターフェイスを実装するクラスはすべて nsIDOMHTMLElement インターフェイスも実装しなければならないということです。 nsIDOMHTMLElement を見ると、それが nsIDOMElement を継承しており、後者は nsIDOMNode を継承しており、さらに nsIDOMNode が、nsISupports を継承しているのが分かります。 nsIDOMHTMLAnchorElement は継承のために今述べたすべてのインターフェイスを実装しなければなりません。インターフェイスの実装方法は 1.E.d.の段落を参照してください。

インターフェイスの継承によってトップレベルのインターフェイスが nsISupports であることが分かります。すべてのインターフェイスは直接的にまた間接的に nsISupports を継承しなければなりません。このインターフェイスは 1.B. の節で説明された AddRef()、 Release()、QueryInterface() という3 つのメソッドを定義します。 nsISupports は xpcom/base/ で 1999 年以降変更されることなく平和に息づいています。 XPCOM インターフェイスと nsISupports については、モジュール化の方法 をご参照ください。

インターフェイスが継承された時の実装を解説するのに、HTML Anchor Element を実装している実際のクラスを調べてみましょう。調べるのは nsHTMLAnchorElement です。実際のクラスの継承の連鎖はこのようになっています:

nsGenericElement -> nsGenericHTMLElement -> nsGenericHTMLContainerElement -> nsHTMLAnchorElement

クラスの定義は以下のようになっています:

class nsHTMLAnchorElement : public nsGenericHTMLContainerElement
class nsGenericHTMLContainerElement : public nsGnericHTMLElement
class nsGenericHTMLElement : public nsGenericElement
クラスの定義を見ると、nsGenericHTMLElement と nsGenericHTMLContainerElement は直接インターフェイスを実装していません。しかし、nsGenericElement は実装しています:
class nsGenericElement : public nsIHTMLContent

これはもちろん nsGenericElement が nsIHTMLContent インターフェイスを実装していることになります。 nsIHTMLContent への継承の連鎖は以下のようになります:

nsISupports -> nsIContent -> nsIStyledContent -> nsIXMLContent -> nsIHTMLContent

nsGenericElement は上記のすべてのインターフェイスを実装しなくてはなりません。そして、nsGenericElement を継承するすべての実際のクラスは自動的にこれらのインターフェイスを実装します。 このことはすでにこの段落で定義した規則と一致しています。

関連資料

ドキュメントのタグと貢献者

Contributors to this page: kohei.yoshino
最終更新者: kohei.yoshino,