Friday, May 23, 2008

Ensuring Release of COM Objects in a .NET FalconView Plugin

As any experienced FalconView developer knows, FalconView relies on Microsoft COM technology for our plugin APIs. To write a FalconView plugin, you implement a member of the ILayerEditor interface family and update the registry to tell FalconView the class to instantiate for the plugin. It's not hard to do once you've seen it done once. The FalconView wiki has a wealth of information on writing plugins.

FalconView 4.1, which is just now hitting the streets, includes the first .NET components ever to ship along with FalconView, including the 4.1 ArcGIS overlay. When I wrote the GIS overlay for FalconView 4.1, I discovered that the stack would become corrupted whenever .NET did its garbage collection. This would lead to instability which eventually made really bad things happen, such as FalconView disappearing from under a user's nose (one exception you may see is ReportAvOnComRelease). After some research, we discovered that this was because .NET was trying to garbage collect COM objects that had already fallen out of scope in FalconView. The trick to fixing the problem - and if you're writing a .NET plugin for FalconView 4.1 this is very important to pay attention to - is to release explicitly the FalconView COM objects once you're finished with them. The following code block, by making use of finally clauses, provides an example of how to ensure the release of your COM objects, even if your routine exits early via a premature return statement or an exception. (See the Hello World example on the wiki for the full project containing this example.)

public void OnDraw(int layer_handle, object pActiveMapProj, int bDrawBeforeLayerObjects)
{
try
{
if (bDrawBeforeLayerObjects == 1)
{
IActiveMapProj pActiveMap = (IActiveMapProj)pActiveMapProj;

try
{
ISettableMapProj pSettableMapProj;
pActiveMap.GetSettableMapProj(out pSettableMapProj);

if (pSettableMapProj != null)
{
try
{
IGraphicsContext pGraphicsContext;
pActiveMap.GetGraphicsContext(out pGraphicsContext);

if (pGraphicsContext != null)
{
try
{
// ... Draw Here ...
}
finally
{
Marshal.ReleaseComObject(pGraphicsContext);
}
}
}
finally
{
Marshal.ReleaseComObject(pSettableMapProj);
}
}
}
finally
{
Marshal.ReleaseComObject(pActiveMap);
}
}
}
finally
{
Marshal.ReleaseComObject(pActiveMapProj);
}
}


Note the use of the nested try ... catch ... finally blocks. This ensures that the COM objects will be released before execution is returned to FalconView.

(In this example, I show each try block explicitly, which has the benefit of constraining the scope of objects. One could make this code more readable by avoiding the nested blocks of code and containing each Marshal.ReleaseComObject call in one big finally statement. As this big finally block could be entered from any point in the try block, this would simply require null reference testing on each COM reference before attempting to release it.)

For developers writing .NET plugins to FalconView 4.2, which is nearly ready for beta release, there is same good news here. The lead FalconView technical guru just informed me this week that FalconView will manage COM objects on the heap, paying full attention to reference counting to prevent premature disposal of the objects. Though this means that explicitly releasing COM objects in you .NET code is no longer required, it is still a nice idea to do so as it allows FalconView to dispose the object without having to wait on .NET garbage collection.

No comments: