Over the weekend I have been working on a project that involves a file processor service. The purpose of the service is to watch one or more folders and dispatch the incoming files to a corresponding file processor. One of the processors uses Captaris Alchemy, a document management repository, to store the files.
The way Alchemy can be used to process files from .NET is to invoke the entire client application. With COM Automation you can steer the application to insert a file in an Alchemy database. To do this from managed code you need to create a COM Interop assembly by referencing AuApiX.dll (Alchemy Objects Library 2.0). The resulting assembly will be called Interop.Alchemy.dll.
The main item to use in the interop code is the Application object, which is used to find databases. The code is reasonably simple:
During further development I quickly found out that there was no direct way to close database or connections to the Alchemy server. The Alchemy API does not have any methods for that. Setting the reference to Nothing should take care of that. But, it doesn’t! The processor service keeps creating more and more Application objects, so more and more connections to the server are opened and not closed in a timely fashion. However, every Visual Basic 6 example out on the Internet suggests that the correct way to close the application and connection is to set the reference to Nothing. How come?
This is where the difference between the lifetimes of COM objects and the Common Language Runtime (CLR) objects comes in. The VB6 garbage collector releases references as soon as a variable goes out of scope. This way you can forget to do so yourself. COM uses a reference counting mechanism to determine the lifetimes of objects. As soon as a reference is dropped, the reference count is decremented by one by a call to Release method of the IUnknown interface (where a call to AddRef was made when the COM object was first referenced, which caused the reference count to be incremented by one). If that counter reaches zero for a particular COM object, it is disposed immediately.
The CLR has a garbage collector (GC) that does its work at a certain point in time that cannot be determined exactly. The algorithm to trigger a garbage collection is pretty much undocumented. This shouldn’t bother you, as you better not interfere with the scheduling. Once the GC starts, it will look for unreachable objects and will destroy these. (Side note: if they have a destructor, they are put on a finalizer queue, where the finalizer thread will eventually call the Finalize method of the object. The remaining object will then be destroyed on the next pass of the GC). Unreachable objects are those that do not have a root reference, such as a static field of a class or a variable that is in scope.
What does the garbage collector have to do with the reference counting mechanism of COM classes? Well, when you use COM interop in .NET you get a .NET proxy object that handles all the IUnknown calls to QueryInterface, AddRef and Release to the real COM object. If you set the reference to the COM proxy to Nothing you will make that object eligible for garbage collection. But, … the actual COM object behind the proxy is not destroyed until the GC does an actual collection. And this may take a long time.
In the case of Alchemy this means that more and more Application objects are lingering around, keeping connections open. Not what you want. So, you need to force the .NET COM proxy to release the COM object. You can do so by using the System.Runtime.InteropServices.Marshal class. It has a shared (static) method called ReleaseComObject, that you can pass a reference to a COM proxy. The method will decrement the reference count of the COM object by calling IUnknown.Release. The COM object terminates if the count reaches zero and with no other references this should be instantaneous. The proxy will be alive after this, but won’t hold the COM object alive anymore.
The code above is what you need to fix the problem. Simple, but really essential if you want to have more control over the lifetime of COM objects from managed code.