Registration icon indicating copy to clipboard operation
Registration copied to clipboard

Automatic wrapper generation for object handles

Open govert opened this issue 9 years ago • 1 comments

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.

govert avatar Jun 05 '15 17:06 govert

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 () = ()

obadz avatar Jun 07 '15 15:06 obadz