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)

  )

]

3 responses to “Getting the list of .NET-defined commands in AutoCAD”

  1. Thanks Kean, That will be excellent to build on.

    Greatly appreciated,
    Regards
    Kerry

  2. Alexander Rivilis Avatar
    Alexander Rivilis

    Thanks Kean!
    For commands which is defined in arx-files (not in .NET) we can use such code:
    [code]
    static void GetArxFileFromCommand(void)
    {
    Acad::ErrorStatus es;
    AcArray<module> modules;
    HANDLE hAcad = GetCurrentProcess();
    HMODULE *hMods = NULL;
    DWORD cbneed=0; EnumProcessModules(hAcad,NULL,0,&cbneed)
    DWORD nModules = cbneed/sizeof(HMODULE);
    hMods = new HMODULE[nModules+1];
    EnumProcessModules(hAcad,hMods,cbneed,&cbneed)
    for (int i=0; i<nmodules; i++)="" {="" module="" module;="" memset(&amp;module,0,sizeof(module))="" getmodulefilename(hmods[i],module.path,sizeof(module.path)-1);="" moduleinfo="" mi;="" memset(&amp;mi,0,sizeof(mi))="" getmoduleinformation(hacad,hmods[i],&amp;mi,sizeof(mi))="" module.base="(PBYTE)mi.lpBaseOfDll;" module.size="mi.SizeOfImage;" modules.append(module);="" }="" delete="" []="" hmods;="" char="" buf[256]="" ;="" if="" (acedgetstring(false,"\ncommand="" name="" (without="" dot="" and="" or="" underlining):="" ",buf)="=" rtnorm)="" {="" acedcommandstruc="" cmd;="" if="" (acedcmdlookup(buf,="" true,="" &amp;cmd)="" ||="" acedcmdlookup(buf,="" false,="" &amp;cmd))="" {="" pbyte="" funptr="(PBYTE)cmd.fcnAddr;" for="" (int="" i="0;" i<modules.length();="" i++)="" {="" if="" (funptr="">= modules[i].base && funPtr <= (modules[i].base+modules[i].size)) {
    acutPrintf("\nModule path: <%s>",modules[i].path);
    }
    }
    }
    }
    }
    [/code]

  3. MethodInfo[] meths = tp.GetMethods(); can be null, use ...
    MethodInfo[] meths = tp?.GetMethods() ?? new MethodInfo[0];

Leave a Reply to Kerry Brown Cancel reply

Your email address will not be published. Required fields are marked *