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    








« Using a jig from F# to create Spirograph patterns in AutoCAD | Main | Plugin of the Month featured in the March/April edition of AUGIWorld »

March 08, 2010

Creating demand-loading entries automatically for your AutoCAD application using C#, F# or VB.NET

This week I’m going to posting a few topics related to F#, as it feels as though I’ve been neglecting it, of late. And as this technology is going to hit the mainstream very soon – when Visual Studio 2010 ships – it seems all the more important to keep one’s F# skills honed.

We’re going to start the week with an F# equivalent to the code shown in this previous post, where we go through and reflect on the commands exposed by an assembly in order to create corresponding demand-loading Registry keys automatically.

We’ve shipped VB.NET and C# versions of this code as part of our Plugins of the Month, but I wanted to make sure the latest versions were posted here, all in one place:

I’ve made a few general changes to the previous approach:

  • More use of “using” to make sure that Registry keys get closed properly
    • Not that this caused any particular issues of which I’m aware, but it’s certainly cleaner
  • Use of CreateSubKey() to open the Applications key for write, or to create it if it doesn’t exist
    • This can apparently be a problem on some vertical versions of AutoCAD such as AutoCAD Electrical

Let’s take a look at the F# code, in particular:

module DemandLoading.RegistryUpdate

 

open System.Reflection

open System.Resources

open Microsoft.Win32

open Autodesk.AutoCAD.DatabaseServices

open Autodesk.AutoCAD.Runtime

 

// Get the information from a custom attribute, if of the right type

 

let commandInfo (rm : ResourceManager) (attb : obj) =

  let cma = attb :?> CommandMethodAttribute

  if cma <> null then

 

    // Harvest the information about each command

 

    match cma.LocalizedNameId with

    | null -> (cma.GlobalName, cma.GlobalName, cma.GroupName)

    | _ ->

      try

        (cma.GlobalName,

        rm.GetString(cma.LocalizedNameId),

        cma.GroupName)

      with _ ->

        (cma.GlobalName, cma.GlobalName, cma.GroupName)

  else

    (null, null, null)

 

let commandsFromMethod rm (meth : MethodInfo) =

  Array.map (commandInfo rm)

    (meth.GetCustomAttributes (typeof<CommandMethodAttribute>, true))

 

let commandsFromType assem (t : System.Type) =

  let rm = new ResourceManager(t.FullName, assem)

  rm.IgnoreCase <- true

  Array.map (commandsFromMethod rm) (t.GetMethods()) |> Array.concat

 

let commandsFromModule assem (m : Module) =

  Array.map (commandsFromType assem) (m.GetTypes()) |> Array.concat

 

let commandsFromAssembly assem =

  Array.map (commandsFromModule assem) (assem.GetModules(true))

    |> Array.concat

 

let createDemandLoadingEntries name path currentUser cmds =

 

  // Choose the Registry hive according to the inputs

 

  let hive =

    if currentUser then

      Registry.CurrentUser

    else

      Registry.LocalMachine

 

  // Check whether any valid commands exist (the command-names are

  // the first two entries in the tuple contained in the command

  // information array)

 

  let hasCmds =

    Array.exists (fun (a,b,c) -> a <> null && b <> null) cmds

 

  // And the same for groups, which is the third entry

 

  let hasGrps = Array.exists (fun (a,b,c) -> c <> null) cmds

 

  // Define whether to load the module on startup (if no commands)

  // or on command invocation (if any are defined)

 

  let flags = if hasCmds then 12 else 2

 

  // Open the main AutoCAD (or vertical) and "Applications" keys

 

  use ack =

    hive.OpenSubKey

      (HostApplicationServices.Current.RegistryProductRootKey,

      true)

  use appk = ack.CreateSubKey("Applications")

 

  // Already registered? Just return

 

  if not

    (Array.exists (fun x -> x = name) (appk.GetSubKeyNames())) then

 

    // Create the our application's root key and its values

 

    use rk = appk.CreateSubKey(name)

    rk.SetValue("DESCRIPTION", name, RegistryValueKind.String)

    rk.SetValue("LOADCTRLS", flags, RegistryValueKind.DWord)

    rk.SetValue("LOADER", path, RegistryValueKind.String)

    rk.SetValue("MANAGED", 1, RegistryValueKind.DWord)

 

    // Create a subkey if there are any commands...

 

    if hasCmds then

      use ck = rk.CreateSubKey("Commands")

      let createCommand (key : RegistryKey) info =

        match info with

        | (null, _, _) -> () // Ignore any null global commands

        | (_, null, _) -> () // Ignore any null local commands

        | (glob, loc, _) ->

          key.SetValue(glob,loc,RegistryValueKind.String)

 

      Array.iter (createCommand ck) cmds |> ignore

 

    // And the command groups, if there are any

 

    if hasGrps then

      use gk = rk.CreateSubKey("Groups")

      let createGroup (key : RegistryKey) info =

        match info with

        | (_, _, null) -> ()

        | (_, _, group) ->

          key.SetValue(group, group,RegistryValueKind.String)

 

      Array.iter (createGroup gk) cmds

 

let removeDemandLoadingEntries name currentUser =

 

  // Choose the Registry hive according to the input

 

  let hive =

    if currentUser then

      Registry.CurrentUser

    else

      Registry.LocalMachine

 

  // Open the main AutoCAD (or vertical) and "Applications" keys

 

  use ack =

    hive.OpenSubKey

      (HostApplicationServices.Current.RegistryProductRootKey)

  use appk = ack.OpenSubKey("Applications", true)

 

  // Delete the key with the same name as this assembly

 

  appk.DeleteSubKeyTree(name)

 

let RegisterForDemandLoading() =

 

  // Get the current assembly

 

  let assem = Assembly.GetExecutingAssembly()

 

  // Get the command information and create Registry

  // entries from it

 

  commandsFromAssembly assem

    |> createDemandLoadingEntries

        (assem.GetName().Name) assem.Location true

 

let UnregisterForDemandLoading() =

  removeDemandLoadingEntries

    (Assembly.GetExecutingAssembly().GetName().Name) true

I’ve tried to structure the F# code somewhat differently to the prior, more imperative versions of the code: it has more emphasis on the application of functions and the flow of data between them. It also makes use of higher order functions such as Array.map and Array.iter to apply functions to the contents of data structures (in this case arrays), and uses pattern-matching where sequences of if-then-else statements would have proven cumbersome.

Later in the week we’ll see this added to the Spirograph application, to create its demand-loading information automatically on startup, but first we’re going to need to see how to implement the IExtensionApplication interface from an F# application…

blog comments powered by Disqus

10 Random Posts