モジュール化テクニック

Rough Draft
Created Feb 25, 1998 by Will Scullin

もくじ

導入

このドキュメントの目的は、新しいモジラモジュールを作成する時や、既存のコードをモジュールに分解する時に必要となる全ての情報を提供することです。我々が使っているメカニズムは、COMにより提示される原理に基づいています。そう言うわけで、あなたがCOMについて知っているどんな事でも、ここに応用でき、COMに関する如何なる文献もが、ここで提供されるものより、より興味深く複雑な実例かもしれません。

基本

インターフェース

モジュールの基本となる組み立て要素はC++の純粋仮想インターフェースです。純粋仮想インターフェースは全てのメソッドが純粋仮想として定義される単なるクラスで、つまり:

virtual int foo(int bar) = 0;

純粋仮想インターフェースは、別の(ダイナミックにロードされるかもしれない)ライブラリ内に存在しうるモジュール間で、関数テーブルを受け渡す為の簡単なメカニズムになります。各インターフェースにはIIDというユニークなインターフェース識別子が割り当てられます。

nsISupports

我々のモデルに於いてキーとなるインターフェースはnsISupportsで、COMのIUnknownと同等です。nsISupportsは、インターフェース問い合わせと、参照カウンティングという、2つのキーとなる特性を持ちます。インターフェース問い合わせは、オブジェクトがどのインターフェースをサポートするかを決定し、オブジェクトがどのように実装されているかのメカニズムを隠蔽する為の、簡易で統一されたメカニズムです。

インターフェース問い合わせは、QueryInterface()メソッドを使用して行われます。呼び出し側は、IDと、結果のインターフェースを受けるアドレスへのポインタを渡します。もし問い合わせが成功すればQueryInterface()NS_OKを返します。もしオブジェクトがそのインターフェースをサポートしていなければ、NS_NOINTERFACEを返します。

参照カウンティングは、AddRef()Release()メソッドを使用して行われます。オブジェクトの参照カウントは一般的には0からスタートします。AddRef()はその参照カウントをインクリメントし、Release()はディクリメントします。もしRelease()呼び出しの結果参照カウントが0になった場合は、オブジェクトは一般的に自らを破棄します。QueryInterface()は、成功するとリターンする前に、要求されたインターフェースに対しAddRef()をコールします。AddRef()Release()は共に、リターン値として参照カウントを返します。

AddRefReleaseを直接コールするより、便利なマクロNS_ADDREF()NS_RELEASE()を使用する方がよいでしょう。デバッグビルドでは、これらのマクロは、有用な参照カウンティングのログを提供します。可能な時はいつでもこれらを使ってください。

/*
 * The nsISupports interface
 */

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の互換性を気にしないなら、インターフェースでこれらを使う必要はありません。

全てのモジラインターフェースは、nsISupportsを継承します。nsISupportsからの継承は、どのインターフェースにも、それがサポートしているであろう他のインターフェースを問い合わせることを許し、参照カウンティング機構が常に利用可能であることを保証します。nsISupportsのIIDは、NS_ISUPPORTS_IIDとして定義されています。

QueryInterface()は維持されなければならない幾つかの重要な特性を持ちます。インターフェースAに対してQueryInterface()してインターフェースBを取得したならば、インターフェースBに対してQueryInterface()してインターフェースAを取得できねばなりません。もしインターフェースAとBとが同じインスタンスにより実装されているならば、どちらに対してnsISupportsQueryInterface()しても、完全に同一のインターフェースが返されるべきです。このことは、かりにインターフェースBがnsISupportsから継承していたとしても、それに対してQueryInterface()すると同じインターフェースを返さないかもしれないことを意味します。この重要な動作は、インターフェースAとBが同じオブジェクトにより実装されているかどうかを決定する為の唯一の確かな方法です。シンプルなオブジェクトにとっては、これらの動作を維持することは易しいでしょう。アグリゲーション(あとで見ますが)では、厄介です。

かたや、AddRef()Release()の実装に於いては、オブジェクトはある程度の柔軟性が許されます。オブジェクト全体を単一の参照カウントで管理しても良いし、各インターフェース毎に独立した参照カウントでも良いです。スタティックオブジェクトでは、参照カウントを全く無視するかもしれません。しかし、これらの機能のまずい実装は、メモリリークや解放されたオブジェクトへの予期せぬアクセスなどの、悪い結果をまねくかもしれません。

ファクトリー

ファクトリーは、クラスのインスタンスを作成する為の、特別なクラスです。Fooクラスは、典型的にはそれに関連した、FooFactoryを持ちます。nsIFactoryインターフェースはCOMのIClassFactoryに当ります。

/*
 * The nsIFactory interface
 */

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

