Kean Walmsley


  • About the Author
    Kean on Google+

July 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    








« 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]

blog comments powered by Disqus

10 Random Posts