The RCW is also responsible for keeping the COM object alive as long as the RCW is being used. The RCW itself is garbage collected like any other managed object but internally it holds a reference on the COM object it wraps. When the RCW is garbage collected, the wrappers finalizer is called which, in turn, calls Release() on any interface pointers it’s caching. Garbage collection for a RCW is triggered under the same circumstances as collection for an NGWS object thereby allowing managed clients to treat a COM object like an NGWS object without worrying about COM reference counting.
Under most circumstance, the scenario described above would work just fine but in some cases it does present a problem. The RCW is automatically garbage collected when the last reference to the wrappers is destroyed. However, there are no guarantees about how long it will take for the RCW to be collected. In fact, it may be some time before the RCW is actually garbage collected. If the underlying COM object is holding resources opened (like a Conn connection) this can be a serious problem as the resources are left opened for prolonged periods. This problem is often referred to as the none-deterministic finalization problem.
Developers using RCW’s should be aware of none-deterministic problem and be prepared to guard against it, especially when the RCW wraps an object that has a large amount of resource overhead. An object that holds open a Conn connection would be a prime example.
One partial solution to this problem is to take on responsibility for explicitly release references to the underlying COM object. In the normal scenario, the RCW releases all references to the COM object when the wrapper is garbage collected, but you can explicitly release the references yourself rather than waiting for the garbage collector to do it. By passing the RCW to System.InteropServices.ReleaseComObject, you force the wrapper to call Release (one time) on any interfaces it’s caching. The code sample below shows how to explicitly release all references held by an RCW.
Baz() { Acme.Slingshot ss; // an RCW for a COM class ss = new Acme.Slingshot(); // create the COM object ss.shootRoadRunner (); // use it ReleaseComObject(ss); // explicitly release it }
This solution is does not working every case however. Consider the following managed code where the object co is a COM object:
class Baz() { void M1(Foo f1) { } void M2(Foo f2) { ReleaseComObject(f2) // ineffective release } }
In this case, the managed class Baz has two methods that take objects as parameters. If the Foo object passed to each of these methods is the same COM object, the call to ReleaseComObject may not be effective. When M1 is called, an RCW is created that wraps the COM object. When f1 goes out of scope at the end of M1, the RCW becomes ready for garbage collectible. The references held by the RCW on the COM object are not released until the collection takes place. If M2 is called before the collection happens, the RCW adds another reference to the underlying COM object to account for f2. When ReleaseComObject is called in M2 the RCW release one reference on the COM object but the second reference keeps the underlying object alive until the RCW is finally collected. The only way to be assured the ReleaseComObject will actually release the underlying object is to make the call before each RCW goes out of scope.