Join MDN and developers like you at Mozilla's View Source conference, 12-14 September in Berlin, Germany. Learn more at https://viewsourceconf.org

Modularization Techniques

警告: この記事の内容は古くなっている可能性があります。 このドキュメントの最終更新は 2004 年です。

草案
このページは完成していません。

はじめに

この文書は、新しい Mozilla のモジュールの作成、または既存のコードをモジュールへ分割するのに必要な情報の提供を目的としています。我々が使った仕組みは、COM により築かれた原則に基づいているため、COM についての多くの知識が適用できます。また COM に関するリファレンスには、ここに挙げたものより、もっと興味深く、複雑な例があります。

基本

インタフェース

ジュールの基本的な要素は、C++ の純粋仮想インタフェースです。純粋仮想インタフェースは、単にすべてのメソッドが純粋仮想として定義されたクラスです。例えばこうです。

virtual int foo(int bar) = 0;

純粋仮想インタフェースは、(おそらく動的にローディングした) 別々のライブラリに存在する可能性があるモジュール間で関数テーブルを渡すための簡単な仕組みを提供します。それぞれのインタフェースには、一意なインタフェース識別子 (IID) が割り当てられます。

nsISupports

我々のモデルにおいてキーとなるインタフェースは、nsISupports インタフェースです。これは、COM における IUnknown インタフェースと同等のものです。nsISupports はインタフェース問い合わせと参照カウントという二つの主要な特徴を持ちます。インタフェース問い合わせは、オブジェクトがどのインタフェースをサポートしているかを決定するための単純で画一的な仕組みです。また、オブジェクトがどのように実装されているかを隠すための仕組みです。

インタフェース問い合わせは、QueryInteface() メソッドを使うことで実行されます。呼び出し側は、ID と結果のインタフェースを格納するアドレスへのポインタを渡します。もし問い合わせが成功したら、QueryInteface() は、NS_OK を返します。もしオブジェクトが与えられたインタフェースをサポートしていない場合は、NS_NOINTERFACE を返します。

参照のカウントは、AddRef()Release() メソッドを使って実行されます。オブジェクトの参照カウントは、通常 0 から始まります。AddRef() は、参照カウントを増加させ、Release() は、それを減少させます。もし Release() への呼び出しにより参照カウントが 0 になったら、オブジェクトは通常自分自身を削除します。 成功した QueryInterface() は、復帰する前に、要求したインタフェースに対して AddRef() を呼び出します。AddRef()Release() の両方とも、呼び出し後の参照カウントを返します。

便利な NS_ADDREF() マクロと NS_RELEASE() マクロの方が、AddRefRelase を直接呼び出すよりもお勧めです。デバッグビルドでは、これらのマクロは有用な参照カウントログを提供します。(これらのマクロを) 可能な限り使うようにしてください。

/*
 * nsISupports インタフェース
 */
 
class nsISupports {
public:
    NS_IMETHOD QueryInterface(const nsIID &aIID,
                              void **aResult) = 0;
    NS_IMETHOD_(nsrefcnt) AddRef(void) = 0;
    NS_IMETHOD_(nsrefcnt) Release(void) = 0;
};

NS_IMETHODNS_IMETHOD_(type) マクロは、基本的に virtual nsresultvirtual type の短縮形です。Windows では、COM との互換性のために、それらは virtual nsresult __stdcallvirtual type __stdcall に展開されます。もし COM の互換性に関心があるのでない限り、それらをインタフェースの中で使う必要はありません。

すべての Mozilla インタフェースは、nsISupports を継承しています。nsISupports を継承しているため、どのインタフェースに対してもそのインスタンスがサポートしている他のインタフェースについて問い合わせることができます。また、参照カウントの仕組みを常に使用できることが保証されます。nsISupports の IID は、NS_ISUPPORTS_IID として定義されています。

