Author Topic: 2 Projects, 1 Solution, CommandMethods in both?  (Read 8416 times)

0 Members and 2 Guests are viewing this topic.

Jeff_M

  • King Gator
  • Posts: 4096
  • C3D user & customizer
2 Projects, 1 Solution, CommandMethods in both?
« on: March 21, 2013, 10:18:51 AM »
I have a VS2010 Solution with multiple Projects, 2 of which contain multiple [CommandMethod()] definitions. The commands in the main project work fine. Those defined in the secondary project, which is referenced into the main project, are not recognized. Is there something I need to do (well, obviously there is...but what?) in order for these additional commands to be 'seen' by Autocad?

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: 2 Projects, 1 Solution, CommandMethods in both?
« Reply #1 on: March 21, 2013, 11:11:34 AM »
the easy way would be to netload the dll from the second project.

the nice way (maybe, haven't tried) might be to add the line

Code - C#: [Select]
  1. [assebly: CommandClass(typeof(the.namespace.of.the.external.commands))]

into the first project, so when it is loaded by autocad it actually registers the commands (note if you do this anywhere in your project you will need to do it everywhere... it signals to autocad that you (the programmer) has explicitly separated command classes and only looks in those places for CommandMethods)


I think it's because of how netload actually functions

Jeff_M

  • King Gator
  • Posts: 4096
  • C3D user & customizer
Re: 2 Projects, 1 Solution, CommandMethods in both?
« Reply #2 on: March 21, 2013, 11:31:46 AM »
OK, I guess I'm confused, again....doesn't take much :-) If the second project is referenced by the first, shouldn't that mean the second gets loaded when the first is loaded? That's what I thought happens, since the public classes, properties, and methods, that I have in other projects are all available and used as needed. But if I manually load this one project's DLL, even though it is referenced by the first, only then are the commands available. I can go ahead and force it to load, it just seems like I shouldn't need to do so.

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: 2 Projects, 1 Solution, CommandMethods in both?
« Reply #3 on: March 21, 2013, 12:05:20 PM »
I think it has to do with class definitions.  Since your classes are instance classes then when AutoCAD looks through your assembly for the CommandMethod attribute it doesn't look through the referenced project.

Did you try the CommandClass assembly attribute? That may force AutoCAD to load up and examine your other project.

You could also just register the commands in the second project for demand loading.  If you look into the guts of the RegistForDemandLoading that Kean has posted you could tweak it to register the commands from the second project along with the first.

huiz

  • Swamp Rat
  • Posts: 917
  • Certified Prof C3D
