Storing custom AutoCAD application settings in the Registry using .NET

Thanks to Sreekar Devatha, Gopinath Taget & Jeremy Tammik (from DevTech India, Americas and Europe, respectively) for contributing to my knowledge in this area over the last few months (whether they knew they were doing so, or not :-).

This post shows how to make use of a handy interface inside AutoCAD to place custom settings in the Registry and how to then read them back. The code is very simple: you simply open up the current profile and then access/modify your hierarchy of setting beneath it. I've used a Registered Developer Symbol (RDS) to prefix the section of the Registry directly beneath the profile, to avoid conflicts with other applications.

There are other ways of saving more complex settings to the Registry: in a future post I'll go more in-depth with the System.Configuration namespace (especially how to implement your own System.Configuration.ConfigurationSettings class and save it to the Registry).

Here's the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.EditorInput;


namespace ApplicationSettings

{

  public class Commands

  {

    // We're using our Registered Developer Symbol (RDS)

    //  TTIF == Through the Interface

    // for the section name.

    // The entries beneath don't need this.


    const string sectionName = "TTIFSettings";

    const string intProperty = "TestInteger";

    const string doubleProperty = "TestDouble";

    const string stringProperty = "TestString";


    [CommandMethod("ATR")]

    static public void AddToRegistry()

    {

      IConfigurationSection con =

        Application.UserConfigurationManager.OpenCurrentProfile();

      using (con)

      {

        IConfigurationSection sec =

          con.CreateSubsection(sectionName);


        using (sec)

        {

          sec.WriteProperty(intProperty, 1);

          sec.WriteProperty(doubleProperty, 2.0);

          sec.WriteProperty(stringProperty, "Hello");

        }

      }

    }


    [CommandMethod("RFR")]

    static public void RetrieveFromRegistry()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;


      IConfigurationSection prf =

        Application.UserConfigurationManager.OpenCurrentProfile();


      using (prf)

      {

        if (prf.ContainsSubsection(sectionName))

        {

          IConfigurationSection sec =

            prf.OpenSubsection(sectionName);


          using (sec)

          {

            double doubleValue =

              (double)sec.ReadProperty(doubleProperty, 0.0);

            string stringValue =

              (string)sec.ReadProperty(stringProperty, "");

            int intValue =

              (int)sec.ReadProperty(intProperty, 0);

            object defValue =

              sec.ReadProperty("NotThere", 3.142);


            ed.WriteMessage("\nInt value: " + intValue);

            ed.WriteMessage("\nDouble value: " + doubleValue);

            ed.WriteMessage("\nString value: " + stringValue);

            ed.WriteMessage("\nNon-existent value: " + defValue);

          }

        }

      }

    }

  }

}

Here's what we see when we run the RFR command (after having run the ATR beforehand, whether in the same session or a previous one):

Command: RFR

Int value: 1

Double value: 2

String value: Hello

Non-existent value: 3.142

And here are the contents of our new section of the Registry:

Custom application settings in the Registry

The observant among you will notice I've switched across to Vista (having just received a new machine). So far I've actually enjoyed using it, having disabled UAC within the first few minutes of getting it. :-)

May 5, 2008 in AutoCAD, AutoCAD .NET, Runtime | Permalink | Comments (8) | TrackBack

Getting the list of .NET-defined commands in AutoCAD

Here's an interesting question that came in from Kerry Brown:

Is there a way to determine the names of Commands loaded into Acad from assemblies ... either a global list or a list associated with a specific assembly ... or both :-)

I managed to put some code together to do this (although I needed to look into how AutoCAD does it to get some of the finer points). I chose to implement two types of command - one that gets the commands for all the loaded assemblies, and one that works for just the currently-executing assembly. The first one is quite slow, though - it takes time to query every loaded assembly - so I added a command that only queries assemblies with explicit CommandClass attributes.

I didn't write a command to get the commands defined by a specified assembly - that's been left as an exercise for the reader. :-)

Here is the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using System;

using System.Reflection;

using System.Collections.Specialized;


namespace GetLoadedCommands

{

  public class Commands

  {

    [CommandMethod("TC")]

    static public void ListCommandsFromThisAssembly()

    {

      // Just get the commands for this assembly


      DocumentCollection dm =

        Application.DocumentManager;

      Editor ed =

        dm.MdiActiveDocument.Editor;


      Assembly asm =

        Assembly.GetExecutingAssembly();

      string[] cmds = GetCommands(asm, false);

      foreach (string cmd in cmds)

      {

        ed.WriteMessage(cmd + "\n");

      }

    }


    [CommandMethod("LCM")]

    static public void ListMarkedCommands()

    {

      // Get the commands for all assemblies,

      //  but only those with explicit

      // CommandClass attributes (much quicker)


      StringCollection cmds = new StringCollection();

      DocumentCollection dm =

        Application.DocumentManager;

      Editor ed =

        dm.MdiActiveDocument.Editor;


      Assembly[] asms =

        AppDomain.CurrentDomain.GetAssemblies();

      foreach (Assembly asm in asms)

      {

        cmds.AddRange(GetCommands(asm, true));

      }

      foreach (string cmd in cmds)

      {

        ed.WriteMessage(cmd + "\n");

      }

    }


    [CommandMethod("LC")]

    static public void ListCommands()

    {

      // Get the commands for all assemblies,

      // marked or otherwise (much slower)


      StringCollection cmds = new StringCollection();

      DocumentCollection dm =

        Application.DocumentManager;

      Editor ed =

        dm.MdiActiveDocument.Editor;


      Assembly[] asms =

        AppDomain.CurrentDomain.GetAssemblies();

      foreach (Assembly asm in asms)

      {

        cmds.AddRange(GetCommands(asm, false));

      }

      foreach (string cmd in cmds)

      {

        ed.WriteMessage(cmd + "\n");

      }

    }


    private static string[] GetCommands(

      Assembly asm,

      bool markedOnly

    )

    {

      StringCollection sc = new StringCollection();

      object[] objs =

        asm.GetCustomAttributes(

          typeof(CommandClassAttribute),

          true

        );

      Type[] tps;

      int numTypes = objs.Length;

      if (numTypes > 0)

      {

        tps = new Type[numTypes];

        for(int i=0; i < numTypes; i++)

        {

          CommandClassAttribute cca =

            objs[i] as CommandClassAttribute;

          if (cca != null)

          {

            tps[i] = cca.Type;

          }

        }

      }

      else

      {

        // If we're only looking for specifically

        // marked CommandClasses, then use an

        // empty list

        if (markedOnly)

          tps = new Type[0];

        else

          tps = asm.GetExportedTypes();

      }


      foreach (Type tp in tps)

      {

        MethodInfo[] meths = tp.GetMethods();

        foreach (MethodInfo meth in meths)

        {

          objs =

            meth.GetCustomAttributes(

              typeof(CommandMethodAttribute),

              true

            );

          foreach (object obj in objs)

          {

            CommandMethodAttribute attb =

              (CommandMethodAttribute)obj;

            sc.Add(attb.GlobalName);

          }

        }

      }

      string[] ret = new string[sc.Count];

      sc.CopyTo(ret,0);

      return ret;

    }

  }

}

And here's what happens when you run the various commands:

Command: TC

TC

LCM

LC


Command: LC

layer

TC

LCM

LC


Command: LCM

layer

Note: you'll see that our own module's command are not listed by LCM... if you add the CommandClass attribute, as mentioned in this earlier post, then they will be:

[assembly:

  CommandClass(

    typeof(GetLoadedCommands.Commands)

  )

]

March 15, 2007 in AutoCAD, AutoCAD .NET, Commands, Runtime | Permalink | Comments (2) | TrackBack

Finding the location of a .NET module

