-
Notifications
You must be signed in to change notification settings - Fork 19
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
Comments
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
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:
I'd welcome discussion and suggestions on what would be useful in this area.
The text was updated successfully, but these errors were encountered: