这是我研究的部分答案。
在大多数情况下,使用 P/Invoke 时,可以简单地从 C 头文件中复制并粘贴签名(当然,没有分号)。但是,至少在一种情况下,天真地这样做会产生无法验证类型安全的代码。我们来看一个具体的例子。给定 C 中的以下函数原型:
__declspec(dllexport) void getVersion (int* major, int* minor, int* patch);
可以在 F# 中使用以下 P/Invoke 签名(和相关调用):
[<DllImport("somelib",CallingConvention=CallingConvention.Cdecl)>]
extern void getVersion (int* major, int* minor, int* patch)
let mutable major,minor,patch = 0,0,0
getVersion(&&major,&&minor,&&patch)
printfn "Version: %i.%i.%i" major minor patch
但是,这并不完全正确。事实证明,在处理 CLR 时,有两种类型的指针:非托管和托管。后者是您在通过引用传递 CLR 类型时使用的(即 F# 中的“byref”,C# 中的“ref”,或 VB 中的“ByRef”)。如果您希望您的 F# 代码是可验证的类型安全的,那么您也应该使用托管类型——这包括 P/Invoke 调用。如果你仔细想想,这是有道理的。运行时只能保证它可以控制的位(即“管理”的部分)。下面是使用托管指针代替 F# 代码的样子:
[<DllImport("somelib",CallingConvention=CallingConvention.Cdecl)>]
extern void getVersion (int& major, int& minor, int& patch)
let mutable major,minor,patch = 0,0,0
getVersion(&major,&minor,&patch)
printfn "Version: %i.%i.%i" major minor patch
方便的桌子:
Pointer F# Type Declaration Invocation
Unmanaged nativeint <type>* &&<type>
Managed byref <type> <type>& &type
在几乎所有情况下,.NET 开发人员都应该更喜欢托管指针。将无法管理的风险留给 C 代码。
编辑来源:P/Invoke Gotcha in f#
作为额外说明,要作为 byref 传入,变量必须标记为可变。传递一个非可变对象,即使具有可变属性,也是一个只读的 inref。通过引用传递只读值类型很方便。 F# ByRef and InRef