A cool tool for identifying .NET API enhancements

A member of our AutoCAD Engineering team pointed me at this very cool tool - the Framework Design Studio. The wiki doesn't really do it justice, so here's a post describing what the tool does. I also found it a little trick to get to the download, so here's the latest version at the time of posting.

So what's so cool about this tool, as a developer working with AutoCAD? It seems as though the tool was primarily intended to allow platform developers to identify when their changes impact API compatibility, but it's also useful for developers working on a platform to identify the new API features - and many of the potential migration issues - in a particular release.

For example, after launching the tool, I added acmgd.dll from the AutoCAD 2008 application folder (using Project -> Add Assembly). Once added, I right-clicked the assembly in the left-hand tree and selected "Select Assemblies to Compare...". I then added the acmgd.dll assembly from the AutoCAD 2009 application folder:

Assemblies_to_compare_2

From there you can navigate to acmgd.dll in the left-hand pane and select the Diff tab in the right, and then browse to an object that is highlighted as containing differences:

Comparison_of_application_namespace

It's also worth noting that you can also use the assemblies provided in the inc (or inc-win32) folder of the ObjectARX SDK, if you don't want to have to install the full product to compare API versions.

Have fun! :-)

April 24, 2008 in AutoCAD, AutoCAD .NET, Visual Studio | Permalink | Comments (2) | TrackBack

A good resource for finding out about the newer features in C#

I've mentioned before how much I appreciate the content on MSDN's Channel 9.

For those of you who are familiar with C# 1.0, but haven't yet taken the time to dive into the language features introduced since then, I strongly recommend the "whirlwind" series posted on Channel 9.

Here's a quick description of what these whirlwinds are all about:

Whirlwinds are bite-sized webcasts, each is shorter than 15 minutes. You can start anywhere in the series to learn about the parts you're most interested in.

Here are links to the individual whirlwinds:

I like the short format and can see it being relevant to individual (or small groups of) API features exposed by our products (a little like mini-DevTV sessions). If any of you have input about the concept, please post a comment or send me an email, ideally letting me know of specific topics in which you'd be interested.

April 14, 2008 in Training, Visual Studio | Permalink | Comments (0) | TrackBack

Using Reflector to diagnose tail call optimization in F#

I've talked about Lutz Roeder's Reflector tool a couple of times and it's proven to be very useful to me, once again.

I mentioned in my last post about some problems I was having with tail recursion, and my choice to replace certain recursive functions with iterative versions. Today we're going to use Reflector to take a look under the hood of some compiled assemblies, to determine which recursive functions have been optimised correctly and which have not.

Let's start by taking a look at the two recursive functions I mentioned last time, starting with the most simple:

// A recursive function to show the contents of a list


let rec drawPointList (x:Point3d list) =

match x with

| [] -> ()

  | h :: t ->

  ed.DrawVector(h,h,1,true)

    drawPointList t

Here's what we see when we open the compiled assembly in Reflector and disassemble the equivalent function into C#:

[Serializable]

internal class drawPointList@140 : FastFunc<List<Point3d>, Unit>

{

  // Fields

  public Editor ed;


  // Methods

  public drawPointList@140(Editor ed)

  {

    this.ed = ed;

  }


  public override Unit Invoke(List<Point3d> x)

  {

    while (true)

    {

      List<Point3d> list = x;

      if (!(list is List<Point3d>._Cons))

      {

          break;

      }

      List<Point3d> list2 = ((List<Point3d>._Cons) list)._Cons2;

      Point3d from = ((List<Point3d>._Cons) list)._Cons1;

      this.ed.DrawVector(from, from, 1, true);

      x = list2;

    }

    return null;

  }

}



You can see that the implementation of Invoke contains a while loop, which loops forever (until the list is empty), rather than calling recursively into itself.

Now let's take a look at the other, more complex function, which generates a list of points via recursion:

// A function that accepts an ObjectId and returns

// a list of random points on its surface


let rec getNPoints n (sol:Solid3d) =

  if n <= 0 then

    []

  else

    let mp = sol.MassProperties


    let pl = new Plane()


    pl.Set(mp.Centroid,randomVector3d sol.ObjectId.OldId)

    let reg = sol.GetSection(pl)

    let ray = new Ray()

    ray.BasePoint <- mp.Centroid

    ray.UnitDir <- randomVectorOnPlane pl


    let pts = new Point3dCollection()

    reg.IntersectWith

      (ray,

       Intersect.OnBothOperands,

       pts,

       0, 0)


    pl.Dispose()

    reg.Dispose()

    ray.Dispose()


    let ptlist = Seq.untyped_to_list pts

    ptlist @ getNPoints (n - pts.Count) sol

