Take the 2-minute tour ×

In C# C struct: s can be passed as arguments to C functions called with DllImport using the StructLayout attribute


How does this work in F#? Apparently it is possible somehow, but I didn't find any example code by Google and I don't know the language very well yet

share|improve this question
Just some clarification: you want to call an WinAPI from F#? Or you want to pass an C# struct to F#? –  Paulo Santos Jan 5 '10 at 23:47
Maybe this helps? stackoverflow.com/questions/1689460/… –  Stringer Jan 5 '10 at 23:52
add comment

2 Answers

up vote 5 down vote accepted

I recently spent a lot of time figuring this out. Here's a brain dump... Hopefully it'll get you started.

First, realize that for the most part, the rules you have to follow are dictated by .Net, not by C#. So all of the MSDN reference material on marshalling structures with P/Invoke apply. Personally, I found it very helpful to read the rules regarding the usage of InAttribute and OutAttribute, and the default marshaling behavior for the primitive .Net data types.

Here's an example structure, from DbgHelp:

type ADDRESS64 = 
        val mutable Offset : DWORD64
        val mutable Segment : WORD
        val mutable Mode : ADDRESS_MODE

(I have type aliases set up for things like DWORD64 to make the code more similar to .h files that it is based on.)

(I prefer the verbose syntax for structs. You could use the light syntax too.)

The mutable keyword is optional. It is needed if you'll modify individual fields after construction.

In-line arrays are done like this:

[<MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)>] val Reserved : DWORD64[]

You may have to set the array subtype if the default marshaling doesn't suit you.

In-line character arrays (aka strings) are done like this:

[<DefaultValue>] [<MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)>] val ModuleName : string

In which case, the structure's CharSet field matters:

[<StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)>]

You must have a default constructor that sets all fields to "0". But you can have other constructors that initialize fields differently. If you do this, you must mark the fields that are not initialized by the constructors with the DefaultValueAttribute:

[<StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)>]
    val SizeOfStruct : DWORD
    [<DefaultValue>] val BaseOfImage : DWORD64
    [<DefaultValue>] val ImageSize : DWORD
    new (_ : bool) = { SizeOfStruct = uint32 (Marshal.SizeOf(typeof<IMAGEHLP_MODULE64>)) }

You can define enumeration types for flags:

type SymOptions =
| ALLOW_ZERO_ADDRESS = 0x01000000u 

If your structs contain unions, you unfortunately have to use explicit layout and set multiple fields to have the same position.

[<StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)>]
        [<FieldOffset(0)>] val dwDebugEventCode : DebugEventKind
        [<FieldOffset(4)>] val dwProcessId : DWORD
        [<FieldOffset(8)>] val dwThreadId : DWORD
        [<FieldOffset(12)>] val Exception : EXCEPTION_DEBUG_INFO
        [<FieldOffset(12)>] val CreateThread : CREATE_THREAD_DEBUG_INFO
        [<FieldOffset(12)>] val CreateProcessInfo : CREATE_PROCESS_DEBUG_INFO

Once you get your structures defined, it's time to define the external functions that use them with DllImportAttribute:

extern bool StackWalk64(
  [<In>] SafeFileHandle hProcess,
  [<In>] HANDLE hThread,
  [<In>][<Out>] STACKFRAME64& StackFrame,
  [<In>][<Out>] Win32.CONTEXT& ContextRecord,
  [<In>] nativeint ReadMemoryRoutine,
  [<In>] nativeint FunctionTableAccessRoutine,
  [<In>] nativeint GetModuleBaseRoutine,
  [<In>] nativeint TranslateAddress

Notice the syntax for structures passed by reference: In and Out, and the & for byref. In this case, to call the function, you must declare the structure mutable:

let mutable stackframe' = ... let mutable mcontext = ... let result = DbgHelp.StackWalk64( DbgHelp.IMAGE_FILE_MACHINE_TYPE.I386, ... &stackframe', &mcontext, ...

For functions returning strings and their length, you may find StringBuilder useful. This is the same as for C#.

If you're using HANDLES, check out Microsoft.Win32.SafeHandles. The marshaller will send these as HANDLES to the native side, but it can be set up to call CloseHandle on them for you when they're collected.

You can oftentimes get by without applying any MarshalAsAttributes, if you understand the marshaller's default behavior. I found this desireable because in some cases performance fell through the floor if things were marshalled that didn't really need to be.

share|improve this answer
add comment

Does this help?

  val Year:int16
  val Month:int16
  val DayOfWeek:int16
  val Day:int16
  val Hour:int16
  val Minute:int16
  val Second:int16
  val Milliseconds:int16

module ExternTest =
  extern void GetLocalTime(SYSTEMTIME& lpSystemTime)

let mutable sysTime = SYSTEMTIME()
share|improve this answer
add comment

Your Answer


