在F#中通过引用编组结构

时间:2018-07-06 08:00:37

标签: f# pinvoke marshalling

我正在尝试将Marshaling Classes, Structures, and Unions MSDN文章中的编组SysTime示例从C#重写为F#。

我的实际代码现在看起来像这样:

module LibWrap =
    open System.Runtime.InteropServices

    [<StructLayout(LayoutKind.Sequential)>]
    type public SystemTime =
        struct
            val mutable public year:uint16
            val mutable public month:uint16
            val mutable public weekday:uint16
            val mutable public day:uint16
            val mutable public hour:uint16
            val mutable public minute:uint16
            val mutable public second:uint16
            val mutable public millisecond:uint16
        end 

    [<DllImport("Kernel32.dll")>]
    extern void GetSystemTime([<param:In>][<param: Out>]SystemTime st)


[<EntryPoint>]
let main argv =
    printfn "F# SysTime Sample using Platform Invoke";
    let st = new LibWrap.SystemTime (month = 1us, day = 2us, year = 34us)
    try
        LibWrap.GetSystemTime st
    with 
        | ex -> printfn "Failed to GetSystemTime: %O" ex
    printfn "The Date is: %d/%d/%d" st.month st.day st.year
    0

它编译并正常运行,但输出与预期不符。 SystemTime结构中的值不会被覆盖。

输出:

F# SysTime Sample using Platform Invoke
The Date is: 1/2/34

如果我在F#交互式控制台中运行代码,则会得到System.AccessViolationException。 C#版本的代码在我的系统上可以正常工作。我尝试使用ref / byref关键字,但这没有帮助。

任何想法有什么问题吗? 有什么好的信息来源,如何正确使用P / Invoke和从F#编组?我找不到很多有用的东西。

2 个答案:

答案 0 :(得分:3)

Win32 GetSystemTime函数将struct参数定义为指针。这意味着如果您仅更改外部函数的定义方式,原始代码应该可以工作:

[<DllImport("Kernel32.dll")>]
extern void GetSystemTime(SystemTime& st)

然后,您将调整主方法以使st可变并将其像指针一样传递:

[<EntryPoint>]
let main argv =
    printfn "F# SysTime Sample using Platform Invoke";
    let mutable st = LibWrap.SystemTime (month = 1us, day = 2us, year = 34us)
    try
        LibWrap.GetSystemTime &st
    with 
        | ex -> printfn "Failed to GetSystemTime: %O" ex
    printfn "The Date is: %d/%d/%d" st.month st.day st.year
    0

此打印:

F# SysTime Sample using Platform Invoke
The Date is: 7/6/2018

答案 1 :(得分:2)

根据rmunn的建议,该结构必须是普通的F#记录。

这是对我有用的代码:

module LibWrap =
    open System.Runtime.InteropServices

    [<CLIMutable>]
    [<StructLayout(LayoutKind.Sequential)>]
    type SystemTime = {
        year:uint16
        month:uint16
        weekday:uint16
        day:uint16
        hour:uint16
        minute:uint16
        second:uint16
        millisecond:uint16
    }

    [<DllImport("Kernel32.dll")>]
    extern void GetSystemTime([<param:In>][<param: Out>]SystemTime st)

open LibWrap

[<EntryPoint>]
let main argv =
    printfn "F# SysTime Sample using Platform Invoke";
    let st = { year = 4us ; month = 1us ; day = 2us ; weekday = 0us ; hour = 0us ; minute = 0us ; second = 0us ; millisecond = 0us }
    try
        LibWrap.GetSystemTime st
    with 
        | ex -> printfn "Failed to GetSystemTime: %O" ex
    printfn "The Date is: %d/%d/%d" st.month st.day st.year
    0

现在输出符合预期:

F# SysTime Sample using Platform Invoke
The Date is: 7/6/2018

感谢您的提示,rmunn