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