Get pointer (IntPtr) from a Span<T> staying in safe mode
Asked Answered
P

1

13

I would like to use Span and stackalloc to allocate an array of struct and pass it to an interop call. Is it possible to retrieve a pointer (IntPtr) from the Span without being unsafe ?

Pending answered 5/2, 2019 at 20:42 Comment(7)
Not as far as I know; I don't think the P/Invoke layer was updated to directly interpret spans as pointers. You'll need to use unsafe. I would say "use a typed pointer instead of an IntPtr", though - i.e. int* if you're using Span<int>, etc. In the general case, you can use &span[0], but &MemoryMarshal.GetReference(span) may be preferable (it handles empty spans correctly); but in your case you may prefer to bypass span completely.Pretypify
Thanks @MarcGravell , C# 7.2 allows to write Span<int> s = stackalloc int[100]; without being unsafe so I'm a little bit frustrated by my use case (pinvoke) but you're probably rightPending
I believe a big factor there is that the P/Invoke layer isn't "language", it is "runtime", which means if they added Span<T> support to P/Invoke, it still wouldn't work except on .NET Core 3.1 or .NET Framework 5.0 (which doesn't exist, which is exactly the point). By not pretending to offer that, it means that what you do write stands a chance of actually working on an existing framework.Pretypify
Note that you can get a ref T from a Span<T> easily enough (but not from a ReadOnlySpan<T>), so if P/Invoke allows ref T with implied pin semantics (for the general case - you obviously don't need pin semantics for a stackalloc span), then you should be golden - not sure that it does, though.Pretypify
@MarcGravell how do you get a ref T (or ref T[]) from a Span<T> ?Pending
a ref T would be span[0] or (preferred) MemoryMarshal.GetReference(span), as per my earlier comment - you can convert a ref T (managed pointer) to a T* (unmanaged pointer) via &, which is what I did above. Note, though, that usually when doing that, you'll want to use fixed, i.e. fixed(int* ptr = &MemoryMarshal.GetReference(span)) {...} - although IIRC there was a plan to make spans directly fixable, i.e. fixed(int* ptr = span) {...} - I can't remember if that got implementedPretypify
@MarcGravell this seems like a step backward to me. Before, we could mix and match lifetimes (of the GCHandle) and allow pointers to pass through safe boundaries. Now APIs that use Span are limited to copying data for use in any other place or box the managed object containing the memory we want to use in a way that doesn't care what type contains the underlying memory. What am I missing? Now, I can only use the fixed statement (limited because we cannot guarantee the lifetime of the pointer beyond that one method/fixed statement). Now that means copies for long-lived data :(Suu
S
-3

Here is how i did it without unsafe:
i just changed lpBuffer to ref byte instead of byte[] (for c++ user COULD represent it as uint8_t*)

[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadProcessMemory(nint hProcess, nuint lpBaseAddress, ref byte lpBuffer, int dwSize, out int lpNumberOfBytesRead);

Span<byte> bytes = stackalloc byte[8];
// Get reference to first byte in the span (for c++ user we COULD represent it as `uint8_t*`)
ref byte bytesReference = ref MemoryMarshal.AsRef<byte>(bytes);

// You can pass `ref MemoryMarshal.AsRef<byte>(bytes)` to the function directly
bool success = Win32.ReadProcessMemory(_processHandle, address, ref bytesReference, cSize, out int numberOfBytesRead);

lpBuffer could be in byte instead of ref byte as that will allow you to use ReadOnlySpan

[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool WriteProcessMemory(nint hProcess, nuint lpBaseAddress, in byte lpBuffer, int dwSize, out int lpNumberOfBytesWritten);

ReadOnlySpan<byte> bytes = stackalloc byte[8];
ref readonly byte bytesReference = ref MemoryMarshal.AsRef<byte>(bytes);

// You can pass `in MemoryMarshal.AsRef<byte>(bytes)` to the function directly
Win32.WriteProcessMemory(_processHandle, address, in bytesReference, bytes.Length, out int numberOfBytesWritten);

Notes:
[+] You could use GetPinnableReference instead of MemoryMarshal.AsRef but it is not intended to be called by user code.
[+] ReadProcessMemory Windows API function.
[+] ReadProcessMemory(kernel32) pinvoke Original implementation.

Sandlin answered 6/2, 2023 at 14:21 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.