strangelights.com

Main F# Site

Edit Edit
Print Print
Recent Changes Recent Changes
Subscriptions Subscriptions
Lost and Found Lost and Found
Find References Find References
Rename Rename
Search
List all versions List all versions
Calling C
.
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 |])
Welcome to F Sharp Wiki, view the HomePage

This site supports the new NoFollow anti-spam initiative.

Recent Topics

Copyright 2005, Robert Pickering (Others where credited) | Terms of Use