A quick pre-Thanksgiving tip that came from an internal discussion today: how to find the location of a .NET module (meaning the currently executing assembly).

Two techniques were identified:

  1. Identify the current assembly by asking where one of its types is defined
  2. Use the Assembly.GetExecutingAssembly() to get the assembly from where the current code is executing

Here's the C# code showing the two techniques:

using System;

using System.Reflection;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;


namespace AssemblyLocationTest

{

  public class AssemblyCmds

  {

    [CommandMethod("LOC")]

    public void GetModuleLocation()

    {

      Editor ed =

        Application.DocumentManager.MdiActiveDocument.Editor;


      // First technique

      Type type = typeof(AssemblyCmds);

      Assembly asm1 = type.Assembly;

      ed.WriteMessage(

        "\nAssembly location (1) is: "

        + asm1.Location

      );


      // Second technique

      Assembly asm2 = Assembly.GetExecutingAssembly();

      ed.WriteMessage(

        "\nAssembly location (2) is: "

        + asm2.Location

      );

    }

  }

}

And here are the results, just to show they return the same thing:

Command: loc

Assembly location (1) is: C:\temp\MyAssembly.dll

Assembly location (2) is: C:\temp\MyAssembly.dll

November 22, 2006 in AutoCAD, AutoCAD .NET, Runtime | Permalink | Comments (0) | TrackBack

Blocking AutoCAD commands from .NET

Thanks to Viru Aithal from the DevTech team in India team for this idea (he reminded me of this technique in a recent ADN support request he handled).

A quick disclaimer: the technique shown in this entry could really confuse your users, if implemented with inadequate care. Please use it for good, not evil.

I talked at some length in previous posts about MDI in AutoCAD, and how various commands lock documents when they need to work on them. When commands try to lock (or unlock) a document, an event gets fired. You can respond to this event in your AutoCAD .NET module, asking AutoCAD to veto the operation requesting the lock.

For some general background on events in .NET, I’d suggest checking out this information on MSDN, both from the Developer’s Guide and from MSDN Magazine. To see how they work in AutoCAD, take a look at the EventsWatcher sample on the ObjectARX SDK, under samples/dotNet/EventsWatcher. This is a great way to see what information is provided via AutoCAD events.

It seems that almost every command causes the DocumentLockModeChanged event to be fired – I would say every command, but I can imagine there being commands that don’t trigger it, even if the technique appears to work even for commands that have no reason to lock the current document, such as HELP.

As you can imagine, having something block commands that a user expects to use could result in a serious drop in productivity, but there are genuine cases where you might have a legitimate need to disable certain product functionality, such as to drive your users through a more automated approach to performing the same task. This is not supposed to act as a fully-fledged security layer – an advanced user could easily prevent a module from being loaded, which invalidates this technique – but it could still be used to help stop the majority of users from doing something that might (for instance) break the drawing standards implemented in their department.

So, let’s take a look at what needs to happen to define and register our event handler.

Firstly, we must declare a callback function somewhere in our code that we will register as an event handler. In this case we’re going to respond to the DocumentLockModeChanged event, and so will take as one of our arguments an object of type DocumentLockModeChangedEventArgs:

VB.NET:

Private Sub vetoLineCommand (ByVal sender As Object, _

  ByVal e As DocumentLockModeChangedEventArgs)

  If (e.GlobalCommandName = "LINE") Then

    e.Veto()

  End If

End Sub

C#:

private void vetoLineCommand(

  object sender,

  DocumentLockModeChangedEventArgs e)

{

  if (e.GlobalCommandName == "LINE")

  {

    e.Veto();

  }

}

This callback function, named vetoLineCommand(), simply checks whether the command that is requesting the change in document lock mode is the LINE command, and, if so, it then vetoes it. In our more complete sample, later on, we’ll maintain a list of commands to veto, which can be manipulated by the user using come commands we define (you will not want to do this in your application – this is for your own testing during development).