This function, when compiled by the F# compiler integrated into Visual Studio and disassembled by Reflector, equates to this C# code:

[Serializable]

internal class getNPoints@101T<T> : OptimizedClosures.FastFunc2<int, Solid3d, List<T>>

{

  // Fields

  public TypeFunc _self0;


  // Methods

  public getNPoints@101T(TypeFunc _self0)

  {

    this._self0 = _self0;

  }


  public override List<T> Invoke(int n, Solid3d sol)

  {

    TypeFunc func = this._self0;

    int num = n;

    int t = 0;

    if ((num > t) ^ true)

    {

      return List<T>.Nil_uniq;

    }

    Solid3dMassProperties h = sol.MassProperties;

    Plane plane = new Plane();

    plane.Set(h.Centroid, MyApplication.randomVector3d<int>(sol.ObjectId.OldId));

    Region section = sol.GetSection(plane);

    Ray entityPointer = new Ray();

    entityPointer.BasePoint = h.Centroid;

    entityPointer.UnitDir = MyApplication.randomVectorOnPlane<Plane>(plane);

    Point3dCollection points = new Point3dCollection();

    section.IntersectWith(entityPointer, Intersect.OnBothOperands, points, 0, 0);

    plane.Dispose();

    section.Dispose();

    entityPointer.Dispose();

    List<T> list = Seq.untyped_to_list<Point3dCollection, T>(points);

    Solid3d u = sol;

    int num2 = n - points.Count;

    return Pervasives.op_Append<T>(FastFunc<int, Solid3d>.InvokeFast2<List<T>>((FastFunc<int, FastFunc<Solid3d, List<T>>>) func.Specialize<T>(), num2, u), list);

  }

}