QueryInterface() には、守られるべき、いくつかの重要な特徴があります。もし QueryInterface() をインタフェース A に対して実行し、インタフェース B を得たならば、QueryInteface() をインタフェース B に対して実行した場合にインタフェース A を得ることができなければなりません。もしインタフェース A と B がどちらも同じインスタンスによって実装されている場合は、nsISupports に対して、QueryInterface() を実行した結果は、どちらも同じインタフェースを返さなければなりません。これは、つまりインタフェース B が nsISupports を継承していても、QueryInteface() の実行によって、同じインタフェースを返さないかもしれないことを意味します。この重要な振舞いは、インタフェース A と B が同じオブジェクトにより実装されているかどうかを決定するための唯一の信頼できる仕組みです。単純なオブジェクトにとって、それらの振舞いを維持するのは簡単です。後に見る集約では、複雑になりえます。

一方、オブジェクトは、AddRef()Release() の実装を、ある程度自由にできます。全体のオブジェクトに対し、一つの参照カウントを維持することができますし、また個々のインタフェースごとの参照カウントを維持することもできます。静的なオブジェクトは、完全に参照カウントを無視することもできます。しかしながら、それらの関数の実装の質が悪ければ、メモリリークや解放済のオブジェクトに対する不慮のアクセスなど否定的な結果になることもありえます。

ファクトリ

ファクトリは、クラスのインスタンスを作るためだけの特別なクラスです。たいてい、Foo クラスはそれと関連する FooFactory を持ちます。nsIFactory インタフェースは、COM の IClassFactory と同等のものです。

/*
 * nsIFactory インタフェース
 */

