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 ?
Asked
Active
Viewed 4,854 times
13
-
3Not 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
`, 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. – Marc Gravell Feb 05 '19 at 20:47 -
1Thanks @MarcGravell , C# 7.2 allows to write Span
s = stackalloc int[100]; without being unsafe so I'm a little bit frustrated by my use case (pinvoke) but you're probably right – Mitsuru Furuta Feb 05 '19 at 23:57 -
I believe a big factor there is that the P/Invoke layer isn't "language", it is "runtime", which means if they added `Span
` 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. – Marc Gravell Feb 06 '19 at 09:01 -
Note that you can get a `ref T` from a `Span
` easily enough (but not from a `ReadOnlySpan – Marc Gravell Feb 06 '19 at 09:02`), 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. -
@MarcGravell how do you get a ref T (or ref T[]) from a Span
? – Mitsuru Furuta Feb 06 '19 at 18:33 -
3a `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 implemented – Marc Gravell Feb 06 '19 at 18:41
-
@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 :( – Ani Mar 28 '19 at 21:31
1 Answers
-1
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.

CorrM
- 498
- 6
- 18