在 F# 中正确使用 P/invoke 和指针

Proper use of P/invoke with pointers in F#

我正在尝试将此 c# 代码转换为 f#:

[DllImport("psapi.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetPerformanceInfo([Out] out PerformanceInformation PerformanceInformation, [In] int Size);

这是我目前拥有的:

[<DllImport("psapi.dll", SetLastError = true)>]
extern [<return: MarshalAs(UnmanagedType.Bool)>] bool GetPerformanceInfo(PerfInfo PerformanceInformation, int Size)

传递必要参数的正确方法是什么?指针、byrefs 或其他东西。 另外,[Out] 和 [In] 属性有什么作用?

编辑:我已经回答了我的一些问题,但还有几个未解决的问题。 我是否需要为 Size 参数指定一个 [],一个 inref,还是简单地推断?

这是我研究的部分答案。

在大多数情况下,当使用 P/Invoke 时,可以简单地 copy-and-paste 来自 C 头文件的签名(当然 sans-semi-colons)。但是,至少有一种情况是天真地这样做会产生不可验证的代码 type-safe。让我们看一个具体的例子。给定以下 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 类型 by-reference 时使用的(即 F# 中的“byref<‘T>”,或 C# 中的“ref”,或 VB 中的“ByRef”)。如果您希望您的 F# 代码是可验证的 type-safe,那么您也应该使用托管类型——这包括 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

得心应手table:

Pointer    F#             Type Declaration      Invocation
Unmanaged  nativeint      <type>*               &&<type>
Managed    byref <type>   <type>&               &type

几乎在所有情况下,.NET 开发人员都应该更喜欢托管指针。将未管理的风险留给 C 代码。

已编辑来源:P/Invoke Gotcha in f#

作为额外说明,要作为 byref 传递,变量必须标记为 mutable。传递非 mutable 对象,即使具有 mutable 属性,也是只读 inref。通过引用传递只读值类型很方便。 F# ByRef and InRef