Saturday, December 8, 2012

"Black Hole" In IExtensionApplication Interface

There is one issue with CommandClass in AutoCAD .NET API, which implements IExtensionApplication interface: if the code inside Initialize() causes exception, the loaded assembly (managed DLL) that contains the class of IExtensionApplication will be rendered useless (that is, if the DLL also defines some command methods, those commands will not be available). When this happens, AutoCAD just silently swallow the exception and tells nothing to user, just like something falls into a "black hole".

Knowing this behaviour of IExtensionApplication.Initialize() is very important to AutoCAD .NET API programming, yet I did not see this was documented when I started with .NET API with AutoCAD 2006. I fell into this a few times in my early stage of doing .NET API programming and spent quite some time trying to figure out what was wrong.  I bet many programmers who worked with AutoCAD .NET API also came across this.

The way to prevent your DLL from being sucked into this "black hole" is to enclose your code in IExtensionApplication.Initialize() in a try{...}catch{...} block, so that the Initialize() method can run to its end of completion. Better yet, one can use Editor.WriteMessage() in the catch{...} block to write some message to the command line, so that user may get some clue that something may have gone wrong.

In my practice, I created a DLL project template that contains a blank Initialize() implementation. Inside the Initialize() method, I have an empty try{...}catch{...} block in it. This way, I can always start coding in Initialize() without forgetting to use try{...}catch{...}.

I have never used the AutoCAD .NET project wizard (and do not feel I need it any time soon) and I am wondering if the code structure created by the wizard does this (adding try{...}catch{...} in the Initialize()). I hope it does. We can see people seek help in various AutoCAD programming forums for "unknown command", which is actually caused by this black hole of IExtensionApplication.Initialize() method.

Recently I came across an interesting issue when I was reviewing and testing a piece of code from someone. The assembly DLL contains a CommandClass with a couple of CommandMethods and the CommandClass also implements the IExtensionApplication interface. In the Initialize() method there is only one line of code

Editor.WriteMessage("\nMy app is loading...");

Yet, upon "NETLOAD" the DLL, the commands defined in this DLL are not available, I got "Unknown command". If I removed the IExtensionApplication implementation, the commands defined by the CommandMethod then worked as expected. This is typical behaviour if something is gone wrong in IExtensionApplication.Initialize() without being caught, to my knowledge and experience. However, as I mentioned, the code in Initialize() is so simple that it no way to cause any exception. I was puzzled.

Then I read the code from its very beginning again and again and finally something like this caught my eye:

Code Snippet
  1. public class MyCommands : IExtensionApplication
  2. {
  3.     private AcData _acData = new AcData();
  4.  
  5.     public void Initialize()
  6.     {
  7.         Document dwg = Application.DocumentManager.MdiActiveDocument;
  8.         Editor ed = dwg.Editor;
  9.         ed.WriteMessage("\nLoading MyCommand...");

It is the private member AcData, which is a user defined class, when being instantiated, throwing an exception beause of its quite complicated code logic.

One may ask why exception in instantiating a private member has anything to do with Initialize()? To understand this, we need to know how AutoCAD instantiates a CommandClass. Not like a standard class, where we instantiate an object of a class by "new", AutoCAD's CommandClass is instantiated by AutoCAD when its command defined by CommandMehod is called (I have an old post on this topic here). In this case, when the DLL is NETLOADed, the Initialize() is to be run right after the DLL is loaded into memory. However, since Initialize() is a method of the CommandClass (MyCommands), before the method being executed, AutoCAD has to instantiate the CommandClass first, thus the private member's constructor is called. If exception occurred in the "new AcData()" part, the Initialize() would not be succeeded, thus the "Unknown command" message later.

Form this incident, we can learn that it is not a good practice by instantiating a CommandClass' private member when declaring it, because a CommandClass' instantiation is triggered by its CommandMethod ( there can be multiple CommandClasses) or Initialize() (if the CommandClass extends on IExtensionApplication). It may also not a so good practice to have complicated code logic in the CommandClass' constructor, for the same reason. Common practice is to declare class member of reference type as null, and in CommandMethod, if the member is needed, test to see if it is null first, and instantiate it if needed. Something like:

Code Snippet
  1. public class MyCommands : IExtensionApplication
  2. {
  3.     private AcData _acData = null;
  4.  
  5.     [CommandMethod("DoWork")]
  6.     public void DoMyWork()
  7.     {
  8.         if (_acData == null)
  9.         {
  10.             _acData = new AcData();
  11.         }
  12.  
  13.         //Then Do something
  14.     }

2 comments:

Anonymous said...

You saved my day, man. Thanks :)

Anonymous said...

Be careful though! Since CAD2015 you cannot access the Editor in the initialize method. Rendering your black-hole solution to be a new black-hole...

I'm still working on the right solution...

Followers

About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.