在C#C中,struct:s可以作为参数传递给使用StructLayout属性调用DllImport的C函数
http://msdn.microsoft.com/en-us/library/aa984739%28VS.71%29.aspx
这在F#中如何运作?显然有可能以某种方式,但我没有找到谷歌的任何示例代码,我还不太清楚该语言
答案 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)