ファクトリーを使う理由は、それがそのオブジェクトの宣言なしにオブジェクトを生成するメカニズムを提供するからです。Foo()に対してnewを呼ぶには、コンパイル時にFoo()の宣言にアクセスすることが必要です。ファクトリーは、実装者がクラスの宣言と作成のこまごましたことの両者を隠すことを可能にします。クラスの実装に最大限の柔軟性を許し、コンパイル時の依存度を軽減する為の非常に重要なステップです。クラスとそのファクトリー全体へのリンク時の依存をなくすことにさえ、使用できます。

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

我々のモジュール化の1つの主要なゴールは、リンク時の依存をなくすことです。では、それとリンクしないとしたら、どうやってモジュールを捜せるのでしょう?我々はnsComponentManagerと呼ばれるものを作りました。nsComponentManagerは単なる、クラスIDから、ファクトリーとそれを含むライブラリへの、マッピングです。

class nsComponentManager {
public:
  // Finds a factory for a specific class ID
  static nsresult FindFactory(const nsCID &aClass,
                              nsIFactory **aFactory);

  // Creates a class instance for a specific class ID
  static nsresult CreateInstance(const nsCID &aClass,
                                 const nsIID &aIID,
                                 nsISupports *aDelegate,
                                 void **aResult);

  // Manually registry a factory for a class
  static nsresult RegisterFactory(const nsCID &aClass,
                                  nsIFactory *aFactory,
                                  PRBool aReplace);

  // Manually registry a dynamically loaded factory for a class
  static nsresult RegisterFactory(const nsCID &aClass,
                                  const char *aLibrary,
                                  PRBool aReplace,
                                  PRBool aPersist);

  // Manually unregister a factory for a class
  static nsresult UnregisterFactory(const nsCID &aClass,
                                    nsIFactory *aFactory);

  // Manually unregister a dynamically loaded factory for a class
  static nsresult UnregisterFactory(const nsCID &aClass,
                                    const char *aLibrary);

  // Unload dynamically loaded factories that are not in use
  static nsresult FreeLibraries();
};

ファクトリーがリポジトリに道を作るには、幾つかの方法があります。最も直接的なものは、RegisterFactory()によるものです。RegisterFactory()は2つの異なる登録メカニズムをサポートします。1つめは、クラスIDとファクトリーへのポインタを(引き数として)とります。このメカニズムは実行形式へリンクされるファクトリーに対して使用することができます。2つめは、クラスIDとダイナミックロード可能なライブラリへのパスをとります。このメカニズムは、実行時に実行形式の内部にあるものと、aPersistフラグでリポジトリにクラス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}};

なぜb66cペアが分割され、最後のバイトセットとしてグループされているかは、多分何処かに脚注が。Windowsでは、Visual C++と共に出荷されているuuidgenguidgenプログラムが、IDの生成に使えます。

簡単な実例

注意:インターフェースを定義する為には、XPIDLを使用することが推奨されます。このサンプルコードはこれを反映してアップデートされるべきですが、C++から見たCOMの基本を理解するにはなります。

nsISample.hでは、極端にシンプルなインターフェースとそのインターフェースID(IID)を定義しています。注意すべき重要なことは、インターフェースがnsISupportsから継承することと、全てのメンバ関数が純粋仮想メソッドであることです。

File nsISample.h
#include "nsISupports.h"

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

/*
 * nsISample Interface declaration
 */

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

nsSample.hは、我々のサンプルクラスの為にクラスID(CID)を定義します。1つのインターフェースが、それを実装する複数のクラスを持ちうることに注意してください。その為、IIDからCIDへのマッピングは必ずしも1対1とは限りません。nsSample.hはまた、我々のクラスのファクトリーを取得sる関数も定義しています。nsSample.hがクラス定義を含まないことに注意してください。

File nsSample.h
#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は、我々のサンプルクラスの定義と実装と、クラスファクトリーの定義と実装の、両方を含みます。

File 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 Declaration
 */ 

class nsSample: public nsISample { 
private: 
  nsrefcnt mRefCnt; 

public: 
// Constructor and Destuctor 
  nsSample(); 
  ~nsSample(); 

// nsISupports methods 
  NS_IMETHOD QueryInterface(const nsIID &aIID, 
                            void **aResult); 
  NS_IMETHOD_(nsrefcnt) AddRef(void); 
  NS_IMETHOD_(nsrefcnt) Release(void); 

// nsISample method 
  NS_IMETHOD Hello(); 
}; 

/* 
 * nsSampleFactory Declaration 
 */ 

