在尝试向同事证明可以使用F#的C ++类时,我提出了以下概念证明。第一个片段是他为挑战提供的代码,下面的代码片段是我在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)
在Visual Studio中调试或作为独立程序运行时,这可以预测:TrivialFoo.Get
返回值bar
和TrivialFoo.Set
分配给它。但是,从F#Interactive运行时,TrivialFoo.Set
将不会设置该字段。我怀疑它可能与从非托管代码访问托管内存有关,但这并不能解释为什么它只在使用F#Interactive时才会发生。有谁知道这里发生了什么?
答案 0 :(得分:1)
我认为概念验证是互操作性的良好证明。您可能最好从C ++项目创建DLL导出定义,并使用去装饰名称。
作为PoC:F#创建适合CLI的MSIL,因此它可以与any other CLI language out there互操作。如果这还不够,并且您需要本机到网络互操作,请考虑使用COM,或者如上所述,在C ++上使用DLL导出定义。我个人不会像你在这里建议的那样尝试与C ++类定义互操作,有更简单的方法可以做到这一点。
或者,只需将您的C ++项目更改为.NET C ++项目,您就可以直接从F#访问这些类,同时仍具有C ++的强大功能。
当然,你可能仍然想知道为什么这个例子不能在FSI中运行。您可以通过运行以下内容来查看答案提示:
> System.IO.Directory.GetCurrentDirectory();;
val it : string = "R:\TMP"
要解决此问题,您有多种选择:
Win32Project2.dll
复制到该目录PATH
复制可能是这些解决方案中最简单的。
由于FSI意味着REPL,因此它可能不适合需要多个项目,库或其他复杂配置的此类任务。您可以考虑在this FSI request for support for #package
上投票以导入NuGet包,这可用于简化此类任务。
答案 1 :(得分:0)
F#中C ++结构的对应部分不一定是结构。在C ++中,类和结构之间的唯一区别在于它们的默认访问限制。
在F#中,结构用于值类型,类用于引用类型。值类型的一个问题是它们应该用作不可变值,而临时副本通常是静默创建的。
您观察到的问题与该情况一致。出于某种原因,F#interactive会创建结构的副本并传递对该结构的引用。然后,C ++代码修改副本,保持原始状态不变。
如果切换到使用类,请确保在让本机代码使用它之前固定实例,否则最终可能会在本机代码引用它之后垃圾收集器移动它。