Microsoft DirectX 8.0 (C++)

COM オブジェクトの寿命の管理

オブジェクトが作成されると、システムによって必要なメモリ リソースが割り当てられる。必要がなくなったオブジェクトは破棄しなければならない。オブジェクトを破棄すれば、システムは、その分のメモリをほかの目的に利用できる。C++ オブジェクトでは、new および delete の各演算子を使ってオブジェクトの寿命を直接制御できる。COM では、オブジェクトを直接作成したり破棄することはできない。これは、同じオブジェクトを複数のアプリケーションが使用している可能性があるからである。複数のアプリケーションで使用されているオブジェクトを 1 つのアプリケーションが破棄すると、ほかのアプリケーションでは高い確率でエラーが発生する。COM では、「参照カウント」というシステムを使ってオブジェクトの寿命を制御している。

オブジェクトの参照カウントは、そのオブジェクトのいずれかのインターフェイスが要求された回数を示す。インターフェイスが要求されるたびに、参照カウントはインクリメントされる。アプリケーションが必要のなくなったインターフェイスを解放すると、参照カウントはデクリメントされる。オブジェクトは、参照カウントが 0 にならない限り、メモリ上に保持される。参照カウントが 0 になると、そのオブジェクト自体によって自動的に破棄される。オブジェクトの参照カウントについて知る必要はない。オブジェクトのインターフェイスの取得および解放を正しく行っている限り、オブジェクトの寿命は適切に制御される。

  COM プログラミングでは参照カウントを正しく処理することが非常に重要である。この処理を正しく行わないと、メモリ リークが生じやすくなる。COM プログラマが最も冒しやすいミスの 1 つが、インターフェイスの解放し忘れである。インターフェイスが解放されないと、参照カウントがいつまでも 0 にならず、オブジェクトは半永久的にメモリに残っていることになる。

参照カウントのインクリメントとデクリメント

新しいインターフェイス ポインタを取得するときは、必ず IUnknown::AddRef を呼び出して参照カウントをインクリメントする必要がある。ただし、通常、アプリケーションでこのメソッドを呼び出す必要はない。オブジェクト作成メソッドまたは IUnknown::QueryInterface を呼び出してインターフェイス ポインタを取得した場合は、オブジェクトによって自動的に参照カウントがインクリメントされる。しかし、既存のポインタをコピーするなど、その他の方法でインターフェイス ポインタを取得した場合は、IUnknown::AddRef を明示的に呼び出す必要がある。これを行わないと、元のインターフェイス ポインタを解放したときに、そのポインタのコピーを継続して使用する必要があっても、オブジェクトが破棄される場合がある。

参照カウントをインクリメントしたのがアプリケーションであるかオブジェクトであるかにかかわらず、すべてのインターフェイス ポインタはアプリケーションによって解放しなければならない。インターフェイス ポインタが不要になった場合は、IUnknown::Release を呼び出して参照カウントをデクリメントする。すべてのインターフェイス ポインタにおいて、初期化時に NULL に設定し、解放時に再び NULL に設定するのが一般的である。これによって、クリーンアップ コードで、すべてのインターフェイス コードをテストできる。NULL でないポインタはまだアクティブであり、アプリケーションを終了する前に解放する必要がある。

次のコードは、「追加のインターフェイスの要求」で説明したサンプルを、参照カウントの処理方法を示すために拡張したものである。

IDirectSoundBuffer8* pDSBPrimary = NULL;
IDirectSound3DListener8* pDSListener = NULL;
IDirectSound3DListener8* pDSListener2 = NULL;
...
//オブジェクトを作成し、追加のインターフェイスを取得する。
//オブジェクトによって参照カウントがインクリメントされる。
if(FAILED(hr = g_pDS->CreateSoundBuffer( &dsbd, &pDSBPrimary, NULL )))
  return hr;

if(FAILED(hr=pDSBPrimary->QueryInterface(IID_IDirectSound3DListener8,
                                           (LPVOID *)&pDSListener)))
  return hr;

//IDirectSound3DListener8 インターフェイス ポインタのコピーを作成する。
//AddRef を呼び出して参照カウントをインクリメントする。これによって、
//使用中のオブジェクトが破棄されるのを防ぐことができる。
pDSListener2 = pDSListener;
pDSListener2->AddRef();
...
//クリーンアップ コード。ポインタがまだアクティブであるかどうかを確認する。
//アクティブであれば、Release を呼び出してインターフェイスを解放する。
if(pDSBPrimary != NULL)
{
  pDSBPrimary->Release();
  pDSBPrimary = NULL;
}
if(pDSListener != NULL)
{
  pDSListener->Release();
  pDSListener = NULL;
}
if(pDSListener2 != NULL)
{
  pDSListener2->Release();
  pDSListener2 = NULL;
}