Re: 2 Projects, 1 Solution, CommandMethods in both?
« Reply #4 on: March 21, 2013, 12:29:11 PM »
Is the class in the second project public (or c# equivalent)? For commands it must be public.
The conclusion is justified that the initialization of the development of critical subsystem optimizes the probability of success to the development of the technical behavior over a given period.

BlackBox

  • King Gator
  • Posts: 3770
Re: 2 Projects, 1 Solution, CommandMethods in both?
« Reply #5 on: March 21, 2013, 12:40:50 PM »
As I understand it, Jeff... Only the first CommandClassAttribute is recognized, meaning that your CommandMethodAttribute definitions need to reside within the same Class/Type the CommandClassAttribute is targeting.

Disregard.
« Last Edit: March 21, 2013, 01:26:45 PM by BlackBox »
"How we think determines what we do, and what we do determines what we get."

BlackBox

  • King Gator
  • Posts: 3770
Re: 2 Projects, 1 Solution, CommandMethods in both?
« Reply #6 on: March 21, 2013, 01:10:59 PM »
As I understand it, Jeff... Only the first CommandClassAttribute is recognized, meaning that your CommandMethodAttribute definitions need to reside within the same Class/Type the CommandClassAttribute is targeting.

... Starting to wonder if anything I was taught during my recent contract position was actually correct. I really hate wasting my time.  :|

Just tried a quick project with two CommandClassAttributes, and both register just fine regardless of being in the same Namespace, or targeting different public Types.

« Last Edit: March 21, 2013, 01:26:27 PM by BlackBox »
"How we think determines what we do, and what we do determines what we get."

Jeff_M

  • King Gator
  • Posts: 4096
  • C3D user & customizer
Re: 2 Projects, 1 Solution, CommandMethods in both?
« Reply #7 on: March 21, 2013, 01:20:22 PM »
I don't utilize any CommandCLassAttributes....

Trying to change everything to use [assembly: CommandClass(....)] is not something I would like to implement. We tried this a few years ago, when we had about half the commands we now do, and it was difficult, at best, to keep the list of commands up to date.

What I just tested, and seems to be working with the one command I tested with, is to move the command call from the second project into it's own class in the main project. So this new class has nothing more than [CommandMethod()] and a method to call the actual method in the second project, for each of the 7 commands in that second project. Seems a bit convoluted but, as I said, it seems to be working.

Jeff H

  • Needs a day job
  • Posts: 6150
Re: 2 Projects, 1 Solution, CommandMethods in both?
« Reply #8 on: March 21, 2013, 01:21:52 PM »
As I understand it, Jeff... Only the first CommandClassAttribute is recognized, meaning that your CommandMethodAttribute definitions need to reside within the same Class/Type the CommandClassAttribute is targeting.
CommandClassAttribute is metadata at the assembly level and you can have many and add them anywhere in any class file, but placing them in  AssemblyInfo file is probably a good practice AutoCAD .NET CommandClass and Some Clarifications
you will see that it is added to assembly Manifest if you look at it with IL Disassembler.
 
If you look at first if statement AutoCAD checks the assembly for CommandClassAttribute if at least one exists then it adds them.
If no CommandClassAttribute are found then it will start looking at metadata for the types in the assembly.


So if you add one CommandClassAttribute then you have to do it for all types that contain methods with CommandMethodAttribute
 

Code - C#: [Select]
  1. [return: MarshalAs(UnmanagedType.U1)]
  2. public unsafe bool Initialize(Assembly assembly)
  3. {
  4.     bool flag;
  5.     ManageGApp app;
  6.     ManageGApp.{ctor}(&app);
  7.     try
  8.     {
  9.         Type[] exportedTypes;
  10.         PerDocumentCommandClass class2;
  11.         object[] objArray2;
  12.         this.m_commandClasses = new List<CommandClass>();
  13.         CommandClass item = null;
  14.         object[] customAttributes = assembly.GetCustomAttributes(typeof(CommandClassAttribute), false);
  15.         int length = customAttributes.Length;
  16.         if (length > 0)
  17.         {
  18.             exportedTypes = new Type[length];
  19.             int num4 = 0;
  20.             if (0 < length)
  21.             {
  22.                 do
  23.                 {
  24.                     CommandClassAttribute attribute = (CommandClassAttribute) customAttributes[num4];
  25.                     exportedTypes[num4] = attribute.Type;
  26.                     num4++;
  27.                 }
  28.                 while (num4 < customAttributes.Length);
  29.             }
  30.         }
  31.         else
  32.         {
  33.             exportedTypes = assembly.GetExportedTypes();
  34.         }
  35.         int index = 0;
  36.         if (0 >= exportedTypes.Length)
  37.         {
  38.             goto Label_0185;
  39.         }
  40.     Label_007B:
  41.         class2 = null;
  42.         if (exportedTypes[index] == null)
  43.         {
  44.             goto Label_0176;
  45.         }
  46.         MethodInfo[] methods = exportedTypes[index].GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
  47.         int num = 0;
  48.         if (0 >= methods.Length)
  49.         {
  50.             goto Label_0165;
  51.         }
  52.     Label_00A3:
  53.         objArray2 = methods[num].GetCustomAttributes(typeof(ICommandLineCallable), false);
  54.         if (objArray2.Length != 0)
  55.         {
  56.             CommandClass class4;
  57.             object[] objArray = methods[num].GetCustomAttributes(typeof(ICondition), false);
  58.             int num7 = objArray.Length;
  59.             if (num7 != 0)
  60.             {
  61.                 int num2 = 0;
  62.                 if (0 < num7)
  63.                 {
  64.                     do
  65.                     {
  66.                         if (!((ICondition) objArray[num2]).Evaluate())
  67.                         {
  68.                             break;
  69.                         }
  70.                         num2++;
  71.                     }
  72.                     while (num2 < objArray.Length);
  73.                 }
  74.                 if (num2 < objArray.Length)
  75.                 {
  76.                     goto Label_0159;
  77.                 }
  78.             }
  79.             if (methods[num].IsStatic)
  80.             {
  81.                 if (item == null)
  82.                 {
  83.                     item = new CommandClass();
  84.                 }
  85.                 class4 = item;
  86.             }
  87.             else
  88.             {
  89.                 if (class2 == null)
  90.                 {
  91.                     class2 = new PerDocumentCommandClass();
  92.                 }
  93.                 class4 = class2;
  94.             }
  95.             int num5 = 0;
  96.             if (0 < objArray2.Length)
  97.             {
  98.                 do
  99.                 {
  100.                     class4.AddCommand((ICommandLineCallable) objArray2[num5], methods[num]);
  101.                     num5++;
  102.                 }
  103.                 while (num5 < objArray2.Length);
  104.             }
  105.         }
  106.     Label_0159:
  107.         num++;
  108.         if (num < methods.Length)
  109.         {
  110.             goto Label_00A3;
  111.         }
  112.     Label_0165:
  113.         if (class2 != null)
  114.         {
  115.             this.m_commandClasses.Add(class2);
  116.         }
  117.     Label_0176:
  118.         index++;
  119.         if (index < exportedTypes.Length)
  120.         {
  121.             goto Label_007B;
  122.         }
  123.     Label_0185:
  124.         if (item != null)
  125.         {
  126.             this.m_commandClasses.Add(item);
  127.         }
  128.         flag = (bool) ((byte) (this.m_commandClasses.Count > 0));
  129.     }
  130.     fault
  131.     {
  132.         ___CxxCallUnwindDtor(ManageGApp.{dtor}, (void*) &app);
  133.     }
  134.     ManageGApp.{dtor}(&app);
  135.     return flag;
  136. }


 


 

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: 2 Projects, 1 Solution, CommandMethods in both?
« Reply #9 on: March 21, 2013, 01:48:04 PM »
A working method of not thinking about the dependency:

First project initialization method calls second project
Code - C#: [Select]
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using Autodesk.AutoCAD.ApplicationServices;
  7. using Autodesk.AutoCAD.DatabaseServices;
  8. using Autodesk.AutoCAD.Runtime;
  9. using Autodesk.AutoCAD.EditorInput;
  10. using Autodesk.AutoCAD.Interop;
  11. using System.Reflection;
  12. using System.IO;
  13. //using project2;
  14. [assembly: CommandClass(typeof(_2ProjectSolution.Class1))]
  15. [assembly: CommandClass(typeof(_2ProjectSolution.Class2))]
  16. namespace _2ProjectSolution
  17. {
  18.     public class Class1
  19.     {
  20.         [CommandMethod("FirstProjectClass1")]
  21.         public void test1()
  22.         {
  23.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  24.             ed.WriteMessage("\nWe've reached the first class");
  25.         }
  26.     }
  27.     public class Class2
  28.     {
  29.         [CommandMethod("FirstProjectClass2")]
  30.         public void test2()
  31.         {
  32.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  33.             ed.WriteMessage("\nWe've reached the second class");
  34.         }
  35.     }
  36.     public class InitializationTest : Autodesk.AutoCAD.Runtime.IExtensionApplication
  37.     {
  38.         public void Initialize()
  39.         {
  40.             Document doc = Application.DocumentManager.MdiActiveDocument;
  41.             Editor ed = doc.Editor;
  42.             int filedia = System.Convert.ToInt32(Application.GetSystemVariable("FILEDIA"));
  43.             try
  44.             {
  45.  
  46.                 ((AcadDocument)doc.GetAcadDocument()).SendCommand("FILEDIA 0 ");
  47.                 ((AcadDocument)doc.GetAcadDocument()).SendCommand(string.Format("NETLOAD {0}\\project2.dll\n", Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)));
  48.                 ((AcadDocument)doc.GetAcadDocument()).SendCommand(string.Format("FILEDIA {0} ", filedia));
  49.             }
  50.             catch (System.Exception e)
  51.             {
  52.                 throw e;
  53.             }
  54.         }
  55.         public void Terminate()
  56.         {
  57.            
  58.         }
  59.     }
  60. }
