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.

Tuesday, May 6, 2008

ArcGIS Programming Trick: Adding a Legend to a Raster Renderer

In the previous entry on creating a DTED mosaic in the FalconView ArcGIS editor, I touched on creating a RasterStretchColorRampRenderer to symbolize the output of the DTED mosaic. This post is a short extension of that entry. One detail that we discovered in implementing the renderer is that the color ramp did not draw on the table of contents legend. It turns out that there is a trick to getting the color ramp to draw. As far as I can tell, this trick is undocumented on EDN, but we learned it through the help of an ArcGIS guru at ESRI.

There are two things that need to happen for the legend to be created, neither of which we were doing. After instantiating the RasterStretchColorRampRenderer, it is important to associate the raster we are rendering with the renderer and to call Update on the renderer immediately thereafter. The code in the previous blog entry must be changed to look like this (the highlighted code is new):

// Create a renderer to be used with the raster layer

IRasterStretchColorRampRenderer pRasterStretchColorRampRenderer = new RasterStretchColorRampRendererClass();
IRasterRenderer pRasterRenderer = (IRasterRenderer)pRasterStretchColorRampRenderer;

pRasterRenderer.Raster = pRasterLayer.Raster;
pRasterRenderer.Update(); // the call of Update right after setting Raster is important

pRasterStretchColorRampRenderer.BandIndex = 0;
//...


In this case pRasterLayer is created in the same way as before, except we moved the block of code that creates the layer earlier in the raster creation procedure.