XPCOM: 第2回: XPCOMコンポーネントの基本

XPCOMプログラミング入門

このシリーズの前回の記事では、XPCOMテクノロジーを概観しました。今回は、タイプ・ライブラリー、xpidlコンパイラー、およびインターフェースの選択について探ります。

Rick Parrish (rfmobile@swbell.net), Independent consultant

Rick Parrish氏は、高校生の頃からコンピューター・サイエンスに興味を持ち始めましたが、エレクトロニクスにはもっと早くから関心を持っていました。最初は電気工学の教育を受けていましたが、ソフトウェアの場合は、ハードウェアとは違って、塩化第二鉄溶液の嫌な臭いをかぐ必要はなく、ただ設計変更のためだけに指にやけどをする危険を冒す必要もないことに気付きました。Rickは、長い間C/C++ のプログラミングに携わってきましたが、VBとDelphi(Pascal) による仕事も多く、アセンブリー言語の仕事も少なくありません。今でも、熱いハンダを必要とする1つか2つのプロジェクトに何とか携わっています。Windows 2000は素晴らしいけれど、Ogg Vorbisもクールだし、IPTableのあるLinux 2.4カーネルは本当にタフだ! というのが彼の現在の持論です。現在の関心事は、ソフトウェア・モデリング設計用のツールを開発するオープン・ソース・プロジェクトを始めることです。Rickの連絡先は、 rfmobile@swbell.net です。



2001年 3月 01日

テクノロジーとしてのXPCOMの背後には、プラットフォームや言語に依存しないモジュラー・フレームワークを提供しようという発想があります。C++ で作成されたコンポーネントが実行できる機能で、JavaScriptや他のスクリプト記述言語で実行できないものはほどんどありません。このような離れ業を実現するためにXPCOMで使われているメカニズムは、タイプ・ライブラリーといいます。

タイプ・ライブラリー

タイプ・ライブラリーには、メソッド、属性、パラメーター、およびコンポーネントのインターフェースを記述するための、共通データ・フォーマットまたは交換メカニズムが備えられています。共通フォーマットを設定すると、複数のプラットフォームや複数のプログラム言語に対して、同じインターフェースを記述できます。この機能は、一般的なマーシャリングやプロキシー・メカニズムをサポートする場合に便利です。プログラムは、このようなタイプ情報を使うことにより、どのインターフェースであっても、関係するメソッドや属性についての各パラメーターを決定できます。このことが分かれば、そのインターフェースと他の環境との間で相互にデータをやり取りすることも可能です。他の環境というのは、スレッド、プロセス、またはネットワークの境界を超越した、スクリプト記述エンジンやプロキシー・メカニズムを指します。スクリプト記述エンジンのケースでは、このことは、スクリプト記述コードがコンポーネントのインターフェースにメソッドを呼び出せるように、スクリプト記述環境でいかにコンポーネントを定義するかに関係します。

XPConnectは、XPCOMの最上部に構築される付加的なレイヤーであり、XPCOMタイプ・ライブラリー・ファイルを読み取り、XPCOMインターフェースを整理してJavaScriptエンジンで使えるようにします。さらにXPConnectでは、XPCOMコンポーネントをすべてJavaScriptで作成し、C++ コードで、JSコンポーネントを呼び出したり、あるいはコンパイルした C++ コンポーネントをJSを使ってロードして扱うことができます。またJavaScriptだけではなく、XPConnectと同様のメカニズムを利用した別のスクリプト記述言語として、Python言語が加わっています。


インターフェース記述

言語に依存せずにインターフェースを指定する方法は、IDLすなわちインターフェース記述言語を使うことです。インターフェース記述からタイプ・ライブラリー・ファイルを作成するツールは、IDLコンパイラーです。XPCOMで使われているIDL方言は、OMG CORBAやMicrosoft IDLで使われているIDL方言とはわずかに異なるため、IDLコンパイラーも別のもの (xpidlコンパイラー) が使われます。xpidlコンパイラーの興味深い機能は、インターフェース定義からC++ コード・スタブを生成するオプションです。この機能は、新しいプロジェクトを始める際に、ほとんどすべての宣言の部分のC++ コードを作成します。ちょうど、コーディングを始めるときに助けてくれるコーディング・ウィザードのようなものです。CORBAおよびMicrosoft IDLコンパイラーにも同様の機能はあります。シェル・プロンプトからxpidlを実行するときの概要は、次のようになります。

