ガベージ コレクション
アプリケーションで多くのオブジェクトをロードする場合、特に他のオブジェクトを参照するオブジェクトをロードする場合は、ロードしたオブジェクトをキャッシングすると、メモリの浪費につながることがある。
自動的なキャッシングが有効になっていると、デフォルトの場合と同様に、参照によってロードされるオブジェクトを含め、IDirectMusicLoader8::GetObject によってロードされるすべてのオブジェクトがキャッシングされる。たとえば、セグメントに対して GetObject を呼び出すと、そのセグメントにスクリプトへの参照が含まれている場合は、スクリプトもロードされてキャッシングされる。
ただし、IDirectMusicLoader8::ReleaseObject または IDirectMusicLoader8::ReleaseObjectByUnknownを呼び出すと、GetObject によってロードされたプライマリ オブジェクトだけがキャッシュから削除される。参照されたオブジェクトは、他のオブジェクトによって使われていなくても解放されない。
使用されていないオブジェクトをクリーンアップするには、IDirectMusicLoader8::CollectGarbageを呼び出す。このメソッドは、GetObject によって直接ロードされたオブジェクト、そのオブジェクトによって参照されるオブジェクトを除いて、すべてのオブジェクトをキャッシュから解放する。もう存在していない他のオブジェクトによって参照されているオブジェクトだけが解放される。CollectGarbage は、ローダーにおけるオブジェクトへの COM 参照を解放することによって、キャッシュからオブジェクトを消去する。この結果、オブジェクトの参照カウントが 0 になった場合は、オブジェクトは破棄され、そのメモリは再び利用可能になる。
まとめると、ロードされたオブジェクトが不要になったときにメモリ内に残らないようにするには、次の処理を行う必要がある。
- GetObject の呼び出しの対象になったオブジェクトに対して、ReleaseObject または ReleaseObjectByUnknown を呼び出す。
- CollectGarbage を呼び出して、間接的にロードされたオブジェクトに対するローダーの参照を解放する。
- アプリケーションが保持しているポインタに対して Release を呼び出す。
オブジェクトが互いに循環参照している場合は、処理が複雑になる。セグメントのスクリプト トラックがスクリプト オブジェクトへの参照を含み、このスクリプト オブジェクトはセグメントへの参照を含んでいるものとする。GetObject を呼び出してセグメントを直接ロードすると、スクリプトが間接的にロードされる。次に、ReleaseObject を使ってキャッシュからセグメントを解放し、アプリケーションにおけるセグメントへの参照に対して Release を呼び出す。スクリプト オブジェクトがセグメントへの COM 参照をまだ 1 つ保持しているので、このセグメントは存在し続ける。この場合、スクリプトはキャッシュ内の他のオブジェクトによって参照されないので、ガベージになる。ただし、特殊な処理を行わない場合、CollectGarbage はローダーにおけるスクリプトへの参照だけを解放する。このため、その参照カウントは 0 にならない。セグメントとスクリプトは互いに参照し続けるので、両方ともキャッシュから削除されているのに、メモリ内に存在し続ける。
この問題を回避するために、CollectGarbage はオブジェクトに対して内部メソッドを呼び出し、そのオブジェクトが他のオブジェクトへの参照を解放するように強制する。上の例では、スクリプトはセグメントへの参照を解放する。セグメントの参照カウントが 0 になり、セグメント自体を破棄するときに、セグメントはスクリプトへの参照を解放するので、ローダーがスクリプトへの参照を解放するときに、スクリプトはそれ自体を破棄できる。
しかし、ここでもう 1 つ問題がある。アプリケーションが、ローダーが知らないスクリプトへのインターフェイスを取得し、このポインタに対する Release の呼び出しを無視したとする。スクリプトは存在し続けるが、意図したとおりに動作しない場合がある。これは、セグメントへの参照が存在しないからである。このスクリプトでメソッドを呼び出すと、致命的なエラーが発生する場合がある。これを回避するために、CollectGarbage は、スクリプト オブジェクトのすべてのメソッドが DMUS_S_GARBAGE_COLLECTED を返すことを保証している。
この状況は、ほとんどのアプリケーションに影響しない。ただし、CollectGarbage によってキャッシュから消去されたオブジェクトでメソッドを呼び出すと、予期した結果にならない場合があることに注意する必要がある。
以下のサンプル関数では、ロードされたスクリプトにセグメントへの参照が含まれるものとする。この関数は、スクリプトでルーチンを呼び出した後、キャッシュからスクリプト オブジェクトを削除し、CollectGarbage を呼び出して参照されているセグメントを解放している。セグメントにスクリプトへの循環参照が含まれる場合は、スクリプトを破棄できるように参照が解放され、次にセグメントに対する最後の参照が解放されて、セグメントを破棄できるようになる。
void CallWhistle(IDirectMusicLoader8* pLoader, IDirectMusicPerformance8* pPerformance)
{
IDirectMusicScript8 *pScript;
WCHAR wszScript[MAX_PATH] = L"soundfx.spt";
pLoader->LoadObjectFromFile(CLSID_DirectMusicScript,
IID_IDirectMusicScript8,
wszScript, (void**)&pScript);
pScript->Init(pPerformance, NULL);
pScript->CallRoutine(L"Whistle", NULL);
pLoader->ReleaseObjectByUnknown(pScript);
pLoader->CollectGarbage();
pScript->Release();
}
参照