可能的F#交互式PInvoke错误

kin*_*jah 10 pinvoke f#

在试图向同事证明可以使用F#的C++类时,我提出了以下概念证明.第一个片段是他为挑战提供的代码,下面的代码片段是我在F#中的实现.


namespace testapp {
    struct trivial_foo {
        int bar;
        __declspec(dllexport) void set(int n) { bar = n; }
        __declspec(dllexport) int get() { return bar; }
    }
}
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)

在Visual Studio中调试或作为独立程序运行时,这可以预测:TrivialFoo.Get返回值barTrivialFoo.Set赋值给它.但是,从F#Interactive运行时,TrivialFoo.Set不会设置该字段.我怀疑它可能与从非托管代码访问托管内存有关,但这并不能解释为什么它只在使用F#Interactive时才会发生.有谁知道这里发生了什么?

Abe*_*bel 1

我不认为这个概念证明是互操作性的良好证明。您最好从 C++ 项目创建 DLL 导出定义并使用经过修饰的名称。

作为 PoC:F# 创建适合 CLI 的 MSIL,因此它可以与任何其他 CLI 语言进行互操作。如果这还不够,并且您想要本机到网络互操作,请考虑使用 COM,或者如上所述,在 C++ 上使用 DLL 导出定义。我个人不会尝试按照您在这里建议的方式与 C++ 类定义进行互操作,有更简单的方法可以做到这一点。

或者,只需将您的 C++ 项目更改为 .NET C++ 项目,您就可以直接从 F# 访问这些类,同时仍然拥有 C++ 的强大功能。

当然,您可能仍然想知道为什么该示例不能在 FSI 中运行。您可以通过运行以下命令来查看答案的提示:

> System.IO.Directory.GetCurrentDirectory();;
val it : string = "R:\TMP"
Run Code Online (Sandbox Code Playgroud)

要解决此问题,您有多种选择:

  • 复制Win32Project2.dll到该目录
  • 添加它所在的任何路径PATH
  • 使用绝对路径
  • 使用编译时常量
  • 或使用环境变量(路径将被扩展)
  • 动态定位dll并动态绑定到它(复杂)

复制可能是这些解决方案中最简单的。

由于 FSI 旨在成为REPL,因此它可能不是最适合此类需要多个项目、库或其他复杂配置的任务。您可以考虑对此 FSI 请求进行投票,以支持#package导入 NuGet 包,这可用于简化此类任务。