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