Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible F# Interactive PInvoke bug

While trying to prove to a colleague that it's possible to use C++ classes from F#, I came up with the following proof of concept. The first snippet is the code he provided for the challenge, and the code snippet below is my implementation in F#.


namespace testapp {
    struct trivial_foo {
        int bar;
        __declspec(dllexport) void set(int n) { bar = n; }
        __declspec(dllexport) int get() { return bar; }
    }
}

open System.Runtime.InteropServices

type TrivialFoo =
    struct
        val bar: int
        new(_bar: int) = { bar = _bar }
    end

[<DllImport("Win32Project2.dll", EntryPoint="?get@trivial_foo@testapp@@QAEHXZ", CallingConvention = CallingConvention.ThisCall)>]
extern int trivial_foo_get(TrivialFoo& trivial_foo)

[<DllImport("Win32Project2.dll", EntryPoint="?set@trivial_foo@testapp@@QAEXH@Z", CallingConvention = CallingConvention.ThisCall)>]
extern void trivial_foo_set(TrivialFoo& trivial_foo, int bar)

type TrivialFoo with
    member this.Get() = trivial_foo_get(&this)
    member this.Set(bar) = trivial_foo_set(&this, bar)

When debugged in Visual Studio or run as a standalone program, this works predictably: TrivialFoo.Get returns the value of bar and TrivialFoo.Set assigns to it. When run from F# Interactive however, TrivialFoo.Set will not set the field. I suspect it might have something to do with accessing managed memory from unmanaged code, but that doesn't explain why it only happens when using F# Interactive. Does anyone know what's going on here?

like image 733
king jah Avatar asked Dec 03 '14 01:12

king jah


1 Answers

I don't think this proof of concept is a good proof of interoperability. You may be better off creating DLL export definitions from your C++ project and use the de-decorated names instead.

As a PoC: F# creates MSIL that fits in the CLI, so it can interoperate with any other CLI language out there. If that is not enough and you want native-to-net interop, consider using COM, or as mentioned above, DLL export definitions on your C++. I personally wouldn't try to interop with C++ class definitions the way you suggest here, there are way easier ways to do that.

Alternatively, just change your C++ project into a .NET C++ project and you can access the classes directly from F#, while still having the power of C++.

Naturally, you may still be wondering why the example doesn't run in FSI. You can see a hint of an answer by running the following:

> System.IO.Directory.GetCurrentDirectory();;
val it : string = "R:\TMP"

To fix this, you have a myriad of options:

  • copy Win32Project2.dll to that directory
  • add whatever path it is in to PATH
  • use an absolute path
  • use a compile-time constant
  • or use an environment variable (the path will be expanded)
  • dynamically locate the dll and dynamically bind to it (complex)

Copying is probably the easiest of these solutions.

Since FSI is meant to be a REPL, it may not be best tailored for this kind of tasks that require multiple projects, libraries or otherwise complex configurations. You may consider voting on this FSI request for support for #package to import NuGet packages, which could be used to ease such tasks.

like image 90
Abel Avatar answered Oct 05 '22 11:10

Abel