Can I get a pointer to a Span?
Asked Answered
N

3

19

I have a (ReadOnly)Span<byte> from which I want to decode a string.

Only in .NET Core 2.1 I have the new overload to decode a string from it without needing to copy the bytes:

Encoding.GetString(ReadOnlySpan<byte> bytes);

In .NET Standard 2.0 and .NET 4.6 (which I also want to support), I only have the classic overloads:

Encoding.GetString(byte[] bytes);
Encoding.GetString(byte* bytes, int byteCount);

The first one requires a copy of the bytes into an array which I want to avoid.
The second requires a byte pointer, so I thought about getting one from my span, like

Encoding.GetString(Unsafe.GetPointer<byte>(span.Slice(100)))

...but I failed finding an actual method for that. I tried void* Unsafe.AsPointer<T>(ref T value), but I cannot pass a span to that, and didn't find another method dealing with pointers (and spans).

Is this possible at all, and if yes, how?

Newborn answered 18/1, 2019 at 14:19 Comment(4)
Enter [ReadOnly]Span<T>.GetPinnableReference(). If using C# 7.3, leveraging this is as simple as fixed (byte* bytes = span) -- this compiles for .NET 4.5.2, at least, I haven't tested if it will also really work (I only have later frameworks installed).Retractile
In prior C# versions, you can use the return value of ref GetPinnableReference() as the argument to Unsafe.AsPointer. You need at least C# 7.0 to use ref locals.Retractile
@JeroenMostert This is great, I'm using C# 7.3 and it's working smooth as silk. Do you want to post an answer about it so I can accept it?Newborn
With the help of ILSpy, I actually found a more convenient syntax for earlier versions as well. Also not tested, but since the pointers returned are identical I'm going to assume it works.Retractile
R
34

If you have C# 7.3 or later, you can use the extension made to the fixed statement that can use any appropriate GetPinnableReference method on a type (which Span and ReadOnlySpan have):

fixed (byte* bp = bytes) {
    ...
}

As we're dealing with pointers this requires an unsafe context, of course.

C# 7.0 through 7.2 don't have this, but allow the following:

fixed (byte* bp = &bytes.GetPinnableReference()) {
    ...
}
Retractile answered 18/1, 2019 at 16:15 Comment(0)
R
2

Try this:

Span<byte> bytes = ...;
string s = Encoding.UTF8.GetString((byte*)Unsafe.AsPointer(ref bytes.GetPinnableReference()),
    bytes.Length);
Rosebay answered 18/1, 2019 at 14:52 Comment(4)
Thanks! I did not see GetPinnableReference() anywhere in IntelliSense. However, this did not seem to work for ReadOnlySpan<byte> (it cannot pass it as ref because it is readonly); but it would work for my initial question (only now I realized I have a ReadOnlySpan, sorry!).Newborn
@RayKoopa: the reason you couldn't see the method, by the way, is because it was explicitly hidden as feature. This was back when it was called DangerousGetPinnableReference; it's since been downgraded to "not actually that dangerous", but the method's still hidden.Retractile
The alternative proposed for ReadOnlySpan is not correct! Storing it in a variable first and taking the ref of that gives you a reference to the local byte, not the spanned array. Accessing any element beyond the first this way will give garbage.Retractile
@JeroenMostert I actually remember that "Dangerous" method name. I didn't follow up on the recent changes, thanks for clearing this up.Newborn
G
0
unsafe T* PointerTo<T>(ReadOnlySpan<T> from) where T : struct => *(T**)&from;

This gets a pointer to the _reference inside the ReadOnlySpan.
Example:

var message = "123456"u8;
var pointer = PointerTo(message);
Console.WriteLine(string.Join(' ', ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]));
// 49 50 51 52 53 54

WARNING. IT'S NOT SAFE!

Gandzha answered 16/7 at 12:9 Comment(2)
What's better here than in the accepted answer that uses built-in language functionality?Newborn
@Ray, because it's 3 IL instructions, instead of calling multiple methods under the hood, which is logically what the same code does. This solution will find its users, who doesn't like the fixed block, like, yea — meGandzha

© 2022 - 2024 — McMap. All rights reserved.