Code - C#: [Select]
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using Autodesk.AutoCAD.ApplicationServices;
  7. using Autodesk.AutoCAD.DatabaseServices;
  8. using Autodesk.AutoCAD.Runtime;
  9. using Autodesk.AutoCAD.EditorInput;
  10.  
  11. [assembly: CommandClass(typeof(project2.Class3))]
  12.  
  13. namespace project2
  14. {
  15.     public class Class3
  16.     {
  17.         [CommandMethod("SecondProjectClass1")]
  18.         public void test3()
  19.         {
  20.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  21.             ed.WriteMessage("\nWe've reached the second project's first class");
  22.         }
  23.     }
  24. }
  25.  

Tried just adding a command class attribute to the first project referencing the class in the second but got an error  :-(

BlackBox

  • King Gator
  • Posts: 3770
Re: 2 Projects, 1 Solution, CommandMethods in both?
« Reply #10 on: March 21, 2013, 01:57:38 PM »
What I just tested, and seems to be working with the one command I tested with, is to move the command call from the second project into it's own class in the main project. So this new class has nothing more than [CommandMethod()] and a method to call the actual method in the second project, for each of the 7 commands in that second project. Seems a bit convoluted but, as I said, it seems to be working.

CommandClassAttribute is metadata at the assembly level and you can have many and add them anywhere in any class file, but placing them in  AssemblyInfo file is probably a good practice AutoCAD .NET CommandClass and Some Clarifications
you will see that it is added to assembly Manifest if you look at it with IL Disassembler.
 
If you look at first if statement AutoCAD checks the assembly for CommandClassAttribute if at least one exists then it adds them.
If no CommandClassAttribute are found then it will start looking at metadata for the types in the assembly.


So if you add one CommandClassAttribute then you have to do it for all types that contain methods with CommandMethodAttribute

It was about ten months ago since I worked with this firm on a part-time basis, but one of the first things I recall was that they had an issue similar to Jeff's where all CommandMethod definitions ended up being located in a single class (of +/-30 different classes for this particular project)... I could have sworn that the CommandClass attribute was used, but clearly I am mistaken given my earlier test to confirm what I am posting.

Jeff (M) has already arrived upon the answer I mistakenly thought I was offering; I just need a better memory (firstly), and to learn a great deal more to stop making these silly mistakes... Apologies for the unintentional distraction, guys.  :-(

Thanks for reminding me of the use/limitations/requirements of CommandClass attribute, as well Jeff (H). I'll have to dedicate some time to digesting the code you posted.

Cheers
"How we think determines what we do, and what we do determines what we get."

Jeff_M

  • King Gator
  • Posts: 4096
  • C3D user & customizer
Re: 2 Projects, 1 Solution, CommandMethods in both?
« Reply #11 on: March 21, 2013, 03:19:33 PM »
Trying something new...I added the [assembly: CommandClass()] entries for all commands, including those in the second project, into one class in the main project. At load time in Autocad I got an eDuplicateKey error. I went back and commented out the 3 lines that point to those in the second project, then it loaded without error. But then, of course, those commands didn't work. So I added just those 3 lines into the AssemblyInfo for the second project, everything still loads fine, but those commands in the second project still no worky. It looks like I need to keep all of the CommandMethods in the main project and just call the methods in the second project. Not ideal, and it's only 7 commands, but it makes me wonder what others do in a Solution with many others working in different projects. It looked like the link JeffH gave might go into that, but I think it fell short of describing what to do...or maybe I missed it.

Jeff_M

  • King Gator
  • Posts: 4096
  • C3D user & customizer
Re: 2 Projects, 1 Solution, CommandMethods in both?
« Reply #12 on: March 21, 2013, 03:58:25 PM »
So, I think that what is happening is Autocad only checks for Command s when the DLL is actually loaded into Autocad. So even the the DLL is referenced by the first project, and all of the public methods & properties are available to the first project, Autocad doesn't automatically load the same dll in order to look for those commands. Which means that I should be able to just add a line to load  the second dll, which won't hurt anything (you can load manually load the same dll into Autocad with no repercussions that I know of), this allowing Autocad to 'see' those commands. Or, just do what I did originally and place the commands in the main project and forget about it.

Will, instead of SendCommand I would use something like this code since both project's dll's will be in the same location:
Code: [Select]
string dllName = "Secondproject.dll";
string dllPath = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + dllName;
System.Reflection.Assembly.LoadFrom(dllPath);
« Last Edit: March 21, 2013, 04:03:01 PM by Jeff_M »

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: 2 Projects, 1 Solution, CommandMethods in both?
« Reply #13 on: March 21, 2013, 04:12:31 PM »
Will, instead of SendCommand I would use something like this code since both project's dll's will be in the same location:
Code: [Select]
string dllName = "Secondproject.dll";
string dllPath = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + dllName;
System.Reflection.Assembly.LoadFrom(dllPath);

Not sure if that would work for us in this case because we would need the CommandMethods to be registered inside AutoCAD.  If you try it let me know the result please

Jeff_M

  • King Gator
  • Posts: 4096
  • C3D user & customizer
Re: 2 Projects, 1 Solution, CommandMethods in both?
« Reply #14 on: March 21, 2013, 04:22:49 PM »
Actually I determined that I couldn't use either method to load the dll, as this dll is actually linked into the main DLL before it gets released for customers. So there is just the one DLL that physically can be loaded. This forced me to go with the 'all commands in one project' approach.

Btw, I think that the loading via Reflection would work if I did have the 2 dll's exposed. We use this method to load the main dll after verifying that the Autocad Session is running as Civil3D, which then allows all of the commands to be seen by Autocad.