Paul Blasucci's Weblog

Thoughts on software development and sundry other topics

weblog index

Managing Pointers, or F#'s Platform-Invoke "Gotcha"

Published:

I love how well F# "plays" with other languages. This is obviously true where its in-the-box .NET siblings are concerned. However, over the past few years, I've come to find it is just as seamless when mixed with good old-fashioned C code.

Well, it's nearly as seamless.

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

At this point, everything will compile and run just fine. So where's the problem? To find out, we have to turn to an under-utilised tool in the .NET developer's arsenal -- PEVerify.

And now back to our program (already in progress)...

Running the tool gives us the following output (reformatted for readability):

Microsoft (R) .NET Framework PE Verifier. Version 4.0.30319.1
 Copyright (c) Microsoft Corporation. All rights reserved.

[IL]: Error:
 [C:\dev\somelibfs.dll : .::.cctor]
 [mdToken=0x600008f][offset 0x0000000D]
 [found address of Int32][expected unmanaged pointer]
 Unexpected type on the stack.

[IL]: Error:
 [C:\dev\somelibfs.dll : .::.cctor]
 [mdToken=0x600008f][offset 0x0000000D]
 [found address of Int32][expected unmanaged pointer]
 Unexpected type on the stack.

[IL]: Error:
 [C:\dev\somelibfs.dll : .::.cctor]
 [mdToken=0x600008f][offset 0x0000000D]
 [found address of Int32][expected unmanaged pointer]
 Unexpected type on the stack.

3 Error(s) Verifying C:\dev\somelibfs.dll

Clearly, something isn't right.

It's not very obvious, but the big clue is where PEVerify tells us it was expecting an unmanaged pointer. Turns out, when dealing with the CLR, there are two types of pointers: unmanaged and managed. The later 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

And, if we run PEVerify on the updated code, we get the following report:

Microsoft (R) .NET Framework PE Verifier.  Version  4.0.30319.1
Copyright (c) Microsoft Corporation.  All rights reserved.

All Classes and Methods in C:\dev\somelibfs.dll

That's much better!

So, to recap, there are two types of pointers, as summarized in the following table:

Pointer Type 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 your C code.

** I'd like to give special thanks to Jack Pappas, for finding (and helping me to understand and vanquish) this issue in fszmq.