リスト1. シェル・プロンプトでのxpidl
Usage: xpidl [-m mode] [-w] [-v] [-I path] [-o basename] filename.idl
-w turn on warnings (recommended)
-v verbose mode (NYI)
-I add entry to start of include path for ``#include "nsIThing.idl"''
-o use basename (e.g. ``/tmp/nsIThing'') for output
-m specify output mode:
header        Generate C++ header            (.h)
typelib       Generate XPConnect typelib     (.xpt)
doc           Generate HTML documentation    (.html)

C++ コードを生成することはおまけのようなもので、IDLコンパイラーの本来の目的は、各モジュールのタイプ・ライブラリー・ファイルを作成することです。C++ コードの方は、IDLによって生成され、C++クラスの仮想メソッドとしてインタフェースを記述するC++ ヘッダー・ファイルを利用することができます。(C++ ヘッダー・ファイルの形式での) このインターフェース記述は、コンパイル時に使われるため、事前バインディング と呼ばれます。あるインスタンスが、事前に見たことのないコンポーネントをあるコードで使用する場合、ヘッダー・ファイルが使用できなくても、タイプ・ライブラリーが同じ機能を提供します。そのコードは、後からこのインターフェースについて知ることができます。このことを、遅延バインディング といいます。

インターフェースを指定するためのXPIDL構文は次のとおりです。interface キーワードに続けて、インターフェース名、コロン、基本インターフェースの名前 (通常はnsISupports)、左中括弧、それぞれがセミコロンで終了する属性とメソッドのリスト、右中括弧、およびセミコロン。属性は、attribute キーワードで宣言されます。メソッドに対するパラメーターは、それぞれの先頭にin またはout キーワードを付けることで、入力または出力パラメーターとして宣言できます。リスト2は、コンピューター画面を記述するときに使うサンプル・インターフェースです。

リスト2. サンプル・インターフェース
#include "nsISupports.idl"
[scriptable, uuid(f728830e-1dd1-11b2-9598-fb9f414f2465)]
interface nsIScreen  : nsISupports
{
void GetRect(out long left, out long top, out long width, out long height);
void GetAvailRect(out long left, out long top, out long width, out long height);
readonly attribute long pixelDepth;
readonly attribute long colorDepth;
};

上のインターフェース記述を詳しく見てみると、インターフェースの名前はnsIScreen であることが分かります。また、2つのメソッド (GetRectGetAvailRect) があり、2つの属性 (pixelDepthcolorDepth) があります。interfaceキーワードのすぐ前には、バインドされた文節が大括弧で囲まれています。この文節は、インターフェース記述の任意指定部分ですが、いくつかの有用なメタデータが示されています。scriptable キーワードは、このインターフェースを、JavaScriptや他のスクリプト記述言語でマーシャリングする候補としてタグ付けします。uuid キーワードは、インターフェースのUUIDすなわちインターフェースIDを指定します。nsIScreen の基本インターフェースは、nsISupports (コロンのすぐ後に示される) です。これは、nsISupports で記述されるメソッドおよび属性が、nsIScreen にも見られることを示しています (詳細は後述)。属性については、attribute キーワードによって、メソッドと区別されます。このケースでは、どちらの属性も調べることはできますが、設定することはできません。このことはreadonly キーワードでわかります。(xpidl構文の詳細な説明へのリンクは、参考文献のセクションを参照してください。)


インターフェースの選択