class nsSampleFactory: public nsIFactory { 
private: 
  nsrefcnt mRefCnt; 

public: 
  nsSampleFactory(); 
  ~nsSampleFactory(); 

// nsISupports methods 
  NS_IMETHOD QueryInterface(const nsIID &aIID, 
                            void **aResult); 
  NS_IMETHOD_(nsrefcnt) AddRef(void); 
  NS_IMETHOD_(nsrefcnt) Release(void); 

// nsIFactory methods 
  NS_IMETHOD CreateInstance(nsISupports *aOuter, 
                            const nsIID &aIID, 
                            void **aResult); 

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

/* 
 * nsSample Implementation 
 */ 

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; 
  } 

  // Always NULL result, in case of failure 
  *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; // Don't access mRefCnt after deleting!
  } 
  return mRefCnt; 

/* 
 * nsSampleFactory Implementation 
 */ 

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; 
  } 

  // Always NULL result, in case of failure 
  *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(); // Increase reference count for caller 
  return NS_OK; 

NS_IMETHODIMP(nsRefCount) nsSampleFactory::AddRef() 

  return ++mRefCnt; 

NS_IMETHODIMP(nsRefCount) nsSampleFactory::Release() 

  if (--mRefCnt == 0) { 
    delete this; 
    return 0; // Don't access mRefCnt after deleting! 
  } 
  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) {
    // We didn't get the right interface, so clean up
    delete inst;
  }

  return res;
}

void nsSampleFactory::LockFactory(PRBool aLock)
{
  // Not implemented in simplest case.
}

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) {
    // We didn't get the right interface, so clean up
    delete inst;
  }

  return res;
}

main.cppは、我々のサンプルクラスのインスタンスを生成し、これを破棄する、簡単なプログラムです。ここではクラスファクトリーを直接取得しているので、そのクラスのCIDを使っていません。

File main.cpp
#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は、1つか2つのエクスポート関数を定義する必要があります:

// Returns the factory associated with the given class ID
extern "C" NS_EXPORT nsresult NSGetFactory(const nsCID &aCID,
                                           nsIFactory **aFactory);

// Returns whether the DLL can be unloaded now.
extern "C" NS_EXPORT PRBool NSCanUnload();

最も単純なケースでのNSGetFactory()の実装は、前の例でのGetSampleFactory()のものと同等です。渡されたクラスIDが実装しているファクトリーにとって正しいものであることを検証する必要があるだけです。もしDLLが複数のファクトリーを含んでいるなら、どれを返すかを決定する条件コードをいれる必要があるでしょう。

NSCanUnload()はオプションですが、有用な関数です。もし実装されると、FreeLibraries()が呼ばれた時に、NSRepositoryがもう使用されていないDLLをアンロードすることによりメモリを解放することを可能にします。DLLがアンロード可能かどうかを決めるのに、実装は2つのことを考慮にいれます。:どれかのファクトリーが使用中かどうかと、誰かがサーバーをロックしていないかどうか、です。もしNSCanUnload()が実装されていなければ、DLLはアンロードされないでしょう。

次ぎの例は、nsSample.cppをDLLとしてコンパイルできるように変更したものです。変更点がブルーでハイライトしてあります。そんなに多くはありません。

File 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); 

/*
 * Globals
 */

static PRInt32 gLockCnt = 0;
static PRInt32 gInstanceCnt = 0;

/*
 * nsSampleClass Declaration
 */ 

class nsSample: public nsISample { 
private: 
  nsrefcnt mRefCnt; 

public: 
// Constructor and Destuctor 
  nsSample(); 
  ~nsSample(); 

// nsISupports methods 
  NS_IMETHOD QueryInterface(const nsIID &aIID, 
                            void **aResult); 
  NS_IMETHOD_(nsrefcnt) AddRef(void); 
  NS_IMETHOD_(nsrefcnt) Release(void); 

// nsISample method 
  NS_IMETHOD Hello(); 
}; 

/* 
 * nsSampleFactory Declaration 
 */ 

class nsSampleFactory: public nsIFactory { 
private: 
  nsrefcnt mRefCnt; 

public: 
  nsSampleFactory(); 
  ~nsSampleFactory(); 

// nsISupports methods 
  NS_IMETHOD QueryInterface(const nsIID &aIID, 
                            void **aResult); 
  NS_IMETHOD_(nsrefcnt) AddRef(void); 
  NS_IMETHOD_(nsrefcnt) Release(void); 

// nsIFactory methods 
  NS_IMETHOD CreateInstance(nsISupports *aOuter, 
                                  const nsIID &aIID, 
                                  void **aResult); 

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

/* 
 * nsSample Implemtation 
 */ 

nsSample::nsSample() 

  mRefCnt = 0; 
  PR_AtomicIncrement(&gInstanceCnt);

nsSample::~nsSample() 

// assert(mRefCnt == 0); 
  PR_AtomicDecrement(&gInstanceCnt);

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; 
  } 

  // Always NULL result, in case of failure 
  *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; // Don't access mRefCnt after deleting! 
  } 
  return mRefCnt; 

/* 
 * nsSampleFactory Implementation 
 */