class nsIFactory: public nsISupports {
public:
    NS_IMETHOD CreateInstance(nsISupports *aOuter,
                              const nsIID &aIID,
                              void **aResult) = 0;
    NS_IMETHOD LockFactory(PRBool aLock) = 0;

ファクトリを使う理由は、それがオブジェクトのためのクラス宣言にアクセスせずに、オブジェクトを作る仕組みを提供しているからです。new Foo() の呼び出しは、コンパイル時に Foo() のクラス宣言にアクセスすることを要求します。ファクトリは、実装者に対して、クラス宣言とオブジェクトの作成の詳細を隠します。特に重要なステップとして、クラスの実装における最大限の自由度を許し、コンパイル時の依存関係を減らします。さらには、クラスとファクトリのリンク時の依存関係を全体的に減らすのにも使われます。

コンポーネントマネージャ

我々のモジュール化の主な目的の一つは、リンク時の依存関係を取り除くことです。では、リンクされないモジュールをどのように見つければよいでしょうか? そのために、nsComponentManager と呼ばれるものを作りました。これは、単純にクラス ID とファクトリ、そしてそれを含むライブラリをマッピングします。

class nsComponentManager {
public:
  // 指定されたクラス ID に対応するファクトリを探す
  static nsresult FindFactory(const nsCID &aClass,
                              nsIFactory **aFactory);
  // 指定されたクラス ID に対応するクラス・インスタンスを作成する
  static nsresult CreateInstance(const nsCID &aClass,
                                 const nsIID &aIID,
                                 nsISupports *aDelegate,
                                 void **aResult);
  // クラスに対応するファクトリを手動で登録する
  static nsresult RegisterFactory(const nsCID &aClass,
                                  nsIFactory *aFactory,
                                  PRBool aReplace);
  // クラスに対応し、動的にロードしたファクトリを手動で登録する
  static nsresult RegisterFactory(const nsCID &aClass,
                                  const char *aLibrary,
                                  PRBool aReplace,
                                  PRBool aPersist);
  // クラスに対応するファクトリを手動で登録抹消する
  static nsresult UnregisterFactory(const nsCID &aClass,
                                    nsIFactory *aFactory);
  // クラスに対応し、動的にロードしたファクトリを手動で登録抹消する
  static nsresult UnregisterFactory(const nsCID &aClass,
                                    const char *aLibrary);
  // 使っていない動的ロードしたファクトリをアンロードする
  static nsresult FreeLibraries();
};

ファクトリをリポジトリーに入れる方法はいくつかあります。最も直接的な方法は、RegisterFactory() を使って行うものです。RegisterFactory() は、2 つの異なる登録の仕組みをサポートしています。最初の方法は、クラス ID とファクトリへのポインタを指定します。この仕組みは、実行可能プログラムにリンクされたファクトリに対して使うことができます。2 番目の方法は、クラス ID と動的にロード可能なライブラリへのパスを指定します。この仕組みは、ファクトリが実行時の実行可能プログラム中にある場合でも使えますし、あるいはその外にあって aPersisit フラグを用いてリポジトリに対し、クラスIDとライブラリの関係を永続的な記憶装置へ格納するように指示することもできます。

nsIID と nsCID について

動的にインタフェースを発見し、ローディングし、結合するプロセスを単純にするために、すべてのクラスとインタフェースにはユニークな ID が割り当てられます。この ID は、UUID に基づくユニークな 128 ビットの数です。詳細を知りたい人のために、構造をここに挙げます:

struct nsID {
  PRUint32 m0;
  PRUint16 m1, m2;
  PRUint8 m3[8];
};

これらは、しばしば以下のように文字列として表現されます:

{221ffe10-ae3c-11d1-b66c-00805f8a2676}

ID 構造体を初期化するためには、以下のように宣言します:

ID = {0x221ffe10, 0xae3c, 0x11d1,
       {0xb6, 0x6c, 0x00, 0x80, 0x5f, 0x8a, 0x26, 0x76}};

なぜ 2 バイトの b66c が分割され、また後ろのバイト列といっしょにされているのかは、おそらくどこかの脚注にあります。Windows では、ID を生成するために、Visual C++ についてくる uuidgenguidgen が使えます。

単純な例

インタフェースを定義するためには、XPIDL を使うことをお勧めします。このサンプルコードはこれを反映するように更新すべきなのですが、これにより C++ の観点から COM の有用で基本的な理解を得ることができます。

ファイル nsISample.h

nsISample.h は、とても単純なインタフェースとそのインタフェース ID (IID) を定義しています。重要なことは、インタフェースは nsISupports から継承し、すべてのメンバ関数は、純粋仮想メソッドであるということです。

#include "nsISupports.h"

// {57ecad90-ae1a-11d1-b66c-00805f8a2676}
#define NS_ISAMPLE_IID \
{0x57ecad90, 0xae1a, 0x11d1, \
  {0xb6, 0x6c, 0x00, 0x80, 0x5f, 0x8a, 0x26, 0x76}}

/*
 * nsISample インタフェース宣言
 */

class nsISample: public nsISupports {
public:
  NS_IMETHOD Hello() = 0;
};

ファイル nsSample.h

nsSample.h は、サンプルクラスのためのクラス ID (CID) を定義します。一つのインタフェースに対し、複数のクラスがそれを実装しても良いため、IID から CID への 1 対 1 マッピングは必要ないことに注意してください。さらに、クラスファクトリを取得するための関数も定義しています。これがクラス宣言を含まないことに注意してください。

#include "nsIFactory.h"

// {d3944dd0-ae1a-11d1-b66c-00805f8a2676} 
#define NS_SAMPLE_CID \
 {0xd3944dd0, 0xae1a, 0x11d1, \
   {0xb6, 0x6c, 0x00, 0x80, 0x5f, 0x8a, 0x26, 0x76}}

extern nsresult GetSampleFactory(nsIFactory **aResult);

ファイル nsSample.cpp

nsSample.cpp は、サンプルクラスの宣言と実装の両方を含みます。またクラスファクトリの宣言と実装も含みます。

#include "nsISample.h" 
#include "nsSample.h" 

static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); 
static NS_DEFINE_IID(kIFactoryIID, NS_IFACTORY_IID); 
static NS_DEFINE_IID(kISampleIID, NS_ISAMPLE_IID); 
static NS_DEFINE_CID(kISampleCID, NS_ISAMPLE_CID); 

/*
 * nsSampleClass 宣言
 */ 

class nsSample: public nsISample { 
private: 
  nsrefcnt mRefCnt; 

public: 
// コンストラクタとデストラクタ
  nsSample(); 
  ~nsSample(); 

// nsISupports メソッド 
  NS_IMETHOD QueryInterface(const nsIID &aIID, 
                            void **aResult); 
  NS_IMETHOD_(nsrefcnt) AddRef(void); 
  NS_IMETHOD_(nsrefcnt) Release(void); 

// nsISample メソッド 
  NS_IMETHOD Hello(); 
}; 