XPCOMは、コンポーネントを扱う際に、インターフェース・ベースの方法を使います。クライアント・コードは、特定のコンポーネントによって提供されるインターフェースだけを使って、そのコンポーネントと対話しなければなりません。ほとんどのコンポーネントは複数のインターフェースをサポートしているため、インターフェース提供メカニズム (つまりQueryInterface メソッド、これについてはすぐに説明) は、インターフェースを管理するための何らかの機能を備えていなければなりません。特に次の機能が必要になります:

  • コンポーネントがサポートするインターフェースを判別すること
  • あるインターフェースから別のインターフェースへ切り替えること (そして元に戻ること)

ここで、これら2つの項目をあわせてインターフェースの選択 (interface discovery) と呼ぶことにします。XPCOMコンポーネントの核となる要件は、インターフェースの選択を処理するための標準インターフェースをサポートしていること、そして、この標準インターフェースは、別のメソッドや機能を提供するために他のXPCOMインターフェースへと拡張するときの基本インターフェースでなければならないということです。この標準インターフェースはnsISupports という名前であり、リスト3の単純なIDLに示されています。

リスト3. 標準インターフェースnslSupports
interface nsISupports
{
void QueryInterface(in nsIIDRef uuid, out nsQIResult result);
nsrefcnt AddRef();
nsrefcnt Release();
};

最初のメソッドであるQueryInterface は、インターフェースの選択を実際に担当するメソッドです。他の2つのメソッドAddRefRelease は、参照をカウントすることにより、コンポーネントの存続期間 (つまり、コンポーネントが存在する期間) を管理するものです。

QueryInterface への最初のパラメーターは、128ビットの長さ (16バイト) のUUID (universally unique ID) 番号への参照です。一例として、nsISupports のインターフェースIDは次のようになります:00000000-0000-0000-c000-000000000046。

UUIDは一般的に、ハイフンで区切られた形式の16進数字を使って書かれています。このID番号は、照会されるコンポーネントでサポートできるインターフェース、あるいはサポートできないインターフェースを指定することになります。コンポーネントは、エラーを示す結果コードを戻すか、要求されたインターフェースのアドレスを2番目のパラメーターにセットし、成功を示す結果コードを戻します。それでXPCOMソフトウェアの設計担当者としては、新しいインターフェースを作成するときに、どの新しいインターフェースにも固有のインターフェースIDを割り当てるよう注意を払います。

リスト4には、特定のコンポーネントの同じインスタンスで異なるインターフェースを切り替えるためにQueryInterface を使う、JavaScriptのサンプルが示されています。

リスト4. インターフェースの切り替え
// first, we create an instance of something...
var file = components.classes["@mozilla.org/file/local;1"].createInstance();
// second, we specify which interface we actually want to use.
file = file.QueryInterface(Components.interfaces.nsIFile);
// do something generic with the nsIFile interface here.
file.create(NORMAL_FILE_TYPE, 0377);
var size = file.fileSize;
// later on, we check to see if an extended interface is supported.
var local = file.QueryInterface(Components.interfaces.nsILocalFile);
if (local)
{
// do something specific to the nsILocalFile interface...
local.initWithPath('/usr/tmp/scratch.txt');
// suppose we're now in some scope where the file variable is no longer
// visible to use but we want to call some function that absolutely
// insists on only accepting an nsIFile and not an nsILocalFile.
// no problem, just QI over to the other interface like so ...
var insists = local.QueryInterface(Components.interfaces.nsIFile);
if (insists)
{
// at this point we can call our hypothetical function
// to do some generic file processing...
hypothetical(insists);
}
}

* MSCOMプログラマーへの注:nsISupports は、形式や機能の点で、MSCOMのIUnknownインターフェースと同じであることがすぐ分かるでしょう。

コンポーネントの作成

上記のJavaScriptの例の中で、奇妙に見えるストリングがあったことに気が付きましたか?それは "@mozilla.org/file/local;1" という部分です。これがコントラクトIDというものです。コンポーネントを明示的に作成するにあたり、作成するコンポーネントをコンポーネント・マネージャーに指定する手段として、2つの形式のIDのいずれかが必要になります。1つの形式は、128ビットの数値である、コンポーネントのクラスIDです。もう1つの形式は、テキスト・ストリングだけを含む契約IDです。コンポーネント・マネージャーからコンポーネントを要求するときには、どちらかがあればそれで十分です。契約IDの意図というのは、該当コンポーネントを使う予定のクライアントに対して、動作や関連するインターフェース群を保証することです。契約IDの推奨フォーマットは、次のような一行のストリングです。