Next we need to register the command. This event belongs to the DocumentManager object, but the technique for registering events is different between VB.NET and C#. In VB.NET you use the AddHandler() keyword, referring to the DocumentLockModeChanged event from the DocumentManager object – in C# you add it directly to the DocumentLockModeChanged property. This could be done from a command or from the Initialize() function, as I’ve done in the complete sample further below.

VB.NET:

Dim dm As DocumentCollection = Application.DocumentManager()

AddHandler dm.DocumentLockModeChanged, AddressOf vetoLineCommand

C#:

DocumentCollection dm = Application.DocumentManager;

dm.DocumentLockModeChanged += new DocumentLockModeChangedEventHandler(vetoLineCommand);

That's really all there is to it... as mentioned, the complete sample maintains a list of commands that are currently being vetoed. I've made this shared/static, and so it will be shared across documents (so if you try to modify this in concurrent drawings, you might get some interesting results).

Here's the complete sample (just to reiterate - use this technique both at your own risk and with considerable caution).

VB.NET:

Imports Autodesk.AutoCAD.Runtime

Imports Autodesk.AutoCAD.ApplicationServices

Imports Autodesk.AutoCAD.EditorInput

Imports System.Collections.Specialized


Namespace VetoTest

  Public Class VetoCmds

    Implements IExtensionApplication


    Shared cmdList As StringCollection _

      = New StringCollection


    Public Sub Initialize() _

      Implements IExtensionApplication.Initialize

      Dim dm As DocumentCollection

      dm = Application.DocumentManager()

      AddHandler dm.DocumentLockModeChanged, _

        AddressOf vetoCommandIfInList

    End Sub


    Public Sub Terminate() _

      Implements IExtensionApplication.Terminate

      Dim dm As DocumentCollection

      dm = Application.DocumentManager()

      RemoveHandler dm.DocumentLockModeChanged, _

        AddressOf vetoCommandIfInList

    End Sub


    <CommandMethod("ADDVETO")> _

    Public Sub AddVeto()

      Dim ed As Editor _

        = Application.DocumentManager.MdiActiveDocument.Editor

      Dim pr As PromptResult

      pr = ed.GetString(vbLf + _

        "Enter command to veto: ")

      If (pr.Status = PromptStatus.OK) Then

        If (cmdList.Contains(pr.StringResult.ToUpper())) Then

          ed.WriteMessage(vbLf + _

            "Command already in veto list.")

        Else

          cmdList.Add(pr.StringResult.ToUpper())

          ed.WriteMessage( _

            vbLf + "Command " + _

            pr.StringResult.ToUpper() + _

            " added to veto list.")

        End If

      End If

    End Sub


    <CommandMethod("LISTVETOES")> _

    Public Sub ListVetoes()

      Dim ed As Editor _

        = Application.DocumentManager.MdiActiveDocument.Editor

      ed.WriteMessage( _

        "Commands currently on veto list: " + vbLf)

      For Each str As String In cmdList

        ed.WriteMessage(str + vbLf)

      Next

    End Sub


    <CommandMethod("REMVETO")> _

    Public Sub RemoveVeto()

      ListVetoes()

      Dim ed As Editor _

        = Application.DocumentManager.MdiActiveDocument.Editor

      Dim pr As PromptResult

      pr = ed.GetString( _

        "Enter command to remove from veto list: ")

      If (pr.Status = PromptStatus.OK) Then

        If (cmdList.Contains(pr.StringResult.ToUpper())) Then

          cmdList.Remove(pr.StringResult.ToUpper())

        Else

          ed.WriteMessage(vbLf + _

            "Command not found in veto list.")

        End If

      End If

    End Sub


    Private Sub vetoCommandIfInList(ByVal sender As Object, _

      ByVal e As DocumentLockModeChangedEventArgs)

      If (cmdList.Contains(e.GlobalCommandName.ToUpper())) Then

        e.Veto()

      End If

    End Sub

  End Class

End Namespace

C#:

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.Runtime;

using System;

