Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Consume UWP app service from F#

I am resorted to create a Universal Windows Platform (UWP) app service provider in C# since the library I want to use is only available from within UWP apps.

However, I need to write a (console) application in F# which would then call the above UWP service provider to take advantage of the library.

According to https://msdn.microsoft.com/en-us/windows/uwp/launch-resume/how-to-create-and-consume-an-app-service this can be done if both the service provider and consumer are written C# using UWP (since F# is not supported in UWP yet).

My question is whether I can call an UWP app service written in C# from an F# project? Could I create a solution with two projects, one of which is the UWP C# app service provider and the other is the F# console app service consumer?

like image 651
bugfoot Avatar asked Oct 18 '22 03:10

bugfoot


1 Answers

Here's an improved version of my code from the other post I linked to above, which additionally maps WinRT asynchronous objects to F# Async<_>, which makes working with some of the types much more pleasant.

open System.Reflection

type Asyncifier() =
    static let winrtExts = System.Type.GetType("System.WindowsRuntimeSystemExtensions, System.Runtime.WindowsRuntime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
    static let asTask = winrtExts.GetMethods() |> Seq.find(fun m -> m.Name = "AsTask" && m.IsGenericMethodDefinition && m.GetGenericArguments().Length = 1 && m.ReturnType.IsGenericType)
    static member FromTask<'t,'u>(t:System.Threading.Tasks.Task<'t>) =
        async {
            let! t = Async.AwaitTask t
            return box t :?> 'u
        }
    static member FromIAsyncObjThrough<'t>(argTy, o) : Async<'t> =
        typeof<Asyncifier>.GetMethod("FromTask").MakeGenericMethod(argTy, typeof<'t>).Invoke(null, [| asTask.MakeGenericMethod(argTy).Invoke(null, [|o|]) |]) :?> _

let getWindowsType (name:string) = 
    let ns = let parts = name.Split('.') in parts.[0] + "." + parts.[1]
    System.Type.GetType(sprintf "%s, %s, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime" name ns)

let IAsyncOperation = getWindowsType "Windows.Foundation.IAsyncOperation`1"

let (?) (o:obj) s : 'a =
    let rec build ty args =
        if Reflection.FSharpType.IsFunction ty then
            let dom, rng = Reflection.FSharpType.GetFunctionElements ty
            let mkArgs =
                if dom = typeof<unit> then
                    if Reflection.FSharpType.IsFunction rng then failwith "Unit as non-final argument in curried definition?"
                    fun _ -> args
                else
                    fun arg -> arg::args
            Reflection.FSharpValue.MakeFunction(ty, fun o -> build rng (mkArgs o))
        else
            let rcvr,ty,flags =
                match o with
                | :? System.Type as ty -> null,ty,BindingFlags.Static
                | _ -> o,o.GetType(),BindingFlags.Instance
            let flags = flags ||| BindingFlags.Public
            let meth =
                if Reflection.FSharpType.IsFunction typeof<'a> then
                    query {
                        for m in ty.GetMethods(flags) do
                        where (m.Name = s)
                        where (m.GetParameters().Length = args.Length)
                        exactlyOne
                    }                    
                else
                    ty.GetProperty(s, flags).GetGetMethod()
            let result = meth.Invoke(rcvr, args |> List.toArray)
            let resultTy =
                let rec resultTy ty = 
                    if Reflection.FSharpType.IsFunction ty then Reflection.FSharpType.GetFunctionElements ty |> snd |> resultTy
                    else ty
                resultTy typeof<'a>
            let (|GenericTypeInstance|_|) (genTy:System.Type) (ty:System.Type) =
                if ty.IsGenericType && ty.GetGenericTypeDefinition() = genTy then Some (ty.GetGenericArguments())
                else None
            let asyncTy = typedefof<Async<_>>
            match meth.ReturnType, resultTy with
            | GenericTypeInstance IAsyncOperation [|winRtTy|], GenericTypeInstance asyncTy [|returnTy|] ->
                // unwrap to F# async
                typeof<Asyncifier>.GetMethod("FromIAsyncObjThrough").MakeGenericMethod(returnTy).Invoke(null, [|winRtTy; result|])
            | _ -> result
    build typeof<'a> [] :?> 'a

And here's how you can use it to do OCR:

let Language = getWindowsType @"Windows.Globalization.Language"
let OcrEngine = getWindowsType "Windows.Media.Ocr.OcrEngine"
let BitmapDecoder = getWindowsType "Windows.Graphics.Imaging.BitmapDecoder"
let StorageFile = getWindowsType "Windows.Storage.StorageFile"

let enUs = Language.GetConstructor([|typeof<string>|]).Invoke([|"en-US"|])
let engine : obj = OcrEngine?TryCreateFromLanguage enUs

let getTextAsync (path:string) : Async<string> = async {
    let! file = StorageFile?GetFileFromPathAsync path
    let! stream = file?OpenReadAsync()
    let! decoder = BitmapDecoder?CreateAsync stream
    let! bitmap = decoder?GetSoftwareBitmapAsync()
    let! result = engine?RecognizeAsync bitmap
    return result?Text
}
like image 134
kvb Avatar answered Nov 15 '22 08:11

kvb