October 2014

Sun Mon Tue Wed Thu Fri Sat
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  










« Wolfram|Alpha – a computational knowledge engine in the cloud | Main | DevLabs reminder »

May 25, 2009

Interfacing an external COM application with a .NET module in-process to AutoCAD (redux)

Thanks to all of your interest in this recent post, which looked at a way to interface an out-of-process .NET application with an assembly running in-process to AutoCAD. After some obvious functionality gaps were raised, Renze de Waal, one of our ADN members, pointed out a DevNote on the ADN website covering – and more completely addressing – this topic. Shame on me for not checking there before writing the post. Anyway, onwards and upwards…

The information in the DevNote highlights some of the problems I and other people had hit with my previous code, mostly related to the fact it wasn’t executed on the main AutoCAD thread (which meant we were effectively limited in the interactions we had with the AutoCAD application).

To fix this we can derive our application from System.EnterpriseServices.ServicedComponent (also adding an additional project reference to the System.EnterpriseServices .NET assembly). Here is the updated C# code for the LoadableComponent:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Geometry;

using System.Runtime.InteropServices;

using System.EnterpriseServices;

 

namespace LoadableComponent

{

  [Guid("5B5B731C-B37A-4aa2-8E50-42192BD51B17")]

  public interface INumberAddition

  {

    [DispId(1)]

    string AddNumbers(int arg1, double arg2);

  }

 

  [ProgId("LoadableComponent.Commands"),

   Guid("44D8782B-3F60-4cae-B14D-FA060E8A4D01"),

   ClassInterface(ClassInterfaceType.None)]

  public class Commands : ServicedComponent, INumberAddition

  {

    // A simple test command, just to see that commands

    // are loaded properly from the assembly

 

    [CommandMethod("MYCOMMAND")]

    static public void MyCommand()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      ed.WriteMessage("\nTest command executed.");

    }

 

    // A function to add two numbers and create a

    // circle of that radius. It returns a string

    // withthe result of the addition, just to use

    // a different return type

 

    public string AddNumbers(int arg1, double arg2)

    {     

      // During tests it proved unreliable to rely

      // on DocumentManager.MdiActiveDocument

      // (which was null) so we will go from the

      // HostApplicationServices' WorkingDatabase

 

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      ed.WriteMessage(

        "\nAdd numbers called with {0} and {1}.",

        arg1, arg2

      );

 

      // Perform our addition

 

      double res = arg1 + arg2;

 

      // Lock the document before we access it

 

      DocumentLock loc = doc.LockDocument();

      using (loc)

      {

        Transaction tr =

          db.TransactionManager.StartTransaction();

        using (tr)

        {

          // Create our circle

 

          Circle cir =

            new Circle(

              new Point3d(0, 0, 0),

              new Vector3d(0, 0, 1),

              res

            );

 

          cir.SetDatabaseDefaults(db);

 

          // Add it to the current space

 

          BlockTableRecord btr =

            (BlockTableRecord)tr.GetObject(

              db.CurrentSpaceId,

              OpenMode.ForWrite

            );

          btr.AppendEntity(cir);

          tr.AddNewlyCreatedDBObject(cir, true);

 

          // Commit the transaction

 

          tr.Commit();

        }

      }

 

      // Return our string result

 

      return res.ToString();

    }

  }

}

Some points to note...

  • We now use an interface to expose functionality from our component, which allows us more flexibility in the way we return data to the calling application.
  • We're labeling our interface and component with specific GUIDs - generated by guidgen.exe - although we could probably skip this step.
  • We're now able to use the MdiActiveDocument property safely, as well as being able to write messages via the editor.

When we build the component we can - as before - register it via the regasm.exe tool. Here's the .reg output if you specify the /regfile option:

REGEDIT4

 

[HKEY_CLASSES_ROOT\LoadableComponent.Commands]

@="LoadableComponent.Commands"

 

[HKEY_CLASSES_ROOT\LoadableComponent.Commands\CLSID]

@="{44D8782B-3F60-4CAE-B14D-FA060E8A4D01}"

 

[HKEY_CLASSES_ROOT\CLSID\{44D8782B-3F60-4CAE-B14D-FA060E8A4D01}]

@="LoadableComponent.Commands"

 

[HKEY_CLASSES_ROOT\CLSID\{44D8782B-3F60-4CAE-B14D-FA060E8A4D01}\InprocServer32]

@="mscoree.dll"

"ThreadingModel"="Both"

"Class"="LoadableComponent.Commands"

"Assembly"="LoadableComponent, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"

"RuntimeVersion"="v2.0.50727"

 

[HKEY_CLASSES_ROOT\CLSID\{44D8782B-3F60-4CAE-B14D-FA060E8A4D01}\InprocServer32\1.0.0.0]

"Class"="LoadableComponent.Commands"

"Assembly"="LoadableComponent, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"

"RuntimeVersion"="v2.0.50727"

 

[HKEY_CLASSES_ROOT\CLSID\{44D8782B-3F60-4CAE-B14D-FA060E8A4D01}\ProgId]

@="LoadableComponent.Commands"

 

[HKEY_CLASSES_ROOT\CLSID\{44D8782B-3F60-4CAE-B14D-FA060E8A4D01}\Implemented Categories\{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}]

One thing to mention - I found that the calling application was not able to to cast the returned System.__COMObject to LoadableComponent.INumberAddition unless I updated the project settings to "Register from COM Interop" (near the bottom of the Build tab).

Now for our calling application… here’s the updated C# code:

using Autodesk.AutoCAD.Interop;

using System.Windows.Forms;

using System.Runtime.InteropServices;

using System.Reflection;

using System;

using LoadableComponent;

 

namespace DrivingAutoCAD

{

  public partial class Form1 : Form

  {

    public Form1()

    {

      InitializeComponent();

    }

 

    private void button1_Click(object sender, EventArgs e)

    {

      const string progID = "AutoCAD.Application.18";

 

      AcadApplication acApp = null;

      try

      {

        acApp =

          (AcadApplication)Marshal.GetActiveObject(progID);

      }

      catch

      {

        try

        {

          Type acType =

            Type.GetTypeFromProgID(progID);

          acApp =

            (AcadApplication)Activator.CreateInstance(

              acType,

              true

            );

        }

        catch

        {

          MessageBox.Show(

            "Cannot create object of type \"" +

            progID + "\""

          );

        }

      }

      if (acApp != null)

      {

        try

        {

          // By the time this is reached AutoCAD is fully

          // functional and can be interacted with through code

 

          acApp.Visible = true;

 

          INumberAddition app =

            (INumberAddition)acApp.GetInterfaceObject(

              "LoadableComponent.Commands"

            );

 

          // Now let's call our method

 

          string res = app.AddNumbers(5, 6.3);

 

          acApp.ZoomAll();

 

          MessageBox.Show(

            this,

            "AddNumbers returned: " + res

          );

        }

        catch (Exception ex)

        {

          MessageBox.Show(

            this,

            "Problem executing component: " +

            ex.Message

          );

        }

      }

    }

  }

}

You should be able to see straightaway that it’s simpler – we cast the results of the GetInterfaceObject call to our interface and call the AddNumbers method on it.

And when we execute the code, we can see we’re now able to write to the command-line, as well as getting better results from our ZoomAll():

Result of driving AutoCAD via in- and out-of-proc code (redux)

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00d83452464869e201156faf9390970c

Listed below are links to weblogs that reference Interfacing an external COM application with a .NET module in-process to AutoCAD (redux):

blog comments powered by Disqus

Feed/Share

10 Random Posts