/* 
 * nsSampleFactory 宣言 
 */ 

class nsSampleFactory: public nsIFactory { 
private: 
  nsrefcnt mRefCnt; 

public: 
  nsSampleFactory(); 
  ~nsSampleFactory(); 

// nsISupports メソッド 
  NS_IMETHOD QueryInterface(const nsIID &aIID, 
                            void **aResult); 
  NS_IMETHOD_(nsrefcnt) AddRef(void); 
  NS_IMETHOD_(nsrefcnt) Release(void); 

// nsIFactory メソッド 
  NS_IMETHOD CreateInstance(nsISupports *aOuter, 
                            const nsIID &aIID, 
                            void **aResult); 

  NS_IMETHOD_(void) LockFactory(PRBool aLock); 
}; 

/* 
 * nsSample 実装 
 */ 

nsSample::nsSample() 
{ 
  mRefCnt = 0; 
} 

nsSample::~nsSample() 
{ 
  assert(mRefCnt == 0); 
} 

NS_IMETHOD nsSample::QueryInterface(const nsIID &aIID, 
                                  void **aResult) 
{ 
  if (aResult == NULL) { 
    return NS_ERROR_NULL_POINTER; 
  } 

  // 失敗時は、常に結果は NULL
  *aResult = NULL; 

  if (aIID.Equals(kISupportsIID)) { 
    *aResult = (void *) this; 
  } else if (aIID.Equals(kISampleIID)) { 
    *aResult = (void *) this; 
  } 

  if (aResult != NULL) { 
    return NS_ERROR_NO_INTERFACE; 
  } 

  AddRef(); 
  return NS_OK; 
} 

nsRefCount nsSample::AddRef() 
{ 
  return ++mRefCnt; 
} 

nsRefCount nsSample::Release() 
{ 
  if (--mRefCnt == 0) { 
    delete this; 
    return 0; // 削除した後、mRefCnt にアクセスしてはいけない!
  } 
  return mRefCnt; 
} 

/* 
 * nsSampleFactory 実装 
 */ 

nsSampleFactory::nsSampleFactory() 
{ 
  mRefCnt = 0; 
} 

nsSampleFactory::~nsSampleFactory() 
{ 
  assert(mRefCnt == 0); 
} 

NS_IMETHODIMP nsSampleFactory::QueryInterface(const nsIID &aIID, 
                                         void **aResult) 
{ 
  if (aResult == NULL) { 
    return NS_ERROR_NULL_POINTER; 
  } 

  // 失敗時は、常に結果は NULL
  *aResult = NULL; 

  if (aIID.Equals(kISupportsIID)) { 
    *aResult = (void *) this; 
  } else if (aIID.Equals(kIFactoryIID)) { 
    *aResult = (void *) this; 
  } 

  if (*aResult == NULL) { 
    return NS_ERROR_NO_INTERFACE; 
  } 

  AddRef(); // 呼び出し側のために参照カウントを増やす
  return NS_OK; 
} 

NS_IMETHODIMP(nsRefCount) nsSampleFactory::AddRef() 
{ 
  return ++mRefCnt; 
} 

NS_IMETHODIMP(nsRefCount) nsSampleFactory::Release() 
{ 
  if (--mRefCnt == 0) { 
    delete this; 
    return 0; // 削除した後、mRefCnt にアクセスしてはいけない!
  } 
  return mRefCnt; 
}

NS_IMETHODIMP nsSampleFactory::CreateInstance(nsISupports *aOuter,
                                         const nsIID &aIID,
                                         void **aResult)
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }

  *aResult = NULL;

  nsISupports inst = new nsSample();

  if (inst == NULL) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  nsresult res = inst->QueryInterface(aIID, aResult);

  if (res != NS_OK) {
    // 正しいインタフェースを取得できなかったので、片付ける
    delete inst;
  }

  return res;
}

