Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic wrapper generation for object handles #6

Open
govert opened this issue Jun 5, 2015 · 1 comment
Open

Automatic wrapper generation for object handles #6

govert opened this issue Jun 5, 2015 · 1 comment

Comments

@govert
Copy link
Member

govert commented Jun 5, 2015

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.

@obadz
Copy link

obadz commented Jun 7, 2015

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants