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

Att*_*ela 5 .net c# f#

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

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

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

Jas*_*son 9

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

首先,要意识到在大多数情况下,您必须遵循的规则由.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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

[<MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)>] val Reserved : DWORD64[]
Run Code Online (Sandbox Code Playgroud)

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

内嵌字符数组(又名字符串)如下所示:

[<DefaultValue>] [<MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)>] val ModuleName : string
Run Code Online (Sandbox Code Playgroud)

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

[<StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)>]
Run Code Online (Sandbox Code Playgroud)

您必须具有将所有字段设置为"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
Run Code Online (Sandbox Code Playgroud)

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

[<Flags>]
type SymOptions =
| ALLOW_ABSOLUTE_SYMBOLS = 0x00000800u 
| ALLOW_ZERO_ADDRESS = 0x01000000u 
...
Run Code Online (Sandbox Code Playgroud)

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

[<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
...
Run Code Online (Sandbox Code Playgroud)

一旦定义了结构,就可以定义使用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
)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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


kvb*_*kvb 5

这有帮助吗?

[<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)
Run Code Online (Sandbox Code Playgroud)