void nsSampleFactory::LockFactory(PRBool aLock)
{
  // 最も単純な場合では、実装しない
}

nsresult GetSampleFactory(nsIFactory **aResult) 
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }

  *aResult = NULL;

  nsISupports inst = new nsSampleFactory();

  if (inst == NULL) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  nsresult res = inst->QueryInterface(kIFactoryIID, aResult);

  if (res != NS_OK) {
    // 正しいインタフェースを取得できなかったので、片付ける
    delete inst;
  }

  return res;
}

ファイル main.cpp

main.cpp は、サンプルクラスのインスタンスを作成し、それを処分する単純なプログラムです。クラスファクトリを直接含むので、クラスのための CID を使いません。

#include "nsISample.h"
#include "nsSample.h"

static NS_DEFINE_IID(kISampleIID, NS_ISAMPLE_IID);

int main(int argc, char *argv[])
{
  nsIFactory *factory;
  GetSampleFactory(&factory);

  nsISample *sample;

  nsresult res = factory->CreateInstance(NULL, kISampleIID,
                                         (void **) &sample);

  if (res == NS_OK) {
    sample->Hello();
    NS_RELEASE(sample);
  }

  return 0;
}

DLL への移行

DLL の実装

一度ファクトリを設定したならば、DLL へ移行するのは比較的小さなことです。ファクトリを含む DLL は、一つまたは二つの関数をエクスポートする必要があります:

// 与えられたクラス ID と関連したファクトリを返す
extern "C" NS_EXPORT nsresult NSGetFactory(const nsCID &aCID,
                                           nsIFactory **aFactory);

// DLL が今アンロード可能かどうかを返す
extern "C" NS_EXPORT PRBool NSCanUnload();

NSGetFactory() の最も単純な実装は、前の例における GetSampleFactory() とほとんど同じです。渡されてきたクラス ID が、実装したファクトリに対して正しい ID であることを確認するだけです。DLL が複数のファクトリを含む場合は、どれを返すか決めるための条件コードを付加する必要があります。

NSCanUnload() は、オプションですが、有用な関数です。これが実装されていた場合は、NSRepository は FreeLibraries() が呼ばれた時点では、もはや使われていない DLL をアンロードすることで、メモリーを解放することができます。DLL がアンロード可能かどうかを判断する時に、実装する上では二つのことを考慮します: そのファクトリのどれかが現在使われているか、そして誰かがサーバをロックしているか。もし NSCanUnload() が実装されていない場合、DLL はアンロードされません。

以下の例は、nsSample.cpp を DLL にコンパイルされるファイルに変更したものです。違いは、強調 で示します。 実際にはあまり違いはないです。

ファイル nsSample3.cpp

#include <iostream.h>
#include "pratom.h"
#include "nsRepository.h"
#include "nsISample.h" 
#include "nsSample.h" 

static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); 
static NS_DEFINE_IID(kIFactoryIID, NS_IFACTORY_IID); 
static NS_DEFINE_IID(kISampleIID, NS_ISAMPLE_IID); 
static NS_DEFINE_CID(kSampleCID, NS_SAMPLE_CID); 

<strong>/*
 * グローバル
 */

static PRInt32 gLockCnt = 0;
static PRInt32 gInstanceCnt = 0;</strong>

/*
 * nsSampleClass 宣言
 */ 

class nsSample: public nsISample { 
private: 
  nsrefcnt mRefCnt; 

public: 
// コンストラクタとデストラクタ
  nsSample(); 
  ~nsSample(); 

// nsISupports メソッド 
  NS_IMETHOD QueryInterface(const nsIID &aIID, 
                            void **aResult); 
  NS_IMETHOD_(nsrefcnt) AddRef(void); 
  NS_IMETHOD_(nsrefcnt) Release(void); 

// nsISample メソッド 
  NS_IMETHOD Hello(); 
}; 

/* 
 * nsSampleFactory 宣言 
 */ 

