开发者

Passing a struct as argument to C function called from F#

In C# C struct: s can be passed as arguments to C functions called with D开发者_运维百科llImport using the StructLayout attribute

http://msdn.microsoft.com/en-us/library/aa984739%28VS.71%29.aspx

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


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:

[<StructLayout(LayoutKind.Sequential)>]
type ADDRESS64 = 
    struct
        val mutable Offset : DWORD64
        val mutable Segment : WORD
        val mutable Mode : ADDRESS_MODE
    end

(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)>]
type IMAGEHLP_MODULE64 =
    struct
    val SizeOfStruct : DWORD
    [<DefaultValue>] val BaseOfImage : DWORD64
    [<DefaultValue>] val ImageSize : DWORD
...
    new (_ : bool) = { SizeOfStruct = uint32 (Marshal.SizeOf(typeof<IMAGEHLP_MODULE64>)) }
end

You can define enumeration types for flags:

[<Flags>]
type SymOptions =
| ALLOW_ABSOLUTE_SYMBOLS = 0x00000800u 
| 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)>]
type DEBUG_EVENT = 
    struct
        [<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:

[<DllImport("Dbghelp.dll")>]
extern bool StackWalk64(
  [<In>] IMAGE_FILE_MACHINE_TYPE MachineType,
  [<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.


Does this help?

[<Struct>]
[<StructLayout(LayoutKind.Sequential)>]
type SYSTEMTIME=
  [<MarshalAs(UnmanagedType.U2)>]
  val Year:int16
  [<MarshalAs(UnmanagedType.U2)>]
  val Month:int16
  [<MarshalAs(UnmanagedType.U2)>]
  val DayOfWeek:int16
  [<MarshalAs(UnmanagedType.U2)>]
  val Day:int16
  [<MarshalAs(UnmanagedType.U2)>]
  val Hour:int16
  [<MarshalAs(UnmanagedType.U2)>]
  val Minute:int16
  [<MarshalAs(UnmanagedType.U2)>]
  val Second:int16
  [<MarshalAs(UnmanagedType.U2)>]
  val Milliseconds:int16

module ExternTest =
  [<DllImport("kernel32.dll")>]
  extern void GetLocalTime(SYSTEMTIME& lpSystemTime)

let mutable sysTime = SYSTEMTIME()
ExternTest.GetLocalTime(&sysTime)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