Kean Walmsley

July 2009

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  

Twitter Updates

    follow me on Twitter



    « Visual Studio 2008 & .NET Framework 3.5 released | Main | Metaprogramming with AutoCAD - Part 3 »

    November 22, 2007

    Metaprogramming with AutoCAD - Part 2

    In this post we're going to continue the topic started in Part 1 of this series, which looked briefly at metaprogramming with AutoCAD using AutoLISP and VB(A). Now we're going to look at .NET, focusing initially on C# and VB.NET.

    [I found the inspiration for the code in this post from The Code Project, although I had to update the code to use non-deprecated CLR methods as well as making it work for AutoCAD, of course.]

    While .NET doesn't provide something as simple as an Eval() function, it actually provides something much more interesting. The CLR exposes the ability to compile and execute source code in .NET languages for which implementations of the CodeDomProvider protocol have been provided.

    The Microsoft.CSharp namespace, for instance, contains the CSharpCodeProvider class, which allows you to specify and compile C# code from any .NET language. Microsoft.VisualBasic contains VBCodeProvider, which does the same for VB.NET.

    Which means that it's actually very easy to implement dynamic metaprogramming in a homogeneous or heterogeneous fashion from .NET. Yay! :-)

    As I had some time, and decided that implementing this for both C# and VB.NET would tell the story nicely, I've provided code below for both environments.

    Here's the C# code, which shows how to compile and execute C# (homogeneous) and VB.NET (heterogeneous) code:

    using Autodesk.AutoCAD.Runtime;

    using Autodesk.AutoCAD.ApplicationServices;

    using Autodesk.AutoCAD.EditorInput;

    using Microsoft.CSharp;

    using Microsoft.VisualBasic;

    using System.CodeDom.Compiler;

    using System.Reflection;

    using System.Text;

    using System;


    namespace Metaprogramming

    {

      public class Commands

      {

        const string acadFolder =

          "c:\\Program Files\\Autodesk\\AutoCAD 2008\\";


        // EvalCS: Evaluates C# source


        public static object EvalCS(string csCode)

        {

          DocumentCollection dm =

              Application.DocumentManager;

          Editor ed = dm.MdiActiveDocument.Editor;


          CSharpCodeProvider cs = new CSharpCodeProvider();

          CompilerParameters cp = new CompilerParameters();


          cp.ReferencedAssemblies.Add("system.dll");

          cp.ReferencedAssemblies.Add(acadFolder + "acdbmgd.dll");

          cp.ReferencedAssemblies.Add(acadFolder + "acmgd.dll");


          cp.CompilerOptions = "/t:library";

          cp.GenerateInMemory = true;


          StringBuilder sb = new StringBuilder();

          sb.Append("using System;\n");

          sb.Append("using Autodesk.AutoCAD.Runtime;\n");

          sb.Append(

            "using Autodesk.AutoCAD.ApplicationServices;\n"

          );

          sb.Append("using Autodesk.AutoCAD.DatabaseServices;\n");

          sb.Append("using Autodesk.AutoCAD.EditorInput;\n");

          sb.Append("using Autodesk.AutoCAD.Geometry;\n");


          sb.Append("namespace CSCodeEval{\n");

          sb.Append("public class CSCodeEval{\n");

          sb.Append("public object EvalCode(){\n");

          sb.Append("return " + csCode + ";\n");

          sb.Append("}\n");

          sb.Append("}\n");

          sb.Append("}\n");


          CompilerResults cr =

            cs.CompileAssemblyFromSource(cp, sb.ToString());

          if (cr.Errors.Count > 0)

          {

            ed.WriteMessage(

              "\nErrors evaluating C# code (" +

              cr.Errors.Count +

              "):"

            );

            for (int i = 0; i < cr.Errors.Count; i++)

            {

              ed.WriteMessage(

                "\nLine number " +

                cr.Errors[i].Line + ": " +

                cr.Errors[i].ErrorText

              );

            }

            return null;

          }


          System.Reflection.Assembly a =

            cr.CompiledAssembly;

          object o =

            a.CreateInstance("CSCodeEval.CSCodeEval");


          Type t = o.GetType();

          MethodInfo mi = t.GetMethod("EvalCode");


          object s = mi.Invoke(o, null);

          return s;

        }


        // EvalVB: Evaluates VB source


        public static object EvalVB(string vbCode)

        {

          DocumentCollection dm =

              Application.DocumentManager;

          Editor ed = dm.MdiActiveDocument.Editor;


          VBCodeProvider vb = new VBCodeProvider();

          CompilerParameters cp = new CompilerParameters();


          cp.ReferencedAssemblies.Add("system.dll");

          cp.ReferencedAssemblies.Add(acadFolder + "acdbmgd.dll");

          cp.ReferencedAssemblies.Add(acadFolder + "acmgd.dll");


          cp.CompilerOptions = "/t:library";

          cp.GenerateInMemory = true;


          StringBuilder sb = new StringBuilder();

          sb.Append("Imports System\n");

          sb.Append("Imports Autodesk.AutoCAD.Runtime\n");

          sb.Append(

            "Imports Autodesk.AutoCAD.ApplicationServices\n"

          );

          sb.Append(

            "Imports Autodesk.AutoCAD.DatabaseServices\n"

          );

          sb.Append("Imports Autodesk.AutoCAD.EditorInput\n");

          sb.Append("Imports Autodesk.AutoCAD.Geometry\n");


          sb.Append("Namespace VBCodeEval\n");

          sb.Append("Public Class VBCodeEval\n");

          sb.Append("Public Function EvalCode() As Object\n");

          sb.Append("Return " + vbCode + " \n");

          sb.Append("End Function\n");

          sb.Append("End Class\n");

          sb.Append("End Namespace\n");


          CompilerResults cr =

            vb.CompileAssemblyFromSource(cp, sb.ToString());

          if (cr.Errors.Count > 0)

          {

            ed.WriteMessage(

              "\nErrors evaluating VB code (" +

              cr.Errors.Count +

              "):"

            );

            for (int i = 0; i < cr.Errors.Count; i++)

            {

              ed.WriteMessage(

                "\nLine number " +

                cr.Errors[i].Line + ": " +

                cr.Errors[i].ErrorText

              );

            }

            return null;

          }


          System.Reflection.Assembly a =

            cr.CompiledAssembly;

          object o =

            a.CreateInstance("VBCodeEval.VBCodeEval");


          Type t = o.GetType();

          MethodInfo mi = t.GetMethod("EvalCode");


          object s = mi.Invoke(o, null);

          return s;

        }


        [CommandMethod("EV")]

        public void Eval()

        {

          DocumentCollection dm =

              Application.DocumentManager;

          Editor ed = dm.MdiActiveDocument.Editor;


          const string csCode =

            "typeof(Autodesk.AutoCAD." +

            "ApplicationServices.Application)";


          const string vbCode =

            "GetType(Autodesk.AutoCAD." +

            "ApplicationServices.Application)";


          ed.WriteMessage("\nEvaluating C# code:\n" + csCode);


          object result = EvalCS(csCode);


          if (result != null)

            ed.WriteMessage(

              "\nC# code returned: " +

              result.ToString()

            );


          ed.WriteMessage("\nEvaluating VB code:\n" + vbCode);


          result = EvalVB(vbCode);


          if (result != null)

            ed.WriteMessage(

              "\nVB code returned: " +

              result.ToString()

            );

        }

      }

    }

    Here's the VB.NET code, which shows how to compile and execute C# (heterogeneous) and VB.NET (homogeneous) code:

    Imports Autodesk.AutoCAD.Runtime

    Imports Autodesk.AutoCAD.ApplicationServices

    Imports Autodesk.AutoCAD.EditorInput

    Imports Microsoft.CSharp

    Imports Microsoft.VisualBasic

    Imports System.CodeDom.Compiler

    Imports System.Reflection

    Imports System.Text

    Imports System


    Namespace Metaprogramming


      Public Class Commands


        Private Const acadFolder As String = _

          "c:\\Program Files\\Autodesk\\AutoCAD 2008\\"


        'EvalCS: Evaluates C# source


        Public Shared Function EvalCS(ByVal csCode As String) _

        As Object


          Dim dm As DocumentCollection = _

            Application.DocumentManager

          Dim ed As Editor = dm.MdiActiveDocument.Editor


          Dim cs As CSharpCodeProvider = New CSharpCodeProvider

          Dim cp As CompilerParameters = New CompilerParameters

          cp.ReferencedAssemblies.Add("system.dll")

          cp.ReferencedAssemblies.Add(acadFolder + "acdbmgd.dll")

          cp.ReferencedAssemblies.Add(acadFolder + "acmgd.dll")

          cp.CompilerOptions = "/t:library"

          cp.GenerateInMemory = True


          Dim sb As StringBuilder = New StringBuilder

          sb.Append("using System;" & vbLf)

          sb.Append("using Autodesk.AutoCAD.Runtime;" & vbLf)

          sb.Append( _

            "using Autodesk.AutoCAD.ApplicationServices;" & vbLf)

          sb.Append( _

            "using Autodesk.AutoCAD.DatabaseServices;" & vbLf)

          sb.Append("using Autodesk.AutoCAD.EditorInput;" & vbLf)

          sb.Append("using Autodesk.AutoCAD.Geometry;" & vbLf)

          sb.Append("namespace CSCodeEval{" & vbLf)

          sb.Append("public class CSCodeEval{" & vbLf)

          sb.Append("public object EvalCode(){" & vbLf)

          sb.Append("return " + csCode + ";" & vbLf)

          sb.Append("}" & vbLf)

          sb.Append("}" & vbLf)

          sb.Append("}" & vbLf)


          Dim cr As CompilerResults = _

            cs.CompileAssemblyFromSource(cp, sb.ToString)


          If (cr.Errors.Count > 0) Then


            ed.WriteMessage( _

              vbLf & "Errors evaluating C# code (" + _

              cr.Errors.Count.ToString + "):")


            Dim i As Integer


            For i = 0 To cr.Errors.Count - 1

              ed.WriteMessage( _

                vbLf & "Line number " + _

                cr.Errors(i).Line.ToString + ": " + _

                cr.Errors(i).ErrorText)

            Next


            Return Nothing


          End If


          Dim a As System.Reflection.Assembly = _

            cr.CompiledAssembly

          Dim o As Object = _

            a.CreateInstance("CSCodeEval.CSCodeEval")

          Dim t As Type = o.GetType

          Dim mi As MethodInfo = t.GetMethod("EvalCode")

          Dim s As Object = mi.Invoke(o, Nothing)


          Return s


        End Function


        'EvalVB: Evaluates VB source


        Public Shared Function EvalVB(ByVal vbCode As String) _

        As Object


          Dim dm As DocumentCollection = _

            Application.DocumentManager

          Dim ed As Editor = dm.MdiActiveDocument.Editor


          Dim vb As VBCodeProvider = New VBCodeProvider

          Dim cp As CompilerParameters = New CompilerParameters

          cp.ReferencedAssemblies.Add("system.dll")

          cp.ReferencedAssemblies.Add(acadFolder + "acdbmgd.dll")

          cp.ReferencedAssemblies.Add(acadFolder + "acmgd.dll")

          cp.CompilerOptions = "/t:library"

          cp.GenerateInMemory = True


          Dim sb As StringBuilder = New StringBuilder

          sb.Append("Imports System" & vbLf)

          sb.Append("Imports Autodesk.AutoCAD.Runtime" & vbLf)

          sb.Append( _

            "Imports Autodesk.AutoCAD.ApplicationServices" & vbLf)

          sb.Append( _

            "Imports Autodesk.AutoCAD.DatabaseServices" & vbLf)

          sb.Append("Imports Autodesk.AutoCAD.EditorInput" & vbLf)

          sb.Append("Imports Autodesk.AutoCAD.Geometry" & vbLf)

          sb.Append("Namespace VBCodeEval" & vbLf)

          sb.Append("Public Class VBCodeEval" & vbLf)

          sb.Append("Public Function EvalCode() As Object" & vbLf)

          sb.Append("Return " + vbCode + " " & vbLf)

          sb.Append("End Function" & vbLf)

          sb.Append("End Class" & vbLf)

          sb.Append("End Namespace" & vbLf)


          Dim cr As CompilerResults = _

            vb.CompileAssemblyFromSource(cp, sb.ToString)


          If (cr.Errors.Count > 0) Then


            ed.WriteMessage( _

              vbLf & "Errors evaluating VB code (" + _

              cr.Errors.Count.ToString + "):")


            Dim i As Integer

            For i = 0 To cr.Errors.Count - 1

              ed.WriteMessage( _

                vbLf & "Line number " + _

                cr.Errors(i).Line.ToString + ": " + _

                cr.Errors(i).ErrorText)

            Next


            Return Nothing


          End If


          Dim a As System.Reflection.Assembly = _

            cr.CompiledAssembly

          Dim o As Object = _

            a.CreateInstance("VBCodeEval.VBCodeEval")

          Dim t As Type = o.GetType

          Dim mi As MethodInfo = t.GetMethod("EvalCode")

          Dim s As Object = mi.Invoke(o, Nothing)

          Return s


        End Function


        <CommandMethod("EV")> _

        Public Sub Eval()


          Dim dm As DocumentCollection = _

            Application.DocumentManager

          Dim ed As Editor = dm.MdiActiveDocument.Editor


          Const csCode As String = _

            "typeof(Autodesk.AutoCAD." + _

            "ApplicationServices.Application)"


          Const vbCode As String = _

            "GetType(Autodesk.AutoCAD." + _

            "ApplicationServices.Application)"


          ed.WriteMessage( _

            vbLf + "Evaluating C# code:" + _

            vbLf + csCode)


          Dim result As Object = EvalCS(csCode)


          If (Not result Is Nothing) Then

            ed.WriteMessage( _

              vbLf + "C# code returned: " + _

              result.ToString)

          End If


          ed.WriteMessage( _

            vbLf + "Evaluating VB code:" + _

            vbLf + vbCode)


          result = EvalVB(vbCode)


          If (Not result Is Nothing) Then

            ed.WriteMessage( _

              vbLf + "VB code returned: " + _

              result.ToString)

          End If


        End Sub


      End Class


    End Namespace

    When we run the "ev" command, implemented by either of the above code fragments, we see these results:

    Command: ev

    Evaluating C# code:

    typeof(Autodesk.AutoCAD.ApplicationServices.Application)

    C# code returned: Autodesk.AutoCAD.ApplicationServices.Application

    Evaluating VB code:

    GetType(Autodesk.AutoCAD.ApplicationServices.Application)

    VB code returned: Autodesk.AutoCAD.ApplicationServices.Application

    TrackBack

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

    Listed below are links to weblogs that reference Metaprogramming with AutoCAD - Part 2:

    » Through the Interface: Metaprogramming with AutoCAD - Part 2 from CadKicks.com
    You've been kicked (a good thing) - Trackback from CadKicks.com [Read More]

    Comments

    Very good stuff. If you get a chance take a look at stuff I've been messing around with in C# and IronPython as you might find it interesting.

    http://code.google.com/p/pyacaddotnet/

    Excellent post. I am a SolidWorks bigot / Autodesk critic but this is a great post. I look forward to trying this.

    "The quotations mechanism in F# appears to be the way to represent, analyse and execute program structure. This article describes the concepts, although it's quite deep and doesn't address the case that's most immediately interesting to AutoCAD develeopers: the ability to evaluate and execute code represented as a string."

    Hi Kean.

    I found this comment interesting, as I haven't come across many situations where the ability to evaluate and execute code represented as a string was useful.

    I have done my share of 'metaprogramming' in AutoLISP (and also with the formalization of metaprogramming in Common LISP - defmacro), but none of that involved manipulating source code in string form.

    Perhaps you could show or cite a practicle example of where evaluating and executing code stored as a string is useful to AutoCAD programmers?

    Thanks,
    Tony

    Hi Tony,

    The instances I've encountered this are typically when code fragments are stored in a DWG or an external database, to be retrieved and executed as needed.

    Let me refer you to the comment that started this topic off, as that may help. Search for the comment from Thomas on this page.

    Regards,

    Kean


    You've been kicked (a good thing) - Trackback from CadKicks.com
    http://cadkicks.com/adkautocad/Through_the_Interface_Metaprogramming_with_AutoCAD_Part_2

    Verify your Comment

    Previewing your Comment

    This is only a preview. Your comment has not yet been posted.

    Working...
    Your comment could not be posted. Error type:
    Your comment has been posted. Post another comment

    The letters and numbers you entered did not match the image. Please try again.

    As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

    Having trouble reading this image? View an alternate.

    Working...

    Post a comment

    Feed & Share

    Search