class nsSampleFactory: public nsIFactory { 
private: 
  nsrefcnt mRefCnt; 

public: 
  nsSampleFactory(); 
  ~nsSampleFactory(); 

// nsISupports メソッド 
  NS_IMETHOD QueryInterface(const nsIID &aIID, 
                            void **aResult); 
  NS_IMETHOD_(nsrefcnt) AddRef(void); 
  NS_IMETHOD_(nsrefcnt) Release(void); 

// nsIFactory メソッド 
  NS_IMETHOD CreateInstance(nsISupports *aOuter, 
                                  const nsIID &aIID, 
                                  void **aResult); 

  NS_IMETHOD_(void) LockFactory(PRBool aLock); 
}; 

/* 
 * nsSample Implemtation 
 */ 

nsSample::nsSample() 
{ 
  mRefCnt = 0; 
  <strong>PR_AtomicIncrement(&gInstanceCnt);</strong>
} 

nsSample::~nsSample() 
{ 
// assert(mRefCnt == 0); 
  <strong>PR_AtomicDecrement(&gInstanceCnt);</strong>
} 

NS_IMETHODIMP nsSample::Hello() {
  cout << "Hello, world\n";

  return NS_OK;
}

NS_IMETHODIMP nsSample::QueryInterface(const nsIID &aIID, 
                                  void **aResult) 
{ 
  if (aResult == NULL) { 
    return NS_ERROR_NULL_POINTER; 
  } 

  // 失敗時は、常に結果は NULL
  *aResult = NULL; 

  if (aIID.Equals(kISupportsIID)) { 
    *aResult = (void *) this; 
  } else if (aIID.Equals(kISampleIID)) { 
    *aResult = (void *) this; 
  } 

  if (aResult != NULL) { 
    return NS_NOINTERFACE; 
  } 

  AddRef(); 
  return NS_OK; 
} 

NS_IMETHODIMP nsSample::AddRef() 
{ 
  return ++mRefCnt; 
} 

NS_IMETHODIMP nsSample::Release() 
{ 
  if (--mRefCnt == 0) { 
    delete this; 
    return 0; // 削除した後、mRefCnt にアクセスしてはいけない!
  } 
  return mRefCnt; 
} 

/* 
 * nsSampleFactory 実装 
 */

nsSampleFactory::nsSampleFactory() 
{ 
  mRefCnt = 0; 
  <strong>PR_AtomicIncrement(&gInstanceCnt);</strong>
} 

nsSampleFactory::~nsSampleFactory() 
{ 
// assert(mRefCnt == 0); 
  <strong>PR_AtomicDecrement(&gInstanceCnt);</strong>
}

NS_IMETHODIMP nsSampleFactory::QueryInterface(const nsIID &aIID, 
                                         void **aResult) 
{ 
  if (aResult == NULL) { 
    return NS_ERROR_NULL_POINTER; 
  } 

  // 失敗時は、常に結果は NULL
  *aResult = NULL; 

  if (aIID.Equals(kISupportsIID)) { 
    *aResult = (void *) this; 
  } else if (aIID.Equals(kIFactoryIID)) { 
    *aResult = (void *) this; 
  } 

  if (*aResult == NULL) { 
    return NS_NOINTERFACE; 
  } 

  AddRef(); // 呼び出し元のために参照カウントを増やす
  return NS_OK; 
} 

NS_IMETHODIMP_(nsrefcnt) nsSampleFactory::AddRef() 
{ 
  return ++mRefCnt; 
} 

NS_IMETHODIMP_(nsrefcnt) nsSampleFactory::Release() 
{ 
  if (--mRefCnt == 0) { 
    delete this; 
    return 0; // 削除した後、mRefCnt にアクセスしてはいけない!
  } 
  return mRefCnt; 
}

NS_IMETHODIMP nsSampleFactory::CreateInstance(nsISupports *aOuter,
                                         const nsIID &aIID,
                                         void **aResult)
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }

  *aResult = NULL;

  nsISupports *inst = new nsSample();

  if (inst == NULL) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  nsresult res = inst->QueryInterface(aIID, aResult);

  if (res != NS_OK) {
    // 正しいインタフェースを取得できなかったので、片付ける
    delete inst;
  }

  return res;
}

