How you described is the clearest way to do this, assuming you have to deal with struct
in this way. For completeness of this method (with packaging implementation details omitted):
open FSharp.NativeInterop [<StructLayout(...)>] type myStructure = struct val mutable a : int val mutable b : byte end let changeA pointer newA = let mutable structure = NativePtr.read pointer structure.a <- newA NativePtr.write pointer structure
However, since you must know the exact offsets of each element, you can also use this information to write directly to this field. F # does not provide a way to do this using named identifiers, and its strict typing like nativeptr<'T>
means that you cannot just apply a pointer to the corresponding type. The NativePtr.set offset ptr
function adds sizeof<'T> * offset
, so this is also useless in this case.
Let's say myStructure
type has a Packed
attribute for simplicity. Then the offsets are 0 for a
and 4 for b
. Throwing all caution to the wind and completely abandoning the area of managed memory, we could do:
let changeB pointer newB = let bPtr = NativePtr.toNativeInt pointer |> (+) 4n |> NativePtr.ofNativeInt<byte> NativePtr.write bPtr newB
or even:
let changeMember pointer offset (value : 'T) = let pointer' = NativePtr.toNativeInt pointer |> (+) (nativeint offset) |> NativePtr.ofNativeInt<'T> NativePtr.write pointer' value
I leave open the question of what is the best way to deal with these situations, if they should be resolved at all. I tend to go in the first, clearest way by using extra memory. The last, arbitrary offset method should be avoided at all costs - if you need to deal with adding raw offsets, it is much better to wrap them in a more easily verifiable function, such as the second method, so the caller does not need to calculate the offset on its own.
source share