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:
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()
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)
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 |])