using System.Collections.Specialized;


namespace VetoTest

{

  public class VetoCmds : IExtensionApplication

  {

    static StringCollection cmdList =

      new StringCollection();


    public void Initialize()

    {

      DocumentCollection dm =

        Application.DocumentManager;

      dm.DocumentLockModeChanged += new

        DocumentLockModeChangedEventHandler(

          vetoCommandIfInList

        );

    }


    public void Terminate()

    {

      DocumentCollection dm;

      dm = Application.DocumentManager;

      dm.DocumentLockModeChanged -= new

        DocumentLockModeChangedEventHandler(

          vetoCommandIfInList

        );

    }


    [CommandMethod("ADDVETO")]

    public void AddVeto()

    {

      Editor ed =

        Application.DocumentManager.MdiActiveDocument.Editor;

      PromptResult pr =

        ed.GetString("\nEnter command to veto: ");

      if (pr.Status == PromptStatus.OK)

      {

        if (cmdList.Contains(pr.StringResult.ToUpper()))

        {

          ed.WriteMessage(

            "\nCommand already in veto list."

          );

        }

        else

        {

          cmdList.Add(pr.StringResult.ToUpper());

          ed.WriteMessage(

            "\nCommand " +

            pr.StringResult.ToUpper() +

            " added to veto list.");

        }

      }

    }


    [CommandMethod("LISTVETOES")]

    public void ListVetoes()

    {

      Editor ed =

        Application.DocumentManager.MdiActiveDocument.Editor;

      ed.WriteMessage("Commands currently on veto list:\n");

      foreach (string str in cmdList)

      {

        ed.WriteMessage(str + "\n");

      }

    }


    [CommandMethod("REMVETO")]

    public void RemoveVeto()

    {

      ListVetoes();

      Editor ed =

        Application.DocumentManager.MdiActiveDocument.Editor;

      PromptResult pr;

      pr =

        ed.GetString(

          "Enter command to remove from veto list: "

        );

      if (pr.Status == PromptStatus.OK)

      {

        if (cmdList.Contains(pr.StringResult.ToUpper()))

        {

          cmdList.Remove(pr.StringResult.ToUpper());

        }

        else

        {

          ed.WriteMessage(

            "\nCommand not found in veto list."

          );

        }

      }

    }


    private void vetoCommandIfInList(

      object sender,

      DocumentLockModeChangedEventArgs e)

    {

      if (cmdList.Contains(e.GlobalCommandName.ToUpper()))

      {

        e.Veto();

      }

    }

  }

}

In terms of usage, it should be simple enough to work out while playing with it:

  • ADDVETO adds commands to the veto list
  • LISTVETOES lists the commands on the veto list
  • REMVETO removes commands from the veto list

These commands are really only intended for you to try out different commands on the veto list, to see what happens. If you need to control use of command(s) from your application, you should not allow your users to manipulate the list of blocked commands themselves.

October 31, 2006 in AutoCAD, AutoCAD .NET, Commands, Notification / Events, Runtime | Permalink | Comments (9) | TrackBack

Automatic loading of .NET modules

Clearly it’s far from ideal to ask your users to load your application modules manually into AutoCAD whenever they need them, so over the years a variety of mechanisms have been put in place to enable automatic loading of applications – acad.lsp, acad.ads, acad.rx, the Startup Suite, to name but a few.

The most elegant way to auto-load both ObjectARX and .NET applications is the demand-loading mechanism. This mechanism is based on information stored in the Registry describing the conditions under which modules should be loaded and how to load them.

Demand loading is fairly straightforward and well documented in the ObjectARX Developer’s Guide (look under “demand loading applications”).

Essentially the information can be stored in one of two places: under HKEY_LOCAL_MACHINE or under HKEY_CURRENT_USER. The decision on where to place the information will depend on a few things – mainly whether the application is to be shared across all users but also whether your application installer has the privileges to write to HKEY_LOCAL_MACHINE or not.