nsSampleFactory::nsSampleFactory() 

  mRefCnt = 0; 
  PR_AtomicIncrement(&gInstanceCnt);

nsSampleFactory::~nsSampleFactory() 

// assert(mRefCnt == 0); 
  PR_AtomicDecrement(&gInstanceCnt);

NS_IMETHODIMP nsSampleFactory::QueryInterface(const nsIID &aIID, 
                                         void **aResult) 

  if (aResult == NULL) { 
    return NS_ERROR_NULL_POINTER; 
  } 

  // Always NULL result, in case of failure 
  *aResult = NULL; 

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

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

  AddRef(); // Increase reference count for caller 
  return NS_OK; 

NS_IMETHODIMP_(nsrefcnt) nsSampleFactory::AddRef() 

  return ++mRefCnt; 

NS_IMETHODIMP_(nsrefcnt) nsSampleFactory::Release() 

  if (--mRefCnt == 0) { 
    delete this; 
    return 0; // Don't access mRefCnt after deleting! 
  } 
  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) {
    // We didn't get the right interface, so clean up
    delete inst;
  }

  return res;
}

/*
 * Exported functions
 */

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) 
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }

  *aResult = NULL;

  nsISupports *inst;

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

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

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

  if (res != NS_OK) {
    // We didn't get the right interface, so clean up
    delete inst;
  }

  return res;
}

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

今回は、直接ファクトリーをコールする替わりに、SRepository::CreateInstance()をコールしています。ファクトリーの登録自体が何だかの方法で済んでいることを前提にしています。

File: 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()メソッドを使って手動でDLLを登録できます。(この例としては、nsSample2.cppを参照してください。)

DLLは、自己登録、登録削除の為に、2つの追加関数をエクスポートできます。

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;
}

これはQueryInterface()、CreateInstance()、NS_NewX()により返されるインターフェースにも当てはまることで、使い終わったらメモリリークを回避する為にそれに対しRelease()をコールしなければならないことを忘れないでください。

入力引き数とローカルポインタ

入力引き数として関数に渡されるインターフェースとそのインターフェースポインタのローカルコピーは、その関数の生存期間のみ有効とされ、AddRef()をコールする必要はありません。

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

  if (aFoo1->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(実際にはOLE 2)は、COMを使用した最初の試みでした。

なぜC++なのでしょう?

C++はインターフェースを実装する為の最も簡単なメカニズムを提供します。関数テーブルやマクロを使用して手動でインターフェースを構築することも出来ますが、それはC++のコンパイラがあなたに代わって自動的にやってくれることを、単に手動でやっているだけです。

Cは使えますか?

Cは、インターフェース以外のところなら、どこにでも使えます。Cでインターフェースを宣言するメカニズムもありますが、それらは固執的でコンパイラに依存します。我々はこれをなるたけ軽量化しようとしています。

COMじゃだめなんですか?

現状COMが広範に利用できる環境をサポートしているプラットフォームは、Windowsだけです。MicrosoftはMacintoshの為のCOM拡張機能を出荷していますが、それは一般的にInternet ExplorerやMicrosoft Officeと共にのみインストールされます。UNIXでのCOMのサポートは皆無です。

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

これはクロスプラットフォーム的解決ではなく、クロスプラットフォームこそ我々が必要としているものです。我々は、自分たちのインターフェースが、COMをサポートするプラットフォームに於いてCOMとの互換を保つよう、多大の努力をしていくので、クロスプラットフォームは大丈夫でしょう。まだ確約はできませんが。

大きな違いは何?

MicrosoftのMIDLコンパイラの代わりに、我々はCORBA互換のIDLコンパイラであるXPIDLを使用しています。これは、C++のヘッダーを作成する際、NSPRのデータ型を出力します。またこれは、Microsoftのものとは互換のないタイプライブラリを生成します。TLBフォーマットです。XPCOMは、JavaScriptなどの他の言語がXPCOMオブジェクトを実装したりコールしたりできるようにする為、これらのタイプライブラリを使用します。我々はまた、typelibとNSPRのイベントキューを使って、クロススレッドのプロクシーコールを行います。

Microsoftは、COMの為の膨大なサポートインフラを提供しています。この技術はWindowsに内臓されていますが、他のほとんどのプラットフォームにはありません。この技術はMicrosoftからライセンスを受けることができますが、明らかな理由により、我々はこれを行ないません。この技術の重要な各要素の我々の等価機能は、必要に応じて開発されていきます。

リンク

改定履歴

  • Feb 25, 1998, Created
  • Oct 19, 1998, Dusted off momentarily
  • Oct 10, 1999, Added comments about XPIDL, language-independantness

Copyright © 1998 Netscape Communications Corporation
Copyright © 1998-2001 The Mozilla Organization.
Last modified February 25, 2000.
Document History.