オブジェクトの有効期間の管理
COM インターフェイスには、まだ言及していない規則があります。 すべての COM インターフェイスは、 IUnknown という名前のインターフェイスから直接または間接的に継承する必要があります。 このインターフェイスには、すべての COM オブジェクトでサポートする必要があるいくつかのベースライン機能が用意されています。
IUnknown インターフェイスは、次の 3 つのメソッドを定義します。
QueryInterface メソッドを使用すると、実行時にオブジェクトの機能に対してクエリを実行できます。 これについては、次のトピック「 インターフェイスのオブジェクトを要求する」で詳しく説明します。 AddRef メソッドと Release メソッドは、オブジェクトの有効期間を制御するために使用されます。 これは、このトピックの主題です。
参照カウント
プログラムが他に何をする可能性がある場合でも、ある時点でリソースを割り当てて解放します。 リソースの割り当ては簡単です。 リソースを解放するタイミングを知ることは困難です。特に、リソースの有効期間が現在のスコープを超えている場合は困難です。 この問題は COM に固有ではありません。 ヒープ メモリを割り当てるプログラムは、同じ問題を解決する必要があります。 たとえば、C++ では自動デストラクターが使用され、C# と Java ではガベージ コレクションが使用されます。 COM では、 参照カウントと呼ばれるアプローチが使用されます。
すべての COM オブジェクトは内部カウントを保持します。 これは参照カウントと呼ばれます。 参照カウントは、現在アクティブになっているオブジェクトへの参照の数を追跡します。 参照の数が 0 に減少すると、オブジェクトはそれ自体を削除します。 最後の部分は繰り返す価値があります:オブジェクトはそれ自体を削除します。 プログラムはオブジェクトを明示的に削除しません。
参照カウントの規則を次に示します。
- オブジェクトが最初に作成されると、その参照カウントは 1 になります。 この時点で、プログラムは オブジェクトへの 1 つのポインターを持ちます。
- プログラムは、ポインターを複製 (コピー) することで、新しい参照を作成できます。 ポインターをコピーするときは、オブジェクトの AddRef メソッドを呼び出す必要があります。 このメソッドは、参照カウントを 1 ずつインクリメントします。
- オブジェクトへのポインターの使用が完了したら、 Release を呼び出す必要があります。 Release メソッドは、参照カウントを 1 ずつデクリメントします。 また、ポインターも無効になります。 Release を呼び出した後は、ポインターをもう一度使用しないでください。 (同じオブジェクトへの他のポインターがある場合は、これらのポインターを引き続き使用できます)。
- すべてのポインターで Release を 呼び出すと、オブジェクトのオブジェクト参照カウントが 0 に達し、オブジェクト自体が削除されます。
次の図は、単純だが一般的なケースを示しています。
プログラムは オブジェクトを作成し、オブジェクトへのポインター (p) を格納します。 この時点で、参照カウントは 1 です。 ポインターを使用してプログラムが終了すると、Release が呼び出 されます。 参照カウントが 0 にデクリメントされ、オブジェクト自体が削除されます。 現在 、p は無効です。 それ以降のメソッド呼び出しで p を使用するとエラーになります。
次の図は、より複雑な例を示しています。
ここで、プログラムはオブジェクトを作成し、前と同様にポインター p を格納します。 次に、プログラムは p を新しい変数 q にコピー します。 この時点で、プログラムは AddRef を 呼び出して参照カウントをインクリメントする必要があります。 参照カウントは 2 になり、オブジェクトへの有効なポインターが 2 つあります。 ここで、プログラムが p を使用して終了したとします。 プログラムは Release を呼び出し、参照カウントは 1 になり、 p は無効になります。 ただし、 q は引き続き有効です。 その後、プログラムは q の使用を終了 します。 そのため、 Release を再度呼び出します。 参照カウントが 0 になり、オブジェクト自体が削除されます。
なぜプログラムが pをコピーするのか疑問に思うかもしれません。 2 つのメイン理由があります。最初に、リストなどのデータ構造にポインターを格納できます。 次に、元の変数の現在のスコープを超えてポインターを保持することが必要な場合があります。 したがって、スコープが広い新しい変数にコピーします。
参照カウントの利点の 1 つは、さまざまなコード パスを調整してオブジェクトを削除しなくても、コードの異なるセクション間でポインターを共有できることです。 代わりに、各コード パスは、 オブジェクトを使用してコード パスが完了したときに Release を呼び出すだけです。 オブジェクトは、適切なタイミングで自身の削除を処理します。
例
[ 開く] ダイアログ ボックスの例 のコードをもう一度次に示します。
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED |
COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr))
{
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));
if (SUCCEEDED(hr))
{
hr = pFileOpen->Show(NULL);
if (SUCCEEDED(hr))
{
IShellItem *pItem;
hr = pFileOpen->GetResult(&pItem);
if (SUCCEEDED(hr))
{
PWSTR pszFilePath;
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
if (SUCCEEDED(hr))
{
MessageBox(NULL, pszFilePath, L"File Path", MB_OK);
CoTaskMemFree(pszFilePath);
}
pItem->Release();
}
}
pFileOpen->Release();
}
CoUninitialize();
}
参照カウントは、このコードの 2 か所で行われます。 まず、プログラムが共通項目ダイアログ オブジェクトを正常に作成した場合は、pFileOpen ポインターで Release を呼び出す必要があります。
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));
if (SUCCEEDED(hr))
{
// ...
pFileOpen->Release();
}
次に、GetResult メソッドが IShellItem インターフェイスへのポインターを返す場合、プログラムは pItem ポインターで Release を呼び出す必要があります。
hr = pFileOpen->GetResult(&pItem);
if (SUCCEEDED(hr))
{
// ...
pItem->Release();
}
どちらの場合も、 Release 呼び出しは、ポインターがスコープ外に出る前に最後に行われることに注意してください。 また、HRESULT の成功をテストした後にのみ Release が呼び出されることに注意してください。 たとえば、 CoCreateInstance の呼び出しが失敗した場合、 pFileOpen ポインターは無効です。 したがって、ポインターで Release を呼び出すとエラーになります。