Proper use of P/invoke with pointers in F#

Viewed 243

I'm trying to convert this c# code to f#:

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

This is what I have so far:

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

What's the proper way to pass in the necessary arguments? Pointers, byrefs, or something else. Also, what are the [Out] and [In] attributes doing?

Edit: I've answered some of my questions but still have several open. Do I need to specify an [] for the Size parameter, an inref, or is it simply inferred?

1 Answers

Here's a partial answer from my research.

For the most part, when using P/Invoke, one may simply copy-and-paste the signatures from a C header file (sans-semi-colons, of course). However, there is at least one scenario where naïvely doing so produces code which is not verifiably type-safe. Let’s look at a specific example. Given the follow function prototype in C:

__declspec(dllexport) void getVersion (int* major, int* minor, int* patch);

One might use the following P/Invoke signature (and associated call) in 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

However, that isn't quite right. Turns out, when dealing with the CLR, there are two types of pointers: unmanaged and managed. The latter are what you use when passing around CLR types by-reference (i.e. “byref<‘T>“ in F#, or “ref“ in C#, or “ByRef“ in VB). It also happens that you should use the managed variety if you want your F# code to be verifiably type-safe — and this includes P/Invoke calls. If you think about it, this makes sense. The runtime can only guarantee the bits it can control (i.e. the parts which are “managed”). So here’s what the F# code looks like using managed pointers instead:

[<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

Handy table:

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

In nearly all cases, a .NET developer should prefer the managed pointer. Leave the unmanaged risks with the C code.

Edited Source : P/Invoke Gotcha in f#

As an extra note, to be passed in as a byref the variable must be marked as mutable. Passing a non mutable object, even with mutable properties, is a readonly inref. Handy for passing read only value types by reference. F# ByRef and InRef

Related