Calling C
Last changed: -83.199.19.152

.
Summary
Notes on using DllImport attributes to call C directly from F# (by Don Syme, on the F# list)

Discussions on the F# List raised the broader issue of how to use C/C++ directly from F#. This is an important topic, and I wanted to give some preliminary notes in order to set expectations about the degree to which this is supported in the current release. I have also included an update of my sample that I sent in reply to his first message with the use of arrays removed, and also an additional sample showing how to pass a callback to C.

Broadly speaking, you can now do everything that you need from a C FFI interop layer from F#. On the whole things are easier than from OCaml, since you generally don’t have to write any C code, though things are a little trickier than from C#, which has added a number of conveniences to the language in order support writing “unsafe” code.

As background to these preliminary notes I would strongly recommend reading some of the tutorials on unsafe code, e.g. in the C# manual, or http://msdn.microsoft.com/library/en-us/csref/html/vcwlkunsafecodetutorial.asp. Uses of “fixed” in C# become explicit uses of a “pinned” function as shown further below.

Here are some notes for those familiar with OCaml’s C FFI interop layer:

Sample 1 – Using a simple function from a DLL, return results by reference

Here is an updated version of SooHyoung’s sample.

 open System
 open System.Runtime.InteropServices
 open System.Windows.Forms
 open System.Drawing




 [<DllImport("cards.dll")>]
 let cdtInit((width: IntPtr), (height: IntPtr)) : unit = ()


 let pinned (obj: obj) f = 
   let gch = GCHandle.Alloc(obj,GCHandleType.Pinned) in 
   try f(gch.AddrOfPinnedObject())
   finally
     gch.Free()


 type PtrRef<'a> = { v : obj }
   with 
     static member Create(x) = { v  = box(x) }
     member x.Value = (unbox x.v : 'a)
     member x.Pin(f) = pinned(x.v) f
   end


 let card_init () =
   let width = PtrRef.Create(300) in
   let height = PtrRef.Create(400) in
   width.Pin (fun widthAddress -> 
     height.Pin (fun heightAddress -> 
       cdtInit (widthAddress, heightAddress)));
   Printf.printf "width.(0) = %d\n" width.Value;
   Printf.printf "height.(0) = %d\n" height.Value;
   ()


 do card_init()

Sample 2 – Passing a callback value to C

 type ControlEventHandler = delegate of int -> bool


 [<DllImport("kernel32.dll")>]
 let SetConsoleCtrlHandler((callback:ControlEventHandler),(add: bool)) : unit = ()


 let CTRL_C = 0  // from Win32 C header files
 let raised = ref false
 let ctrlEventHandler = new ControlEventHandler(fun i ->  if i = CTRL_C then (raised := true; true) else  false ) 
 do SetConsoleCtrlHandler(ctrlEventHandler,true)


 // Main application goes here
 //...


 // End of main application
 //
 // We add the line below to ensure that the ctrlEventHandler is not 
 // collected while the callback may
 // be active.  A GCHandle or type Normal
 // with the appropriate lifetime can also be used.


 do GC.KeepAlive(ctrlEventHandlers)

Sample 3 - Wrapping an unmanaged array

Dmitry was wanting to access an arbitrary block of memory allocated elsewhere, so he'll need to use unverifiable IL instructions, which is of course what C# does with it's /unsafe switch. I've given a sample below to get you going - it's one of the small wonders of F# inline .NET assembly code combined with .NET generics that you can get this implemented for a huge range of data types in only 10 lines! I'll leave it to you to put together a big-array wrapper which uses the full range of phantom-type tricks to make this safe. If you need big array of non-primitive datatypes you'll need to do a little more work (you need to make structs that are blittable).

I see no real reason why the corresponding generated native code won't go as fast as C code, though that would have to be checked and maybe some minor glitches ironed out.

  /// This code is only for use with .NET 2.0 (nb. if you add 'inline'
  /// to the 'get' function it should work correctly on .NET 1.x)
  /// Also fsi.exe won't accept it entered interactively when last checked (11/02/2006)


  /// These represent unmanaged external arrays of type 'a, allocated by 
  /// some other external source, e.g. malloc. You have an obligation
  /// when creating an instance of this value to ensure that the 
  /// aent type is correct, and you also have an obligation not
  /// to access out-of bounds.
  type 'a earray = EA of nativeint 


  /// Get the address of an element in the array
  let address (EA x : 'a earray) n = 
    let size = (# "sizeof !!0" type ('a) : int32) in 
    (# "add" x (n * size) : nativeint)  


  /// Fetch a value from the unmanaged array
  let get (arr : 'a earray) n = (# "ldobj !0" type ('a) (address arr n) : 'a) 


  /// Set a value in the unmanaged array
  let set (arr : 'a earray) n (x : 'a) = (# "stobj !0" type ('a) (address arr n) x)  

Here's some sample code:

  // That's all you need to do.  The rest of this code just
  // cons's up some data and grabs the internal address using a
  // GCHandle, and tests it, then tests it on a range of types.


  open System.Runtime.InteropServices


  let data = [| 1;2;3;4 |]
  let gch = GCHandle.Alloc(data,GCHandleType.Pinned) 
  let addr = gch.AddrOfPinnedObject()


  let wrapped : int earray = EA addr 
  do printf "wrapped.(0) = %d\n" (get wrapped 0)
  do printf "wrapped.(1) = %d\n" (get wrapped 1)
  do printf "wrapped.(2) = %d\n" (get wrapped 2)
  do set wrapped 2 10067
  do printf "wrapped.(0) = %d\n" (get wrapped 0)
  do printf "wrapped.(1) = %d\n" (get wrapped 1)
  do printf "wrapped.(2) = %d\n" (get wrapped 2)

So that was all fine, except we forgot to dispose of the GC handle!

  // This standard piece of code disposes the GC handle when we leave the 
  // lexical scope.


  let pinned (obj:>obj) f = 
   let gch = GCHandle.Alloc(obj,GCHandleType.Pinned) in 
   try f(gch.AddrOfPinnedObject())
   finally
     gch.Free()


  // Now write a generic test wrapper that uses that


  let test (data : 'a[]) = 
    pinned data (fun addr -> 
      let wrapped : 'a earray = EA addr  in
      for i = 0 to Array.length data - 1 do 
        printf "wrapped.(%d) = %a\n" i output_any (get wrapped i)
      done)


  // And test it....


  do test [| 1;2;3;4 |]
  do test [| 1.0;2.0;3.0;4.0 |]
  do test [| 1.0f;2.0f;3.0f;4.0f |]
  do test (Array.map Byte.of_int [| 1;2;3;4 |])