Missing method exception while InvokeMember - f#

I'm creating a COM component in F#. The component is expected to be used from scripting.
The component code:
namespace WebUIPlugin
open System
open System.Windows
open System.Runtime.InteropServices
[<Guid("BAEF0C5B-EFA5-4868-8342-7A1E6F8F7AF4")>]
type IPlugin =
[<DispId(1)>]
abstract OpenFormFromFile : path:string -> unit
[<Guid("8D71E2DB-D718-4595-B856-58D14EEAEBB2");
ClassInterface(ClassInterfaceType.None);
ComVisible(true)>]
type Plugin = class
new () = {}
interface IPlugin with
member this.OpenFormFromFile(path) =
let showWindow =
let window = Window()
window.Show
UI.spawn showWindow |> ignore
end
end
I'm registering it with regasm /codebase Plugin.dll and it works well from scripting cscript test.js.
test.js is following:
var obj = new ActiveXObject("WebUIPlugin.Plugin");
obj.OpenFormFromFile("");
It even stops on breakpoint in OpenFormFromFile. So good so far.
Unfortunately, I cannot make it work from F#/C#:
let objectType = Type.GetTypeFromProgID("WebUIPlugin.Plugin")
let handler = Activator.CreateInstance(objectType)
objectType.InvokeMember("OpenFormFromFile", BindingFlags.InvokeMethod, null, handler, [|""|]) |> ignore
var objectType = Type.GetTypeFromProgID("WebUIPlugin.Plugin");
dynamic handler = Activator.CreateInstance(objectType);
objectType.InvokeMember("OpenFormFromFile", BindingFlags.InvokeMethod, null, handler, new object[]{""});
The code throws exception:
An unhandled exception of type System.MissingMethodException occurred in mscorlib.dll
Additional information: Attempted to access a missing member.
Everything (component, test C# and F# projects, regasm, cscript) is either in x64 or x86 consistently. With the same result - WSH script works, .NET assembly does not.

Related

How does Elmish.WPF marshall secondary windows into the main function?

How do the various #Windows (mainWindow, createWindow1, and createWindow2) get marshalled as parameters when calling the Elmish.WPF NewWindow code sample Program.fs as seen here ...
let main mainWindow (createWindow1: Func<#Window>) (createWindow2: Func<#Window>) =
let logger =
LoggerConfiguration()
.MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose)
.MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose)
.MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose)
.WriteTo.Console()
.CreateLogger()
let createWindow1 () = createWindow1.Invoke()
let createWindow2 () =
let window = createWindow2.Invoke()
window.Owner <- mainWindow
window
let init () = App.init
let bindings = App.bindings createWindow1 createWindow2
WpfProgram.mkSimple init App.update bindings
|> WpfProgram.withLogger (new SerilogLoggerFactory(logger))
|> WpfProgram.startElmishLoop mainWindow
???
What plumbing is happening in .NET, WPF, Elmish.WPF that organizes the reference found at the top of NewWindows.xaml to NewWindow.Core so that the F# function main is called with the windows marshalled and passed in in the right order?
As I finished the above question I determined what I think is the answer. Left the question in case it helps someone else. -RCHF
For reference I have thoroughly read the the Elmish.WPF Tutorial.
In arriving at this answer I found Getting started with Elmish.WPF, item #7 and #8 helpful.
In Elmish.WPF, the MainWindow.xaml`` file calls the C# code-behind file MainWindow.xaml.cs*must* have aStartElmish``` function with the following code …
private void StartElmish(object sender, EventArgs e)
{
this.Activated -= StartElmish;
Program.main(MainWindow, () => new Window1(), () => new Window2());
}
Here you see references to all three windows in the Elmish.WPF NewWindow code sample (see C# XAML) and F# Core) being marshalled into the subordinate F# main function.

F# XUnit test deadlocks when initializer has dependency

I am having problems with a test in a netcoreapp2.2 .net core test project.
Before the tests starts I need to fetch some data that will be shared between the tests.
However, when running the following test from command line it will hang.
Executing the test like this:
dotnet test --filter "Test async initialization"
The faulty code looks like this:
let c = new HttpClient (BaseAddress = (Uri "https://swapi.co/api/people/1/"))
let luke =
async {
return! c.GetStringAsync "" |> Async.AwaitTask
} |> Async.RunSynchronously
[<Fact>]
let ``Test async initialization`` () =
Assert.NotNull(luke)
While if I put the creation of the HttpClient inside the luke fetcher like this it works:
let luke =
let c = new HttpClient (BaseAddress = (Uri "https://swapi.co/api/people/1/"))
async {
return! c.GetStringAsync "" |> Async.AwaitTask
} |> Async.RunSynchronously
[<Fact>]
let ``Test async initialization`` () =
Assert.NotNull(luke)
This means I can't share the same HttpClient between different fetchers.
Anyone knows what is going on, and how to share the same client between multiple functions?
The problem is caused because the "initialization" code isn't really initialization code. Those are just two static fields that will be evaluated only when requested. If you debug the unit test you'll see that c and luke execute only when execution reaches the line
Assert.NotNull(luke)
If you use a decompiler like JustDecompile you'll see that the module's code is placed in a static class called Tests$ whose static constructor initializes its own c and luke properties. Test async initialization is placed in a Tests class with its own c and luke properties that delegate to the Tests$ class.
Long story sort, none of that "initialization" code runs until the value of luke is requested. I don't know why that ends up blocking the test, most likely there's a conflict with the test runner. It's enough that the initialization code doesn't run at initialization.
To make the initialization code run when it should, a "classic" test type can be used :
namespace MyTests
open System
open Xunit
open System.Net.Http
open Xunit.Abstractions
type Tests() =
static let c = new HttpClient (BaseAddress = (Uri "https://swapi.co/api/people/1/"))
static let luke =
async {
return! c.GetStringAsync "" |> Async.AwaitTask
} |> Async.RunSynchronously
static do
//Pity we can't actually print here
printfn "Even more initialization!"
[<Fact>]
let ``Test async initialization`` () =
Assert.NotNull(luke)
The static bindings in this case are executed before any of the tests, as they should, and the code doesn't block. This initialization will happen only once.
To capture output the test class constructor should accept an ITestOutputHelper parameter. That's easy to do now that we have a test class :
type Tests(output:ITestOutputHelper) =
...
[<Fact>]
let ``Test async initialization`` () =
Assert.NotNull(luke)
output.WriteLine "It worked!"
Per-test initialization should go in a do block :
type Tests(output:ITestOutputHelper) =
do
output.WriteLine "This prints before each test"

WebSharper: Rpc-returned object are unusuable on the client or cause error

I have a few server-side objects with inheritrence hierarchy, like this:
[<JavaScriptExport>]
type [<AbstractClass>] A() = ...
[<JavaScriptExport>]
type [<AbstractClass>] B() =
inherit A()
[<JavaScriptExport>]
type C() =
inherit B()
The above objects have certain fields and methods, which I have omitted for brevity. All of those can be compiled to javascript -- I receive no build errors.
I have an RPC that would return a server-side created instance of such an object:
module Remoting =
[<Rpc>]
let GetObject (arg: string) : Async<A> =
async {
return (upcast C() : A)
}
When I invoke the rpc and debug the relevant javascript in my browser, I see that the retured object is {}. I do not see any server-side errors in the logs.
If I change the signature of the Rpc to be of the concrete type GetObject (arg: string) : Async<C>, I receive an error on the server:
System.Exception: Could not load method (GetObject : System.String -> Microsoft.FSharp.Control.FSharpAsync`1<C>) candidates: [|"(GetObject : System.String -> Microsoft.FSharp.Control.FSharpAsync`1<.C>)"|]
It seems that it looks for a type .C instead of type C (emphasys on the leading dot)
What is the reason for this behavior? Is there a way to get my object instance from the server without having to specify its concrete type?
Update
Interestingly, it works if I replace the RPC call with a client-side method like this:
[<JavaScript>]
let GetObjectClient (arg : string) : A =
(upcast C() : A)
I suppose there are issues converting the server-side object to its corresponding client counterpart. Still I have no idea how to get over this

How Can Unit Tests On iOS with Xamarin and F#

I am using Xamarin Studio 5.5 and I'd like to run my tests on the iOS simulator.
Sadly there is no template to create an iOS unit test project.
Create an empty F# project
Copy the following file (AppDelegate.fs) into your newly created project
namespace ioslibrarytests //obviously you can choose any namespace you want
open System
open MonoTouch.UIKit
open MonoTouch.Foundation
open MonoTouch.NUnit.UI
[<Register("AppDelegate")>]
type AppDelegate() =
inherit UIApplicationDelegate()
override val Window = null with get, set
override this.FinishedLaunching(app, options) =
this.Window <- new UIWindow(UIScreen.MainScreen.Bounds)
let runner = new TouchRunner(this.Window)
runner.Add (System.Reflection.Assembly.GetExecutingAssembly ());
this.Window.RootViewController <- new UINavigationController (runner.GetViewController ());
this.Window.MakeKeyAndVisible()
true
module Main =
[<EntryPoint>]
let main args =
UIApplication.Main(args, null, "AppDelegate")
0
Create your test file. for example Tests.fs and follow this implementation pattern
namespace ioslibrarytests
open System
open NUnit.Framework;
[<TestFixture>]
type Tests() =
[<Test>]
member me.Pass() = Assert.True (true)
[<Test>]
member me.Fail() = Assert.False (true)
[<Test>]
[<Ignore ("another time")>]
member me.``Ignore me``() = Assert.True (false)
Please take care that
your test class needs to have a default constructor => type Tests() = ...
your test methods need to have an empty param list => member me.Pass() = ...

Awaiting an IAsyncOperation in F#

I have the following code in F#:
let CreateSampleDataFromJson<'T>(path) =
let uri = new Uri(path)
async {
let file = StorageFile.GetFileFromApplicationUriAsync(uri)
let jsonText = FileIO.ReadTextAsync(file)
return JsonObject<'T>.Parse(jsonText)
}
The problem I'm having is that file is an IAsyncOperation<StorageFile> and not a StorageFile as ReadTextAsync expects.
In C# you can do something similar to this:
var file = await StorageFile.GetFileFromApplicationUriAsync(uri)
i.e.
public async Task<T> CreateSampleDataFromUrl<T>(string path)
{
var uri = new Uri(path);
var file = await StorageFile.GetFileFromApplicationUriAsync(uri);
var jsonText = await FileIO.ReadTextAsync(file);
return JsonObject<T>.Parse(jsonText);
}
The problem is that I don't know how to await an IAsyncOperation in F#. The usual let! doesn't work. i.e. the following fails to compile:
async {
let! file = StorageFile.GetFileFromApplicationUriAsync(uri)
With the compiler error:
error FS0001: This expression was expected to have type Async<'a> but here has type IAsyncOperation<StorageFile>
I found a document that said there's an AsTask() extension method defined in the System.WindowsRuntimeSystemExtensions class which I can use as follows:
let! file = StorageFile.GetFileFromApplicationUriAsync(uri).AsTask() |> Async.AwaitTask
Is there a standard way of doing this or something available in an F# library somewhere that makes this a bit nicer?
Your solution seems fine by me. If you're looking for a nicer syntax, how about rolling it into a function like this (without the possibly gratuitous type annotations):
let await<'a> (op: IAsyncOperation<'a>) : Async<'a> =
op.AsTask() |> Async.AwaitTask
This will give you the almost exact same syntax you'd see in c#:
async {
let! file = await <| StorageFile.GetFileFromApplicationUriAsync(uri)
...
}
The compiler errors you were getting with your previous approaches are to be expected. All async workflow cares about is the F#-specific Async type. This type gives you a way to interop with the rest of .NET world through Tasks, but that's it. IAsyncOperation is from a 'different part of the world', I wouldn't expect F# core libraries to support it anytime soon.

Resources