While a little harder to follow (please don't worry about the details), we can see that the recursive tail call has not been optimised out, as we don't have an iterative construct (e.g. a while loop). Instead we see a call to InvokeFast2 (which will result in Invoke being called recursively), prior to the value being returned.

Why exactly this case hasn't been handled isn't clear to me - in fact I'm pretty sure it's a bug in F#, which I will go ahead and report - but it is clear that (at least for now) we need to change the implementation if we're to avoid a stack overflow. And that's fine: the last post contains an iterative version we can use.

Finallly we're going to take a look at parts of the assembly created by building the code in the recent Robotic Hatching posts. As I mentioned previously, I ended up hitting problems when creating 400 or so segments in the polyline hatch when using my F# implementation. Rather than automatically changing each of the recursive functions (those prefixed by "let rec") to iterative versions, I checked the disassembly listings one by one until I found the problematic one:

// A recursive function to get the points

// for our path


let

  if times <= 0 then

    []

  else

    try

      let pt =

        nextBoundaryPoint cur start doTrace

      (pt :: definePath pt (times-1))

    with exn ->

      if exn.Message = "Could not get point" then

        definePath start times

      else

        failwith exn.Message

rec definePath start times =

Here's how the compiled (and then disassembled/reassembled) function looks in C#:

[Serializable]

internal class definePath@281 : OptimizedClosures.FastFunc2<Point3d, int, List<Point3d>>

{

  // Fields

  public Curve cur;

  public bool doTrace;


  // Methods

  public definePath@281(bool doTrace, Curve cur)

  {

    this.doTrace = doTrace;

    this.cur = cur;

  }


  public override List<Point3d> Invoke(Point3d start, int times)

  {

    int num = times;

    int num2 = 0;

    if ((num > num2) ^ true)

    {

      return List<Point3d>.Nil_uniq;

    }

    try

    {

      Point3d pt = Commands.nextBoundaryPoint(this.cur, start, this.doTrace);

      return new List<Point3d>._Cons(pt, FastFunc<Point3d, int>.InvokeFast2<List<Point3d>>(this, pt, times - 1));

    }

    catch (object obj1)

    {

      Exception exn = (Exception) obj1;

      string message = exn.Message;

      string b = "Could not get point";

      if (string.Equals(message, b))

      {

        return FastFunc<Point3d, int>.InvokeFast2<List<Point3d>>(this, start, times);

      }

      return Operators.failwith<List<Point3d>>(exn.Message);

    }

  }

}

This one is actually only recurses when we catch an exception we expect to occur (and actually have thrown ourselves elsewhere in the code) occasionally during run-time. A better implementation - one that F#'s compiler is able to tail call optimise and meets my aesthetic requirements - is here:

// An iterative function to get the points

// for our path


let definePath start times =

  let pts = new Point3dCollection()

  pts.Add(start) |> ignore


  let mutable i = 0

  while i < times do

    try

      let pt =

        nextBoundaryPoint

          cur pts.[pts.Count-1] doTrace runAsync

      pts.Add(pt) |> ignore

      i <- i + 1

    with exn ->

      if exn.Message <> "Could not get point" then

        failwith exn.Message


  Seq.untyped_to_list pts

I won't bother showing this function's C# equivalent, as determined by Reflector, you should just trust me that it's better. :-)

I did change the implementation slightly, so we no longer add our first vertex when we create the polyline - we simply add it to the array of vertices, which is cleaner - and we also have to pass a start index of 0 into the definePath function, rather than 1. Very minor changes, but they actually do make the code slightly simpler.

Anyway, this kind of analysis and control is made possible by the fact that F# compiles to IL, just like the other .NET languages. This allows us to take advantage of existing tools for code analysis & obfuscation, and ultimately give us better, more consistent control than we would get from a functional programming implementation outside of .NET.

February 27, 2008 in AutoCAD, AutoCAD .NET, F#, Visual Studio | Permalink | Comments (7) | TrackBack

Source now available for the .NET Framework 3.5

A quick post, for now, just to point you to this blog:

http://blogs.msdn.com/sburke/archive/2008/01/16/configuring-visual-studio-to-debug-net-framework-source-code.aspx

This will only work with Visual Studio 2008, it seems, so I haven't yet tested this out myself (I tend to be a laggard when it comes to Visual Studio, for some reason).

A quick note on what I've been up to in my spare time: I'm currently diving deeply into F#, to prepare for some internal presentations I'll be giving in February. As part of the exercise I went up to the attic and dusted off the 3.5" floppies containing my old final year project from my Computer Science studies, which uses a functional programming language called Miranda to model the behaviour of a Motorola 6800 processor. I've managed to convert this to F#, and am now seeing if I can get it to show some results.

I'll be back posting  more regularly next week, I hope.

January 17, 2008 in F#, Visual Studio | Permalink | Comments (0) | TrackBack

Microbenchmarking C# code

Jeremy Tammik, from our DevTech EMEA team, pointed me to this useful and interesting site:

http://www.yoda.arachsys.com/csharp/benchmark.html

It introduces a very easy way to benchmark functions in your application by simply tagging them with a [Benchmark] attribute (you also need to have included the C# file posted on the above site in your project, of course).

Jeremy also highlighted a very pertinent paragraph in the above site:

Use local variables where possible.

The CLR can do a more optimisations on code which doesn't (for the most part) "escape" from just local variables. For instance, it doesn't need to worry about other threads tampering with the variables. That's the reason the second example copies the number of iterations into a local variable before running the loop, and copies the result out of a local variable into a class variable right at the very end. This may or may not make a significant difference to your test (on the current CLR), but I believe it's good practice anyway - although you need to bear this in mind when considering using the results in a real application!

That's it for this post - I'll be back on Friday with my last post before the New Year (assuming I manage to stay away from my computer during our annual, end-of-year office shut-down).

December 19, 2007 in Visual Studio | Permalink | Comments (3) | TrackBack

Metaprogramming with AutoCAD - Part 3

In parts 1 & 2 of this series we looked at metaprogramming with AutoCAD using AutoLISP and VB(A), and then using VB.NET and C#.

In this post we're going to look at what's possible from F#, through the lens of my relative inexperience with the language, of course.

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. In fact, this doesn't yet appear to be part of the F# language, according to this thread, which helped me formulate the below F# version of the code I showed in my last post.

I would like to see a Microsoft.FSharp.FSharpCodeProvider class allowing execution of F# code provided as a string. I don't know whether the plan is to provide this - or something like it - but I can certainly see it being of use.

What we can do today is execute C# and VB.NET code, just as we did in the previous post, but this time the generation language is F#. The C# and VB.NET code we create is heterogeneous from the perspective of F#, of course.

Here's the F# code:

#light


module MyNamespace.MyApplication


// Import managed assemblies


#I @"C:\Program Files\Autodesk\AutoCAD 2008"


#r "acdbmgd.dll"

#r "acmgd.dll"


open System

open System.CodeDom.Compiler

open System.Reflection;

open Microsoft.VisualBasic;

open Microsoft.CSharp;

open Autodesk.AutoCAD.Runtime

open Autodesk.AutoCAD.ApplicationServices

open Autodesk.AutoCAD.DatabaseServices


let acadFolder =

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


let CompileVBExpression (expression:string) =

    let source =

        "Imports System\n" ^

        "Imports Autodesk.AutoCAD.Runtime\n" ^

        "Imports Autodesk.AutoCAD.ApplicationServices\n" ^

        "Imports Autodesk.AutoCAD.DatabaseServices\n" ^

        "Imports Autodesk.AutoCAD.EditorInput\n" ^

        "Imports Autodesk.AutoCAD.Geometry\n" ^

        "Namespace VBCodeEval\n" ^

        "Public Class VBCodeEval\n" ^

        "Public Function EvalCode() As Object\n" ^

        "Return " ^ expression ^ "\n" ^

        "End Function\n" ^

        "End Class\n" ^

        "End Namespace"


    let provider = new VBCodeProvider()

    let parameters = new CompilerParameters()

    ignore

      (parameters.ReferencedAssemblies.Add

        ("system.dll"))

    ignore

      (parameters.ReferencedAssemblies.Add

        (acadFolder ^ "acdbmgd.dll"))

    ignore

      (parameters.ReferencedAssemblies.Add

        (acadFolder ^ "acmgd.dll"))

    parameters.CompilerOptions <- "/t:library"

    parameters.GenerateInMemory <- true

    provider.CompileAssemblyFromSource(parameters, [|source|])


let RunVBCode (expression:string) =

    let results = CompileVBExpression expression

    if not results.Errors.HasErrors then

        let a = results.CompiledAssembly

        let o = a.CreateInstance("VBCodeEval.VBCodeEval")

        let t = o.GetType()

        let mi = t.GetMethod("EvalCode")

        mi.Invoke(o, null)

    else

        null


let CompileCSExpression (expression:string) =

    let source =

        "using System;\n" ^

        "using Autodesk.AutoCAD.Runtime;\n" ^

        "using Autodesk.AutoCAD.ApplicationServices;\n" ^

        "using Autodesk.AutoCAD.DatabaseServices;\n" ^

        "using Autodesk.AutoCAD.EditorInput;\n" ^

        "using Autodesk.AutoCAD.Geometry;\n" ^

        "namespace CSCodeEval{\n" ^

        "public class CSCodeEval{\n" ^

        "public object EvalCode(){\n" ^

        "return " ^ expression ^ ";\n" ^

        "}\n" ^

        "}\n" ^

        "}"


    let provider = new CSharpCodeProvider();

    let parameters = new CompilerParameters();

    ignore

      (parameters.ReferencedAssemblies.Add

        ("system.dll"))

    ignore

      (parameters.ReferencedAssemblies.Add

        (acadFolder ^ "acdbmgd.dll"))

    ignore

      (parameters.ReferencedAssemblies.Add

        (acadFolder ^ "acmgd.dll"))

    parameters.CompilerOptions <- "/t:library"

    parameters.GenerateInMemory <- true

    provider.CompileAssemblyFromSource(parameters, [|source|])


let RunCSCode (expression:string) =

    let results = CompileCSExpression expression

    if not results.Errors.HasErrors then

        let a = results.CompiledAssembly

        let o = a.CreateInstance("CSCodeEval.CSCodeEval")

        let t = o.GetType()

        let mi = t.GetMethod("EvalCode")

        mi.Invoke(o, null)

    else

        null


[<CommandMethod("ev")>]

let evaluate () =


  let csCode =

    "typeof(Autodesk.AutoCAD." ^

    "ApplicationServices.Application)"


  let vbCode =

    "GetType(Autodesk.AutoCAD." ^

    "ApplicationServices.Application)"


  let ed =

    Autodesk.AutoCAD.ApplicationServices.

      Application.DocumentManager.

        MdiActiveDocument.Editor;


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


  let csRes = RunCSCode csCode


  if csRes != null then

    ed.WriteMessage("\nC# code returned: " ^ csRes.ToString());


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


  let vbRes = RunVBCode vbCode


  if vbRes != null then

    ed.WriteMessage("\nVB code returned: " ^ vbRes.ToString()); 

And when we run the "ev" command, we see the same results as with the VB.NET and C# implementation in the last post:

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

November 26, 2007 in AutoCAD, AutoCAD .NET, F#, Visual Studio | Permalink | Comments (1) | TrackBack

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

November 22, 2007 in AutoCAD, AutoCAD .NET, Visual Studio | Permalink | Comments (5) | TrackBack