Microsoft DirectX 8.1 (C++) |
Caching of loaded objects can lead to wasted memory when an application loads many objects, particularly objects that reference other objects.
When automatic caching is enabled, as it is by default, every object loaded by IDirectMusicLoader8::GetObject is cached, including objects that are loaded by reference. For example, if you call GetObject on a segment, and that segment contains a reference to a script, the script is loaded and cached as well.
When you call IDirectMusicLoader8::ReleaseObject or IDirectMusicLoader8::ReleaseObjectByUnknown, however, only the primary object that was loaded by GetObject is removed from the cache. Referenced objects are not released, regardless of whether they are still in use by other objects.
In order to clean up objects that are not in use, call IDirectMusicLoader8::CollectGarbage. This method releases all objects from the cache except objects directly loaded by GetObject and objects referenced by them. CollectGarbage clears an object from the cache by releasing the loader's COM reference to the object. If the object's reference count drops to zero as a result, the object destroys itself, thus making its memory available again.
In summary, to ensure that loaded objects do not remain in memory when no longer needed, you need to do the following:
A complication arises when objects have circular references to one another. Suppose a segment contains a reference, by way of its script track, to a script object, and the script contains a reference to the segment. You load the segment directly by calling GetObject, and the script is loaded indirectly. Then you release the segment from the cache by using ReleaseObject, and call Release on your application's reference to it. The segment continues to exist because there is still one COM reference to it, which is held by the script object. The script is now garbage, because it is not referenced by any other object in the cache. Without taking special measures, however, CollectGarbage could only release the loader's reference to the script; therefore its reference count would not drop to zero. The segment and script would continue to be referenced by one another, and although both were removed from the cache, they would both continue to exist in memory.
To avoid this problem, CollectGarbage calls an internal method on an object that forces the object to release its references to other objects. In the example above, it causes the script to release its reference to the segment. The segment's reference count drops to zero, and in the course of destroying itself, the segment releases its reference to the script, thus allowing the script to destroy itself when the loader releases its reference.
There is one more complication, however. Suppose the application has obtained an interface to the script that the loader knows nothing about, and neglects to call Release on this pointer. The script continues to exist, but it might not be able to behave as it should, because it no longer has a reference to the segment. Calling a method on the script could lead to a fatal error. To prevent this, CollectGarbage ensures that all methods on the script return DMUS_S_GARBAGE_COLLECTED.
This scenario does not affect most applications. However, you should be aware that calling a method on an object that has been cleared from the cache by CollectGarbage might not yield the desired result.
The following sample code, where g_pLoader is an IDirectMusicLoader8 interface and g_pPerformance is an IDirectMusicPerformance8 interface, loads a script that contains a reference to a segment. After calling a routine in the script, the example removes the script object from the cache and then calls CollectGarbage to free the segment object. If the segment contains a reference to the script, this is released so that the script can be destroyed, in turn releasing the segment and allowing it to be destroyed.
// Load script and call routine.
IDirectMusicScript8 *pScript;
g_pLoader->GetObject(scriptdesc, IID_IDirectMusicScript8, &pScript);
pScript->Init(g_pPerformance, NULL);
pScript->CallRoutine(L”DoorSlam”, NULL);
// Release script object and collect garbage.
pLoader->ReleaseObjectByUnknown(pScript);
pLoader->CollectGarbage();
pScript->Release();