@<internetdomain>/module[/submodule[...]];<version>[?<name>=<value>[&<name>=<value>[...]]]

上記の大括弧の [ 中 ] には、任意指定の項目が入ります。いくつか例をあげます:

  • @mozilla.org/file/directory_service;1>
  • @mozilla.org/file/local;1>
  • @mozilla.org/file;1
  • @mozilla.org/filelocator;1
  • @mozilla.org/filepicker;1
  • @mozilla.org/filespec;1

例にはそれぞれ、1というバージョン番号が入ります。2の値のバージョンが入ったコントラクトIDには、必ずしも下位互換性があるわけではありません。異なるバージョン番号を持つコントラクトIDでは、その保証する動作とインターフェースの一部として、他のコントラクトで保証される動作とインタフェースを含む場合があります。


存続期間の管理

コンポーネントは、使用中のインターフェースが発行された回数をおぼえていなければなりません。他のコードの一部でコンポーネントのいずれかのインターフェースを使おうとしているときに、そのコンポーネントが破棄されてしまうと非常にまずいことになります。あるオブジェクトがインターフェースの別のコピーを作成すると、内部参照カウントが増やされます。オブジェクトのインターフェースが解放されると、その参照カウントは減らされます。オブジェクトの参照カウントがゼロになると、オブジェクト自体が破棄されます。参照カウントというのは、要約するとそのようなものです。

一般に、QueryInterface メソッドは、有効なインターフェース・ポインターを戻すときに、照会されるコンポーネントに対して暗黙的にAddRef を実行します。クライアント・コードの一部がそのインターフェースを使い終えた場合、そのことをコンポーネントに知らせるためにRelease メソッドを呼び出します。このことはすべてのXPCOMクライアント・ソフトウェアで重要な責務です。なぜなら、コンポーネントのQueryInterface またはAddRef ごとに、Release が対応していなければならないためです。XPCOMバグの大部分を突き詰めると、コンポーネント上の欠落しているまたは余分のRelease が見つかるでしょう。


マクロおよびスマート・ポインター

このタイプのエラーに対処するため、XPCOMにはいくつかのC++ テンプレートが備えられています。これにより、スマート・インターフェース・ポインターを宣言することができます。このテンプレートでは、「セットして忘れる」ポインターを使えます。ポインターをあるインターフェースにセットしたら、そのポインターがそのインターフェースを忘れずにリリースします。ポインターを別のインターフェースにセットしても、前のインターフェースをリリースします。とても簡単に思えるでしょう。スマート・ポインターを必要な有効範囲内で宣言して、特定のインターフェースに割り当てるわけです。スマート・ポインターは、ごく普通のインターフェース・ポインターのように使えばよいのです。スマート・ポインターが有効範囲をはずれたら、組み込まれたデストラクターがRelease メソッドを呼び出します。

nsCOMPtr またはnsIPtr を使うMozillaコードについて見てみると、リスト5に示されているようなコードを見ることになります。

リスト5. nsCOMPtrまたはnslPtrを使ったMozillaコード
nsresult nsExample::DoSomething(void)
{
nsresult rv;
nsCOMPtr<nsIManager> pManager;
*aResult = nsnull;
pManager = do_GetService("Some contract ID goes here");
if (pManager == nsnull)
return NS_ERROR_NOT_AVAILABLE;
rv = pManager->ManageSomething(); // do some more work here ...
return rv;
}

リスト5では、架空のnsIManager インターフェースに対するスマート・ポインターが、pManager ("nsCOMPtr.." で開始する行を参照) という名前で宣言されています。スマート・ポインターはあるサービスに割り当てられています。有効なポインターが本当に戻されることをテストした後、上記のコードは、ポインターを参照してManageSomething() メソッドを呼び出します。上記の関数が戻ると、pManager スマート・ポインターは破棄されます。ただし、内部で保留されているインターフェース・ポインターに対してRelease が呼び出される前に破棄されることはありません。

