Teni
Teni

Reputation: 83

Marshalling struct by reference in F#

I'm trying to rewrite the marshalling SysTime sample from the Marshaling Classes, Structures, and Unions MSDN article from C# to F#.

My actual code now looks like this:

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

It compiles and run with no exception but the output is not as expected. The values in the SystemTime structure are not overwritten.

Output:

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

If I run the code in the F# interactive console I got System.AccessViolationException. The C# version of the code works fine on my system. I tried to use ref/byref keywords but that did not help.

Any ideas what is wrong? Any good source of information how to use P/Invoke and marshalling from F# correctly? I did not find much useful stuff.

Upvotes: 3

Views: 483

Answers (2)

Aaron M. Eshbach
Aaron M. Eshbach

Reputation: 6510

The Win32 GetSystemTime function defines the struct parameter as a pointer. That means your original code should work if you just change the way your external function is defined:

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

Then you would tweak your main method to make st mutable and pass it like a pointer:

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

This prints:

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

Upvotes: 3

Teni
Teni

Reputation: 83

As rmunn suggested, the struct must be a plain F# record.

So this is the code that works for me:

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

The output is now as expected:

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

Thanks for your hint, rmunn

Upvotes: 2

Related Questions