Automatic wrapper generation for object handles
Object handles wrappers provide constructor and access functions for object instances (in contrast with the static methods currently supported by Excel-DNA). It would be convenient to generate these wrappers.
One sample project with an implementation of object handles, with some specialized features like batch updating, can be found in the (Samples repository)[https://github.com/Excel-DNA/Samples/tree/master/ObjectHandles].
Using the RTD / IExcelObservable implementation to provide deterministic lifetime management has proven to be robust.
There are quite a number of design issue to resolve in implementing the wrapper generation. Among these:
- Identifying classes, constructors and methods/properties to generate wrappers for
- Details of the wrappers
- Update processing - INotifyPropertyChanged etc.
I'd welcome discussion and suggestions on what would be useful in this area.
Here's some code I've written to do automatic object handle conversions and exceptions wrapping:
open System
open System.Collections.Generic
open System.Linq.Expressions
open ExcelDna.Integration
open ExcelDna.Registration
// XlCache module largely copied from https://github.com/bramjochems/MyExcelLib
// TODO:
// 1) Cache:
// Use a concurent datastructure
// Use pointers to determine object identity and don't use counters
// Handle RTD
// 2) Display F# types nicely
// 3) Log function arguments & types when reporting an exception
let log s =
// ExcelDna.Logging.LogDisplay.WriteLine s
ExcelDna.Logging.LogDisplay.RecordLine s
let rec typeString (t : Type) =
t.Name.Split('`').[0] +
if t.IsGenericType
then
t.GetGenericArguments()
|> Seq.map typeString
|> fun s -> "<" + String.Join(", ", s) + ">"
else ""
let toBriefString (o : obj) =
let maxChars = 15
let s = o.ToString()
if s.Length >= maxChars - 2
then s.Substring(0, maxChars - 2) + ".."
else s
// Inspired from ExcelDna/Source/ExcelDna.Integration/AssemblyLoader.cs
let isTypeSupported t =
t = typeof<float>
|| t = typeof<decimal>
|| t = typeof<int>
|| t = typeof<string>
|| t = typeof<bool>
|| t = typeof<DateTime>
|| t = typeof<float []>
|| t = typeof<float [,]>
|| t = typeof<unit>
type XlCache () =
/// The cache that holds objects indexed by a key
let cache = Dictionary<string, obj>()
let tagstore = Dictionary<string, int>()
member this.add (o : obj) =
let tag = typeString <| o.GetType()
let counter =
match tagstore.TryGetValue tag with
| true, c -> c + 1
| _ -> 1
tagstore.[tag] <- counter
let handle = sprintf "[%s #%d = %s]" tag counter (toBriefString o)
cache.[handle] <- o
handle
member this.drop handle =
if cache.ContainsKey(handle) then cache.Remove(handle) |> ignore
member this.lookup handle =
match cache.TryGetValue handle with
| true, value -> value
| _ -> failwithf "Could not find handle in cache: %s" handle
member this.reset () =
cache.Clear()
tagstore.Clear()
type XlExceptionHandler () =
inherit FunctionExecutionHandler()
override this.OnEntry args =
// log <| sprintf "%s: entering" args.FunctionName
()
override this.OnSuccess args =
// log <| sprintf "%s: succeeded with r = %s (%s)" args.FunctionName (toBriefString args.ReturnValue) (typeString <| args.ReturnValue.GetType())
()
override this.OnException args =
log <| sprintf "Caught exception in function %s" args.FunctionName
log <| sprintf "Arguments: %d" args.Arguments.Length
args.Arguments
|> Seq.mapi (fun i a -> sprintf "#%d: %s (%s)" (i + 1) (toBriefString a) (typeString <| a.GetType()))
|> Seq.iter log
log <| "Exception:"
args.Exception.ToString().Split('\n') |> Seq.iter log
override this.OnExit args =
// log <| sprintf "%s: exited" args.FunctionName
()
type XlAddin () =
interface IExcelAddIn with
member this.AutoOpen () =
let cache = XlCache ()
// ExcelDna.Logging.LogDisplay.DisplayOrder <- ExcelDna.Logging.DisplayOrder.NewestFirst
let paramConverter (t : Type) _ =
if isTypeSupported t
then
let p = Expression.Parameter(t)
Expression.Lambda(p :> Expression, p) // Identity
else
let p = Expression.Parameter(typeof<string>)
let m = cache.GetType().GetMethod("lookup")
let c = Expression.Call(Expression.Constant(cache), m, [ p :> Expression ])
Expression.Lambda(Expression.TypeAs(c, t) :> Expression, p)
let returnConverter (t : Type) _ =
let p = Expression.Parameter(t)
Expression.Lambda(
(if isTypeSupported t
then p :> Expression
else
let m = cache.GetType().GetMethod("add")
Expression.Call(Expression.Constant(cache), m, [ p :> Expression ]) :> Expression),
p
)
// let p = returnConverter typeof<string option> ()
// let l = paramConverter typeof<string option> ()
// let k = p.Compile().DynamicInvoke([| box (Some "hey") |])
// l.Compile().DynamicInvoke([| box k |]) :?> string option
let funcHandle =
FunctionExecutionConfiguration()
.AddFunctionExecutionHandler(Func<_, _> (fun _ -> XlExceptionHandler() :> IFunctionExecutionHandler))
let conv =
ParameterConversionConfiguration()
.AddParameterConversion(Func<_, _, _> paramConverter, null)
.AddReturnConversion(Func<_, _, _> returnConverter)
ExcelRegistration
.GetExcelFunctions()
.ProcessFunctionExecutionHandlers(funcHandle)
.ProcessParameterConversions(conv)
.RegisterFunctions()
// ExcelRegistration.GetExcelFunctions().RegisterFunctions()
log <| "Registration complete."
member this.AutoClose () = ()