It’s not really the place to talk about the pros and cons of these two locations – for the sake of simplicity the following examples show writing the information to HKEY_CURRENT_USER. Let’s start by looking at the root location for the demand-loading information:

HKEY_CURRENT_USER\Software\Autodesk\AutoCAD\R17.0\ACAD-5001:409\Applications

Most of this location is logical enough (to humans), although the “ACAD-5001:409” piece needs a bit of explanation. This number has evolved over the releases, but right now 5001 means AutoCAD 2007 (it was 4001 for AutoCAD 2006, 301 for AutoCAD 2005 and 201 for AutoCAD 2004), and 409 is the “locale” corresponding to English.

A more complete description of the meaning of this key is available to ADN members at:

Registry values for ProductID and LocaleID for AutoCAD and the vertical products

There are two common times to load a .NET application: on AutoCAD startup and on command invocation. ObjectARX applications might also be loaded on object detection, but as described in a previous post this is not something that is currently available to .NET applications.

Let’s take the two common scenarios and show typical settings for the test application shown in this previous post.

Loading modules on AutoCAD startup

Under the root key (HKEY_CURRENT_USER\Software\Autodesk\AutoCAD\R17.0\ACAD-5001:409\Applications) there is some basic information needed for our application. Firstly, you need to create a key just for our application: in this case I’ve used “MyTestApp” (as a rule it is recommended that professional software vendors prefix this string with their Registered Developer Symbol (RDS), which can be logged here, but for in-house development this is not necessary – just avoid beginning the key with “Acad” :-).

Under our application key (HKEY_CURRENT_USER\Software\Autodesk\AutoCAD\R17.0\ACAD-5001:409\Applications\MyTestApp), we then create a number of values:

  • DESCRIPTION    A string value describing the purpose of the module
  • LOADCTRLS      A DWORD (numeric) value describing the reasons for loading the app
  • MANAGED         Another DWORD that should be set to "1" for .NET modules
  • LOADER            A string value containing the path to the module

The interesting piece is the LOADCTRLS value – the way to encode this is described in detail in the ObjectARX Developer’s Guide, but to save time I’ll cut to the chase: this needs to have a value of "2" for AutoCAD to load the module on startup.

Here's a sample .reg file:

Windows Registry Editor Version 5.00


[HKEY_CURRENT_USER\Software\Autodesk\AutoCAD\R17.0\ACAD-5001:409\Applications\MyTestApplication]

"DESCRIPTION"="Kean's test application"

"LOADCTRLS"=dword:00000002

"MANAGED"=dword:00000001

"LOADER"="C:\\My Path\\MyTestApp.dll"

After merging it into the Registry, here's what happens when you launch AutoCAD:

Regenerating model.

Initializing - do something useful.

AutoCAD menu utilities loaded.

Command: tst

This is the TST command.

Command:

Loading modules on command invocation

To do this we need to add a little more information into the mix.

Firstly we need to change the value of LOADCTRLS to "12" (or "c" in hexadecimal), which is actually a combination of 4 (which means "on command invocation") and 8 (which means "on load request"). For people that want to know the other flags that can be used, check out rxdlinkr.h in the inc folder of the ObjectARX SDK.

Secondly we need to add a couple more keys, to contain information about our commands and command-groups.

Beneath a "Commands" key (HKEY_CURRENT_USER\Software\Autodesk\AutoCAD\R17.0\ACAD-5001:409\Applications\MyTestApp\Commands), we'll create as many string values as we have commands, each with the name of the "global" command name, and the value of the "local" command name. As well as the "TST" command, I've added one more called "ANOTHER".

Beneath a "Groups" key (HKEY_CURRENT_USER\Software\Autodesk\AutoCAD\R17.0\ACAD-5001:409\Applications\MyTestApp\Groups), we'll do the same for the command-groups we've registered our commands under (I used the default CommandMethod attribute that doesn't mention a group name, so this is somewhat irrelevant for our needs - I'll use "ASDK_CMDS" as an example, though).

