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:
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:
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:
- What's new in C# 2
- What's new in C# 3
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:
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

Atom

