F# memory management best practices for native interop

Viewed 99

When interoperating with native code using pointers what is considered the best practice (or what shouldn't be used) for techniques for allocation. As an example let's say I have the struct and external function:

[<StructLayout(layoutKind = LayoutKind.Sequential)>]
type SomeFloats = 
    struct
        val count:int
        val arr:nativeint
        new(count, arr:nativeptr<float>) = 
            { count = count; arr = NativePtr.toNativeInt arr}
    end


[<DllImport("dllName")>]
extern int SomeFunc(nativeint pSomeFloats)

And some code which calls it:

let run () =

    let count = 5

    let mutable val0 = 
        let arr = Array.zeroCreate count
        SomeFloats(count, &&(arr[0]))

    let mutable val1 = 
        let stackPtr = NativePtr.stackalloc (count)
        SomeFloats(count, stackPtr)

    let mutable val2 = 
        let arr = GC.AllocateUninitializedArray(count, pinned = true)
        SomeFloats(count, &&(arr[0]))

    let mutable val3, disp3 = 
        let x = Marshal.AllocHGlobal(count * sizeof<float>)
        SomeFloats(count, NativePtr.ofNativeInt x), 
            { new IDisposable with
                override this.Dispose(): unit = 
                    Marshal.FreeHGlobal(x) }


    let i = SomeFunc(NativePtr.toNativeInt &&val0)
    let i = SomeFunc(NativePtr.toNativeInt &&val1)
    let i = SomeFunc(NativePtr.toNativeInt &&val2)
    let i = SomeFunc(NativePtr.toNativeInt &&val3)

    //Do more work

    disp3.Dispose()

All of val0 to val3 will compile however is there any issues with using any of these construction forms? Looking around it seems like val0 is invalid as the GC may rearrange an array on the heap without warning. This suggests the use of val2 with pinned = true should be preferred.

When arr goes out of scope in val0 and val2 could they be collected before SomeFunc is called?

Update: Seems my instinct is correct here and val0/2 should be created like this:

    let mutable val2, disp2 = 
        let arr = GC.AllocateUninitializedArray(count, pinned = true)
        let h = GCHandle.Alloc(arr, GCHandleType.Pinned)
        SomeFloats(count, &&(arr[0])), 
            { new IDisposable with
                override this.Dispose(): unit = 
                    h.Free() }

When stackPtr goes out of scope is the contents of the stack retained until SomeFunc is called or will the stack be unwound (and the contents of val1 potentially altered) as soon as the pointer is returned to val1?

val3 seems like the safest option however I'm not sure of the more general implications of using unmanaged memory with xHGlobal?

0 Answers
Related