Here's the the updated .reg file:

Windows Registry Editor Version 5.00


[HKEY_CURRENT_USER\Software\Autodesk\AutoCAD\R17.0\ACAD-5001:409\Applications\MyTestApplication]

"DESCRIPTION"="Kean's test application"

"LOADCTRLS"=dword:0000000c

"MANAGED"=dword:00000001

"LOADER"="C:\\My Path\\MyTestApp.dll"


[HKEY_CURRENT_USER\Software\Autodesk\AutoCAD\R17.0\ACAD-5001:409\Applications\MyTestApp\Commands]

"TST"="TST"

"ANOTHER"="ANOTHER"


[HKEY_CURRENT_USER\Software\Autodesk\AutoCAD\R17.0\ACAD-5001:409\Applications\MyTestApp\Groups]

"ASDK_CMDS"="ASDK_CMDS"

And here's what happens when we launch AutoCAD this time, and run the tst command. You see the module is only loaded once the command is invoked:

Regenerating model.

AutoCAD menu utilities loaded.

Command: tst

Initializing - do something useful.This is the TST command.

Command:

September 11, 2006 in AutoCAD, AutoCAD .NET, Runtime | Permalink | Comments (32) | TrackBack

Optimizing the loading of AutoCAD .NET applications

In my previous post I described how you could use the Autodesk.AutoCAD.Runtime.IExtensionApplication interface to implement initialization code in your .NET module. Building on this, we're now going to look at how use of the Autodesk.AutoCAD.Runtime.IExtensionApplication interface can also allow you - with very little effort - to optimize the architecture of your managed modules for faster loading into AutoCAD.

