March 15, 2007

Getting the list of .NET-defined commands in AutoCAD

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

{

publicclassCommands

{

[CommandMethod("TC")]

staticpublicvoid 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")]

staticpublicvoid ListMarkedCommands()

{

// Get the commands for all assemblies,

// but only those with explicit

// CommandClass attributes (much quicker)

StringCollection cmds = newStringCollection();

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")]

staticpublicvoid ListCommands()

{

// Get the commands for all assemblies,

// marked or otherwise (much slower)

StringCollection cmds = newStringCollection();

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");

}

}

privatestaticstring[] GetCommands(

Assembly asm,

bool markedOnly

)

{

StringCollection sc = newStringCollection();

object[] objs =

asm.GetCustomAttributes(

typeof(CommandClassAttribute),

true

);

Type[] tps;

int numTypes = objs.Length;

if (numTypes > 0)

{

tps = newType[numTypes];

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

{

CommandClassAttribute cca =

objs[i] asCommandClassAttribute;

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 = newType[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 = newstring[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: