将结构作为参数传递给从F#调用的C函数

时间:2010-01-05 23:39:46

标签: c# .net f#

在C#C中,struct:s可以作为参数传递给使用StructLayout属性调用DllImport的C函数

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

这在F#中如何运作?显然有可能以某种方式,但我没有找到谷歌的任何示例代码,我还不太清楚该语言

2 个答案:

答案 0 :(得分:7)

我最近花了很多时间搞清楚这一点。这是一个大脑转储...希望它能让你开始。

首先,要意识到在大多数情况下,您必须遵循的规则由.Net决定,而不是由C#决定。所以在P / Invoke的编组结构上的所有MSDN参考资料都适用。就个人而言,我发现读取有关InAttribute和OutAttribute的使用规则以及原始.Net数据类型的默认编组行为非常有帮助。

这是一个来自DbgHelp的示例结构:

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

(我为DWORD64之类的东西设置了类型别名,使代码更类似于它所基于的.h文件。)

(我更喜欢结构的详细语法。你也可以使用light语法。)

mutable关键字是可选的。如果您在构建后修改单个字段,则需要它。

内联数组的完成方式如下:

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

如果默认封送不适合您,则可能必须设置数组子类型。

内联字符数组(又名字符串)的完成方式如下:

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

在这种情况下,结构的CharSet字段很重要:

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

您必须具有将所有字段设置为“0”的默认构造函数。但是您可以使用其他构造函数来以不同方式初始化字段。如果执行此操作,则必须使用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

您可以为标志定义枚举类型:

[<Flags>]
type SymOptions =
| ALLOW_ABSOLUTE_SYMBOLS = 0x00000800u 
| ALLOW_ZERO_ADDRESS = 0x01000000u 
...

如果您的结构包含联合,那么您不得不使用显式布局并将多个字段设置为具有相同的位置。

[<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
...

一旦定义了结构,就可以定义使用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
)

注意通过引用传递的结构的语法:In和Out,以及&amp; for byref。在这种情况下,要调用该函数,必须声明结构是可变的:

让mutable stackframe'= ... 让mutable mcontext = ... 让结果=     DbgHelp.StackWalk64(         DbgHelp.IMAGE_FILE_MACHINE_TYPE.I386,         ...         &安培;的StackFrame”,         &安培; mcontext,         ...

对于返回字符串及其长度的函数,您可能会发现StringBuilder很有用。这与C#相同。

如果您使用的是HANDLES,请查看Microsoft.Win32.SafeHandles。编组员会将这些作为HANDLES发送到本地,但可以设置为在收集时为你调用CloseHandle。

如果您了解marshaller的默认行为,您通常可以不使用任何MarshalAsAttributes。我发现这是可取的,因为在某些情况下,如果事情被编组并且实际上不需要,那么性能就会下降。

答案 1 :(得分:4)

这有帮助吗?

[<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)