<strong>/*
 * エクスポートされた関数
 */

void nsSampleFactory::LockFactory(PRBool aLock)
{
  if (aLock) {
    PR_AtomicIncrement(&gLockCnt);
  } else {
    PR_AtomicDecrement(&gLockCnt);
  }
}

extern "C" NS_EXPORT nsresult NSGetFactory(const nsCID &aCID, 
                                           nsIFactory **aResult)</strong>
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }

  *aResult = NULL;

  nsISupports *inst;

  <strong>if (aCID.Equals(kSampleCID)) {
    inst = new nsSampleFactory();
  } else {
    return NS_ERROR_ILLEGAL_VALUE;
  }

  if (inst == NULL) {
    return NS_ERROR_OUT_OF_MEMORY;
  }</strong>

  nsresult res = inst->QueryInterface(kIFactoryIID, (void **) aResult);

  if (res != NS_OK) {
    // 正しいインタフェースを取得できなかったので、片付ける
    delete inst;
  }

  return res;
}

<strong>extern "C" NS_EXPORT PRBool NSCanUnload()
{
  return PRBool(gInstanceCnt == 0 && gLockCnt == 0);
}</strong>

これで、ファクトリを直接呼び出す代わりに、NSRepository::CreateInstance() を呼び出すことができます。私たちはどういうわけかファクトリ登録そのものに依存しているのです。

ファイル main2.cpp

#include "nsRepository.h"
#include "nsISample.h"
#include "nsSample.h"

static NS_DEFINE_IID(kISampleIID, NS_ISAMPLE_IID);
static NS_DEFINE_CID(kSampleCID, NS_SAMPLE_CID);

int main(int argc, char *argv[])
{
  nsISample *sample;

  nsresult res = NSRepository::CreateInstance(kSampleCID,
                                              NULL,
                                              kISampleIID,
                                              (void **) &sample);

  if (res == NS_OK) {
    sample->Hello();
    NS_RELEASE(sample);
  }

  return 0;
}

DLL の登録

これは、現在論じあっている部分です。現在のところ、NSRepository の RegisterFactory() メソッド (この例については nsSample2.cpp を見てください) を使って DLL を手動で登録できます。

DLL は自分の登録と登録抹消のために二つの付加的な関数をエクスポートすることができます。

extern "C" NS_EXPORT nsresult NSRegisterSelf(const char *path);
extern "C" NS_EXPORT nsresult NSUnregisterSelf(const char *path);

これにより、DLL のすべてのファクトリを登録および登録抹消することができます。RegFactory.exe (Windows 用) または regfactory (Unix 用) という単純なプログラムを使うことで自己登録する DLL を登録することができます。

参照カウントの基本

参照カウントは、モジュール方式の図式の重要な部分です。参照カウントについて、覚えるべき多くの基本的なルールがあります。これは、簡単なサマリです。

出力パラメタ

新しいインタフェースを返す関数は、それを返す前にそれ (返すインタフェース) に対して AddRef() を呼ぶ必要があります。

nsresult GetFoo(IFoo **aFooRes)
{
  if (aFooRes == NULL) {
    return NS_ERROR_NULL_POINTER;
  }
  *aFooRes = mFoo;
  NS_ADDREF(*aFooRes);

  return NS_OK;
}

これは、QueryInteface()CreateInstance()NS_Newx() により返されるインタフェースに適用されることを覚えておきましょう。そして、メモリーリークを防ぐために、使い終ったら、Release() を呼ぶ必要があります。

入力パラメタとローカルポインタ

渡されたインタフェースとそのインタフェースポインタのローカルコピーは、呼び出している関数の生存期間に含まれると見なされます。AddRef() を呼び出す必要はありません。

nsresult TweekFoo(IFoo *aFoo1, IFoo *aFoo2) {
  IFoo local = aFoo;

  if (aFoo->Bar() == NS_OK) {
    local = aFoo2;
  }

  return local->Boff();
}

入出力パラメタ

入出力パラメタは、入力パラメタとしても出力パラメタとしても使われます。もし、関数がインタフェースの入出力パラメタの値を変えた場合、入力のインタフェースに対しては、Release() を呼び出し、出力のインタフェースに対しては、AddRef() を呼び出す必要があります。