First some information from the "Using .NET for AutoCAD documentation" (which is available in the ObjectARX Developer's Guide on the ObjectARX SDK):

When AutoCAD loads a managed application, it queries the application's assembly for an ExtensionApplication custom attribute. If this attribute is found, AutoCAD sets the attribute's associated type as the application's entry point. If no such attribute is found, AutoCAD searches all exported types for an IExtensionApplication implementation. If no implementation is found, AutoCAD simply skips the application-specific initialization step.

...

In addition to searching for an IExtensionApplication implementation, AutoCAD queries the application's assembly for one or more CommandClass attributes. If instances of this attribute are found, AutoCAD searches only their associated types for command methods. Otherwise, it searches all exported types.

The samples that I've shown in this blog - and most of those on the ObjectARX SDK - do not show how you can use the ExtensionApplication or CommandClass attribute in your code, as it's not essential to implement them for your application to work. But if you have a large .NET module to be loaded into AutoCAD, it might take some time for AutoCAD to check the various objects in the assembly, to find out which is the ExtensionApplication and which are the various CommandClasses.

The attributes you need to implement are very straightforward:

C#:

[assembly: ExtensionApplication(typeof(InitClass))]

[assembly: CommandClass(typeof(CmdClass))]

VB.NET:

<Assembly: ExtensionApplication(GetType(InitClass))>

<Assembly: CommandClass(GetType(CmdClass))>

These assembly-level attributes simply tell AutoCAD where to look for the various objects it will otherwise need to identify by searching. Here's some more information from the documentation on the use of these attributes:

The ExtensionApplication attribute can be attached to only one type. The type to which it is attached must implement the IExtensionApplication interface.

...

A CommandClass attribute may be declared for any type that defines AutoCAD command handlers. If an application uses the CommandClass attribute, it must declare an instance of this attribute for every type that contains an AutoCAD command handler method.

While optimizing yesterday's code to reduce load-time, I also changed the structure slightly to be more logical. The above attributes also take classes within a namespace, so I decided to split the initialization code (the "Initialization" class) away from the command implementations (the "Commands" class), but keeping them both in the same ("ManagedApplication") namespace.

And here's the code...

C#:

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.EditorInput;

using System;

[assembly:

  ExtensionApplication(

    typeof(ManagedApplication.Initialization)

  )

]

[assembly:

  CommandClass(

    typeof(ManagedApplication.Commands)

  )

]

namespace ManagedApplication

{

  public class Initialization

    : Autodesk.AutoCAD.Runtime.IExtensionApplication

  {

    public void Initialize()

    {

      Editor ed =

        Application.DocumentManager.MdiActiveDocument.Editor;

      ed.WriteMessage("Initializing - do something useful.");

    }

    public void Terminate()

    {

      Console.WriteLine("Cleaning up...");

    }

  }

  public class Commands

  {

    [CommandMethod("TST")]

    public void Test()

    {

      Editor ed =

        Application.DocumentManager.MdiActiveDocument.Editor;

      ed.WriteMessage("This is the TST command.");

    }

  }

}

VB.NET:

Imports Autodesk.AutoCAD.Runtime

Imports Autodesk.AutoCAD.ApplicationServices

Imports Autodesk.AutoCAD.EditorInput

Imports System

<Assembly: _

  ExtensionApplication( _

    GetType(ManagedApplication.Initialization))>

<Assembly: _

  CommandClass( _

    GetType(ManagedApplication.Commands))>

Namespace ManagedApplication

  Public Class Initialization

    Implements Autodesk.AutoCAD.Runtime.IExtensionApplication

    Public Sub Initialize() Implements _

    IExtensionApplication.Initialize

      Dim ed As Editor = _

        Application.DocumentManager.MdiActiveDocument.Editor

      ed.WriteMessage("Initializing - do something useful.")

    End Sub

    Public Sub Terminate() Implements _

    IExtensionApplication.Terminate

      Console.WriteLine("Cleaning up...")

    End Sub

  End Class

  Public Class Commands

    <CommandMethod("TST")> _

    Public Sub Test()

      Dim ed As Editor = _

        Application.DocumentManager.MdiActiveDocument.Editor

      ed.WriteMessage("This is the TST command.")

    End Sub

  End Class

End Namespace

September 8, 2006 in AutoCAD, AutoCAD .NET, Runtime | Permalink | Comments (5) | TrackBack

Initialization code in your AutoCAD .NET application

It's very common to need to execute some code as your application modules are loaded, and then to clean-up as they get unloaded or as AutoCAD terminates. Managed AutoCAD applications can do this by implementing the Autodesk.AutoCAD.Runtime.IExtensionApplication interface, which require Initialize() and Terminate() methods.

During the Initialize() method, you will typically want to set system variables and perhaps call commands which execute some pre-existing initialization code for your application.

Here's some code showing how to implement this interface using VB.NET:

Imports Autodesk.AutoCAD.Runtime

Imports Autodesk.AutoCAD.ApplicationServices

Imports Autodesk.AutoCAD.EditorInput

Imports System

Public Class InitializationTest

  Implements Autodesk.AutoCAD.Runtime.IExtensionApplication

  Public Sub Initialize() Implements _

  IExtensionApplication.Initialize

    Dim ed As Editor = _

      Application.DocumentManager.MdiActiveDocument.Editor

    ed.WriteMessage("Initializing - do something useful.")

  End Sub

  Public Sub Terminate() Implements _

  IExtensionApplication.Terminate

    Console.WriteLine("Cleaning up...")

  End Sub

  <CommandMethod("TST")> _

  Public Sub Test()

    Dim ed As Editor = _

      Application.DocumentManager.MdiActiveDocument.Editor

    ed.WriteMessage("This is the TST command.")

  End Sub

End Class

And here's the equivalent code in C#:

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.EditorInput;

using System;

public class InitializationTest

  : Autodesk.AutoCAD.Runtime.IExtensionApplication

{

  public void Initialize()

  {

    Editor ed =

      Application.DocumentManager.MdiActiveDocument.Editor;

    ed.WriteMessage("Initializing - do something useful.");

  }

  public void Terminate()

  {

    Console.WriteLine(