-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
One of the valuable aspects of Span is it keeps code safe and largely pointer-free. However, at the interop boundary, Span isn't supported, which leads to code like:
fixed (byte* bufPtr = &buffer.DangerousGetPinnableReference())
{
bytesRead = CheckFileCall(Interop.Sys.Read(_fileHandle, bufPtr, buffer.Length));
}
This both forces the method to become unsafe and is a lot of extra work. We should add marshaling support for Span to allow it to be used end-to-end.
For the most part, existing array marshaling semantics are a good match for Spans. For example:
[DllImport("kernel32.dll")]
static extern uint GetTempPath(uint nBufferLength, Span<char>lpBuffer);
would marshal the Span's underlying pointer (pinning as necessary). Reverse interop is a bit trickier. For example, the native signature of HeapAlloc is:
LPVOID WINAPI HeapAlloc(_In_ HANDLE hHeap, _In_ DWORD dwFlags, _In_ SIZE_T dwBytes);
If we wanted a span wrapping the returned pointer, an ideal P/Invoke would look like:
static extern Span<byte> HeapAlloc(SafeHandle hHeap, uint dwFlags, UIntPtr dwBytes);
but that doesn't tell the Span its length. It may be enough to add MarshalAsAttribute with SizeParamIndex (or SizeConst):
static extern [MarshalAs(SizeParamIndex=3)] Span<byte> HeapAlloc(SafeHandle hHeap, uint dwFlags, UIntPtr dwBytes);
Open questions:
- Should the Span's elements be marshaled as we would for an array? For example, a bool is 1 byte in managed code, but defaults to marshaling to 4 bytes. If we apply this with Spans, it requires copying in those cases.
- How should InAttribute and OutAttribute apply? Should they match what we do with arrays?
- In reverse interop cases, the Span may hold native memory that now needs to be freed. What's a good pattern for freeing the memory and getting rid of the now-wild pointer in the Span?