XPCOMは、Cマクロのファミリーを使うことにより、C++ で要求される、大量で説明のわずらわしい作業をはかどらせます。ほとんどのインターフェースはnsresult を戻します。ほとんどのケースで、nsresult を検査するための魔法の値は、NS_OK です。(nsresult 値について徹底して網羅したリストは、nsError.h を参照してください。)

XPCOMには、nsCom.h、nsDebug.h、nsError.h、nsIServiceManager.h ファイルがあり、nsISupportsUtils.h には、テスト、デバッグ、インプリメンテーションのための別のマクロがあります。

いろいろなC++ XPCOMコンポーネントのヘッダー・ファイルをブラウズしてみると、クラス定義の一部にNS_DECL_ISUPPORTS と示されています。このマクロが、nsISupports インターフェースの定義を提供しています。

リスト6. nsISupportsの定義
public:
NS_IMETHOD QueryInterface(REFNSIID aIID void** aInstancePtr);
NS_IMETHOD_(nsrefcnt) AddRef(void);
NS_IMETHOD_(nsrefcnt) Release(void);
nsrefcnt mRefCnt;

コンポーネントの対応するインプリメンテーション・ファイルをブラウズすると、NS_IMPL_ISUPPORTS1 (または同様のもの) という別の不思議な一行マクロがあることに気づかれるでしょう。このマクロが、nsISupports インターフェースの実際のインプリメンテーションを提供しています。マクロの末尾に付けられた "1" という数字は、そのコンポーネントがインプリメントしている (nsISupports 以外の) インターフェースの数を表します。1つのクラスが2つのインターフェースをインプリメントした場合、NS_IMPL_ISUPPORTS2 が使われます。

上記のmRefCnt データ・メンバーを覚えておいてください。またすぐ後で参照します。

次に、nsISupportsUtils.h 内でNS_IMPL_ISUPPORTS1 マクロが定義されている様子を示します:

リスト7. NS_IMPL_ISUPPORTS1
#define NS_IMPL_ISUPPORTS1(_class, _interface) \
NS_IMPL_ADDREF(_class)                       \
NS_IMPL_RELEASE(_class)                      \
NS_IMPL_QUERY_INTERFACE1(_class, _interface)

これを見て分かるように、3つの別のマクロの項で定義されています。さらに詳しく、NS_IMPL_ADDREF の定義からみてみましょう:

