5

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?

bugfoot
  • 667
  • 7
  • 20
  • "since the library I want to use is only available from within UWP apps" => must be a world first :) – Bart Aug 18 '16 at 10:58
  • @Bart Windows.Media.Ocr (https://msdn.microsoft.com/en-us/library/windows/apps/windows.media.ocr) is such a world first according to my understanding - "The library works only with Windows Phone and Windows Store apps, which is a strong limitation." @ http://blog.a9t9.com/2015/09/ocr-api.html – bugfoot Aug 18 '16 at 11:08
  • 1
    There's a cloud API for you to use instead: https://ocr.space/OCRAPI – Bart Aug 18 '16 at 11:20
  • Thanks Bart, but cloud is not an option, the OCR should reside on the client side, and Microsoft.Media.Ocr needs to be specifically used, there is not much leeway in these options. – bugfoot Aug 18 '16 at 13:01
  • 1
    See http://stackoverflow.com/a/38354382/82959 – kvb Aug 18 '16 at 14:30
  • Thanks @kvb, I will check your solution out as it seems a better option than creating an UWP when in fact I don't need it. I'll get back to you whether it worked out for me. – bugfoot Aug 18 '16 at 15:06

1 Answers1

3

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
}
ildjarn
  • 62,044
  • 9
  • 127
  • 211
kvb
  • 54,864
  • 2
  • 91
  • 133
  • Thanks @kvb, it looks fabulous :-) Will check during the weekend! – bugfoot Aug 19 '16 at 08:24
  • @kvb Do I need to specify capabilities somehow still? The program tends to hang when I do certain things, like trying to retrieve a Bluetooth device reference. – Rei Miyasaka Apr 13 '17 at 01:29
  • If you're running from a desktop app (e.g. F# Interactive) then I don't think that should apply. Maybe post a new question with a simplified repro? – kvb Apr 13 '17 at 02:22