nsresult RefreshFoo(IFoo **aFoo)
{
  if (aFoo == NULL || *aFoo == NULL) {
    return NS_ERROR_NULL_PARAMETER;
  }
  if ((*aFoo)->Stale()) {
    NS_RELEASE(*aFoo);
    *aFoo = mFoo;
    NS_ADDREF(*aFoo);
  }
  return NS_OK;
}

グローバル変数とメンバー変数

グローバル変数とメンバ変数は、生存期間がどの関数によっても変えられる可能性があります。そのため、関数に渡されて来たグローバル変数とメンバ変数に対して AddRef() を呼び出し、使い終ったら Release() を呼び出す必要があります。

NS_ADDREF(mFoo);
TweekFoo(mFoo);
NS_RELEASE(mFoo);

(もうすぐ) よく聞かれる質問

なぜ COM を真似る必要があるのですか? COM は最低ではないですか?

この意見は、おそらく OLE に対する経験もしくは伝聞を元にしているのではないでしょうか。重要なことは、COM は OLE ではないということです。OLE は COM の上に作られていますが、COM の輝かしい例ではありません。COM は単純にインタフェースの配置と使用、またここで多くの説明をした重要なコンポーネントのための仕組みです。OLE (実際には OLE2) は COM を使用する最初の試みでした。

なぜ C++ なのですか?

C++ は、インタフェースを実装する間単な仕組みを持っています。手動で関数テーブルとマクロを使ってインタフェースを組み立てることもできますが、C++ コンパイラが自動的にやってくれることに対しては、単にそれを使うべきでしょう。

Cを使うことはできますか?

インタフェース以外の部分では、どこでも C を使うことができます。C でインタフェースを宣言する仕組みもありますが、とても大変ですし、コンパイラに依存してしまいます。我々は、これをできるだけ軽くしようとしています。

なぜ COM ではないのですか?

広範な COM のサポートがあるプラットフォームは、現在のところ Windows だけです。Microsoft は、Macintosh 用の COM 拡張を出荷していますが、一般的に Internet Explorer か Microsoft Office といっしょにしかインストールされません。COM の UNIX サポートは、不十分です。

Windows 上の COM ではだめなのですか?

それは、我々が必要としているクロスプラットフォームな解決策ではないからです。我々は、サポートしているプラットフォーム上での COM と我々のインタフェースが互換性を持つように努力しています。そのため、(COM ではないということが) 問題とならないかもしれません。ただ、まだ約束はできません。

大きな違いはなんでしょうか?

Microsoft の MIDL コンパイラではなく、CORBA 準拠の IDL コンパイラの XPIDL を使っていることです。それは、C++ ヘッダを生成するときに NSPR の型を出力します。Microsoft の .TLB フォーマットとは互換性がない タイプライブラリ も出力します。XPCOM は、JavaScript のような 他の言語が実装し、また XPCOM オブジェクトの呼び出しを使えるようにするため、これらのタイプライブラリを使います。タイプライブラリと NSPR のイベントキューを使ったスレッド間の代理呼び出しも行います。

Microsoft は、COM の広範なサポート基盤を提供しています。この技術は、Windows に組み込まれていますが、他の多くのプラットフォームではそうではありません。この技術は、Microsoft からライセンス取得可能ですが、明らかな理由から我々はそうしません。この技術の重要な要素と同等のものを、自分たちで必要に応じて開発するつもりです。

リンク

修正履歴

  • 1998 年 2 月 25 日 作成
  • 1998 年 10 月 19 日 ちょっと修正
  • 1999 年 10 月 10 日 XPIDL、言語無依存についてのコメントを追加

原文書の情報

  • 著者: Will Scullin
  • 最終更新日: September 13, 2004
  • 著作権: Portions of this content are © 1998–2007 by individual mozilla.org contributors; content available under a Creative Commons license | 詳細

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

タグ: 
 このページの貢献者: Kohei, Mgjbot
 最終更新者: Kohei,