リスト8. NS_IMPL_ADDREF
#define NS_IMPL_ADDREF(_class)                               \
NS_IMETHODIMP_(nsrefcnt) _class::AddRef(void)                \
{                                                            \
NS_PRECONDITION(PRInt32(mRefCnt) >= 0, "illegal refcnt");  \
NS_ASSERT_OWNINGTHREAD(_class);                            \
++mRefCnt;                                                 \
NS_LOG_ADDREF(this, mRefCnt, #_class, sizeof(*this));      \
return mRefCnt;                                            \
}

これが実際のコードです! 5行あるコードのうち3行はデバッグ用のマクロで、無視しても構いません。コードの最後の行はreturnステートメントです。AddRef を呼び出すコードはすべて、戻された値を破棄するため、戻りステートメントを無視することができます。

ここで関係してくる1行のコードは、++mRefCnt ステートメントです。このステートメントの役割はカウンターを増やすことがすべてであるため、何らかのインターフェースでAddRef を呼び出すたびに、必ず (十中八九) コンポーネントは内部カウンターを増やします。次に、NS_IMPL_RELEASE マクロを覗いてみましょう:

リスト9. NS_IMPL_RELEASEマクロ
#define NS_IMPL_RELEASE(_class)                              \
NS_IMETHODIMP_(nsrefcnt) _class::Release(void)               \
{                                                            \
NS_PRECONDITION(0 != mRefCnt, "dup release");              \
NS_ASSERT_OWNINGTHREAD(_class);                            \
--mRefCnt;                                                 \
NS_LOG_RELEASE(this, mRefCnt, #_class);                    \
if (mRefCnt == 0) {                                        \
mRefCnt = 1; /* stabilize */                             \
NS_DELETEXPCOM(this);                                    \
return 0;                                                \
}                                                          \
return mRefCnt;                                            \
}

やはり、デバッグ用マクロに関係した3つのステートメントがありますので、無視して構いません。また、2つのreturnステートメントも、前述の理由で無視して構いません。

ここで注目する2つのステートメントは、オブジェクトのカウンターを減らす--mRefCnt と、カウンターがゼロの値に到達したかどうかを確認するためにテストを行うif (mRefCnt == 0) です。次の2行を見てみると、この内部カウンターがゼロになると、オブジェクトはそれ自体を削除することが分かります。

要約しますと、AddRef はカウンターを増やし、Release はカウンターを減らすということです。また、AddRef への呼び出しの数がRelease への呼び出しの数と同じになると、ネットの参照カウントはゼロになるため、コンポーネントはそれ自体を破棄します。このような参照カウントのアイデア全体は、まったく単純明快なものにみえてきたと思います。次に、リスト10で、NS_IMPL_QUERY_INTERFACE1 を定義しています。

リスト10. NS_IMPL_QUERY_INTERFACE1
#define NS_IMPL_QUERY_INTERFACE1(_class, _i1)            \
NS_INTERFACE_MAP_BEGIN(_class)                         \
NS_INTERFACE_MAP_ENTRY(_i1)                          \
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, _i1)   \
NS_INTERFACE_MAP_END

やれやれ!見なければならないマクロがまだあります。これまで、MFCのコード作成者は皆、この種のマクロによる間接を無意味だと見なしてきたため、ここまでの話に思わず笑ってしまうことでしょう。これらのマクロはコンポーネントのインターフェース・マップを作成するため、QueryInterface インプリメンテーションを作成するときには、その順序や位置が重要になります。この遠まわしなやり方にめげることなく、さらに掘り下げてNS_INTERFACE_MAP_BEGIN を見てみましょう。これはNS_IMPL_QUERY_HEAD の別名であることが分かります。そのコードはリスト11に展開されています。

リスト11. NS_INTERFACE_MAP_BEGIN
#define NS_IMPL_QUERY_HEAD(_class)                                       \
NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) \
{                                                                        \
NS_ASSERTION(aInstancePtr, "QueryInterface requires a non-NULL destination!"); \
if ( !aInstancePtr )                                                   \
return NS_ERROR_NULL_POINTER;                                        \
nsISupports* foundInterface;

このマクロのコードは、実際には何もしません。これは関数の宣言の序文を提供すると同時に、ヌル戻りポインターのテストの形式で、簡単なエラー検査の機能を提供するに過ぎません。それどころか、関数を完了するために必要な閉じ中括弧さえ存在していませんので、このマクロの後に別のマクロが続き、残りのコードを構成するのではないかと考えることは理にかなっています。この次のマクロは、そのある部分を行います。NS_INTERFACE_MAP_ENTRY は、NS_IMPL_QUERY_BODY の別名です。

リスト12. NS_IMPL_QUERY_BODY
#define NS_IMPL_QUERY_BODY(_interface)                  \
if ( aIID.Equals(NS_GET_IID(_interface)) )            \
foundInterface = NS_STATIC_CAST(_interface*, this); \
else

これは、インターフェース・マップとのマッチングを実行する、重要なコードの断片です。if/elseステートメントの構造のおかげで、いかなる数のインターフェースIDにも対応するインターフェース・マップを構築するために、複数のNS_IMPL_QUERY_BODY マクロを続けて積み重ねることができます。次のマクロNS_INTERFACE_MAP_ENTRY_AMBIGUOUS は、NS_IMPL_QUERY_BODY_AMBIGUOUS の別名です。

リスト13. NS_IMPL_QUERY_BODY_AMBIGUOUS
#define NS_IMPL_QUERY_BODY_AMBIGUOUS(_interface, _implClass)             \
  if ( aIID.Equals(NS_GET_IID(_interface)) )                             \
    foundInterface = NS_STATIC_CAST(_interface*, NS_STATIC_CAST(_implClass*, this)); \
  else

NS_IMPL_QUERY_BODY_AMBIGUOUS は、NS_IMPL_QUERY_BODY と同じような機能のように見えますが、実際に同じ機能です。唯一追加されているのは、nsISupports 用のインターフェース・ポインターを戻そうとする場合で、nsISupports から派生しサポートされるインターフェースが複数存在する場合に、コンパイラー・エラーを回避することです。サポートされるインターフェースはどれも有効なnsISupports インターフェース・ポインターです。そのため、C++ コンパイラーはどれを選ぶかに関してジレンマに直面してしまいます。XPCOMインターフェース配布メカニズムには1つの要件があり、それは、同じインターフェースIDに対しては必ず同じポインターを戻すということです。それで、このマクロを役立てて、nsISupports から派生したどのインターフェース・ポインターが、それ自体のnsISupports インターフェース・ポインターとして使われるかについても指定し、このルールに適合させることができます。リスト14に示されているように、NS_INTERFACE_MAP_ENDNS_IMPL_QUERY_TAIL_GUTS の別名です。

リスト14. NS_IMPL_QUERY_TAIL_GUTS
#define NS_IMPL_QUERY_TAIL_GUTS    \
foundInterface = 0;            \
nsresult status;                 \
if ( !foundInterface )           \
status = NS_NOINTERFACE;       \
else                             \
{                              \
NS_ADDREF(foundInterface);   \
status = NS_OK;              \
}                              \
*aInstancePtr = foundInterface;  \
return status;                   \
}

やっとQueryInterface のインプリメンテーションの終了にたどり着きました。この最後のコードの断片は、呼び出し元のインターフェースIDがマップに記されているどのIDとも一致しない場合に、エラー・コードNS_NOINTERFACE を戻します。呼び出し元のインターフェースIDが、オブジェクトのサポートされているインターフェースのIDと一致すると、コードはオブジェクトのAddRef メソッドを呼び出し、結果コードNS_OK と共にインターフェースへのポインターを戻します。

これまで調べてきたコードは、1つのインターフェースを持つコンポーネント用の標準のnsISupports のインプリメンテーションです。複数のインターフェースをサポートする標準インプリメンテーションも同様です。実際のコンポーネントは、標準インプリメンテーションのいずれかで作成されていたり、それぞれが独自のインプリメンテーションを用意していたりします。ほとんどは、nsISupportsUtils.h にあるマクロを使うことになります。ここまでで、mozilla/XPCOMコード・ベースを読んで理解したいのであれば、Cスタイルのマクロ (特に定義がネストされているマクロ) を吟味できるようにすることがなぜこれほど重要なのか、その理由を理解できたと思います。


結論

ここまでで出てきたデータは、mozillaソース・コードをブラウズするときの補助として役立つものです。コンポーネントがインターフェース・ポインターを配布する方法、そして存続期間を管理する方法について特に焦点を当てて顕微鏡的な視点で見てきました。この先は、コンポーネントを管理するときに使うことができる、より高いレベルの機能について考えてゆきます。次のステップでは、mozillaブラウザーとその下にあるXPCOMフレームワーク全体を実際に作成するための、開発ツールと他の要件をざっと見てみます。XPCOM開発環境を作成する方には最適でしょう。

参考文献

  • XPIDL。XPIDLインターフェース記述ファイルに基づいてXPCOMインターフェース情報を生成するためのツール。
  • これらのファイルには、テスト、デバッグ、およびインプリメンテーションのためのマクロが含まれます:
  • NetscapeのJavaScript は、ECMA-262 Revision 3 (ECMAScript) 標準スクリプト記述言語のスーパーセットです。
  • 標準ECMA-262ECMAScript言語仕様。
  • Python プログラム言語のホーム・ページ。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=SOA and web services
ArticleID=244013
ArticleTitle=XPCOM: 第2回: XPCOMコンポーネントの基本
publish-date=03012001