Skip to content

Commit 2afaaf5

Browse files
committed
Add helpers for unsafe pointers and marshalling
1 parent 8676541 commit 2afaaf5

10 files changed

Lines changed: 554 additions & 5 deletions

File tree

sources/core/Stride.Core/Pointer{T}.cs renamed to sources/core/Stride.Core/Unsafe/Pointer{T}.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
using System;
88

9-
namespace Stride.Core
9+
namespace Stride.Core.UnsafeExtensions
1010
{
1111
/// <summary>
1212
/// Wraps a pointer to <typeparamref name="T"/> so it can be used as a generic type parameter.
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
2+
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
3+
4+
// Contains code from TerraFX Framework, Copyright (c) Tanner Gooding and Contributors
5+
// Licensed under the MIT License (MIT).
6+
7+
using System;
8+
using System.Runtime.CompilerServices;
9+
using System.Runtime.InteropServices;
10+
using System.Text;
11+
12+
using static Stride.Core.UnsafeExtensions.UnsafeUtilities;
13+
14+
namespace Stride.Core.UnsafeExtensions
15+
{
16+
/// <summary>
17+
/// Provides a set of methods to supplement or replace <see cref="Marshal"/> when operating on <see cref="string"/>s
18+
/// and spans of characters.
19+
/// </summary>
20+
public static unsafe class StringMarshal
21+
{
22+
/// <summary>
23+
/// Gets a <see cref="string"/> for a given span of bytes, assuming an UTF-8 encoding.
24+
/// </summary>
25+
/// <param name="span">The span for which to create the string.</param>
26+
/// <returns>A string created from <paramref name="span"/>.</returns>
27+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
28+
public static string GetString(this ReadOnlySpan<sbyte> span)
29+
=> span.GetPointer() != null
30+
? Encoding.UTF8.GetString(span.As<sbyte, byte>())
31+
: null;
32+
33+
/// <summary>
34+
/// Gets a <see cref="string"/> for a given span, assuming an UTF-16 encoding.
35+
/// </summary>
36+
/// <param name="span">The span for which to create the string.</param>
37+
/// <returns>A string created from <paramref name="span"/>.</returns>
38+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
39+
public static string GetString(this ReadOnlySpan<ushort> span)
40+
=> span.GetPointer() != null
41+
? new string(span.As<ushort, char>())
42+
: null;
43+
44+
/// <summary>
45+
/// Gets a null-terminated sequence of ASCII characters for a <see cref="string"/>.
46+
/// </summary>
47+
/// <param name="source">The string for which to marshal.</param>
48+
/// <returns>A null-terminated ASCII string that is equivalent to <paramref name="source"/>.</returns>
49+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
50+
public static ReadOnlySpan<sbyte> GetAsciiSpan(this string source)
51+
{
52+
ReadOnlySpan<byte> result;
53+
54+
if (source is not null)
55+
{
56+
var maxLength = Encoding.ASCII.GetMaxByteCount(source.Length);
57+
var bytes = new byte[maxLength + 1];
58+
59+
var length = Encoding.ASCII.GetBytes(source, bytes);
60+
result = bytes.AsSpan(0, length);
61+
}
62+
else
63+
{
64+
result = null;
65+
}
66+
67+
return result.As<byte, sbyte>();
68+
}
69+
70+
/// <summary>
71+
/// Gets a span for a null-terminated ASCII character sequence.
72+
/// </summary>
73+
/// <param name="source">The pointer to a null-terminated ASCII character sequence.</param>
74+
/// <param name="maxLength">The maxmimum length of <paramref name="source"/> or <c>-1</c> if the maximum length is unknown.</param>
75+
/// <returns>
76+
/// A <see cref="ReadOnlySpan{SByte}"/> that starts at <paramref name="source"/> and extends to
77+
/// <paramref name="maxLength"/> or the first null character, whichever comes first.
78+
/// </returns>
79+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
80+
public static ReadOnlySpan<sbyte> GetAsciiSpan(sbyte* source, int maxLength = -1)
81+
=> GetUtf8Span(source, maxLength);
82+
83+
/// <summary>
84+
/// Gets a span for a null-terminated ASCII character sequence.
85+
/// </summary>
86+
/// <param name="source">The reference to a null-terminated ASCII character sequence.</param>
87+
/// <param name="maxLength">The maxmimum length of <paramref name="source"/> or <c>-1</c> if the maximum length is unknown.</param>
88+
/// <returns>
89+
/// A <see cref="ReadOnlySpan{SByte}"/> that starts at <paramref name="source"/> and extends to
90+
/// <paramref name="maxLength"/> or the first null character, whichever comes first.
91+
/// </returns>
92+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
93+
public static ReadOnlySpan<sbyte> GetAsciiSpan(in sbyte source, int maxLength = -1)
94+
=> GetUtf8Span(in source, maxLength);
95+
96+
/// <summary>
97+
/// Gets a null-terminated sequence of UTF-8 characters for a <see cref="string"/>.
98+
/// </summary>
99+
/// <param name="source">The string for which to get the null-terminated UTF8 character sequence.</param>
100+
/// <returns>A null-terminated UTF8 character sequence created from <paramref name="source"/>.</returns>
101+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
102+
public static ReadOnlySpan<sbyte> GetUtf8Span(this string source)
103+
{
104+
ReadOnlySpan<byte> result;
105+
106+
if (source is not null)
107+
{
108+
var maxLength = Encoding.UTF8.GetMaxByteCount(source.Length);
109+
var bytes = new byte[maxLength + 1];
110+
111+
var length = Encoding.UTF8.GetBytes(source, bytes);
112+
result = bytes.AsSpan(0, length);
113+
}
114+
else
115+
{
116+
result = null;
117+
}
118+
119+
return result.As<byte, sbyte>();
120+
}
121+
122+
/// <summary>
123+
/// Gets a span for a null-terminated UTF-8 character sequence.
124+
/// </summary>
125+
/// <param name="source">The pointer to a null-terminated UTF-8 character sequence.</param>
126+
/// <param name="maxLength">
127+
/// The maxmimum length of <paramref name="source"/> or <c>-1</c> if the maximum length is unknown.
128+
/// </param>
129+
/// <returns>
130+
/// A <see cref="ReadOnlySpan{SByte}"/> that starts at <paramref name="source"/> and extends to
131+
/// <paramref name="maxLength"/> or the first null character, whichever comes first.
132+
/// </returns>
133+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
134+
public static ReadOnlySpan<sbyte> GetUtf8Span(sbyte* source, int maxLength = -1)
135+
=> (source != null)
136+
? GetUtf8Span(in source[0], maxLength)
137+
: null;
138+
139+
/// <summary>
140+
/// Gets a span for a null-terminated UTF-8 character sequence.
141+
/// </summary>
142+
/// <param name="source">The reference to a null-terminated UTF-8 character sequence.</param>
143+
/// <param name="maxLength">
144+
/// The maxmimum length of <paramref name="source"/> or <c>-1</c> if the maximum length is unknown.
145+
/// </param>
146+
/// <returns>
147+
/// A <see cref="ReadOnlySpan{SByte}"/> that starts at <paramref name="source"/> and extends to
148+
/// <paramref name="maxLength"/> or the first null character, whichever comes first.
149+
/// </returns>
150+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
151+
public static ReadOnlySpan<sbyte> GetUtf8Span(in sbyte source, int maxLength = -1)
152+
{
153+
ReadOnlySpan<sbyte> result;
154+
155+
if (!IsNullRef(in source))
156+
{
157+
if (maxLength < 0)
158+
maxLength = int.MaxValue;
159+
160+
result = CreateReadOnlySpan(in source, maxLength);
161+
var length = result.IndexOf((sbyte) '\0');
162+
163+
if (length != -1)
164+
{
165+
result = result[..length];
166+
}
167+
}
168+
else
169+
{
170+
result = null;
171+
}
172+
173+
return result;
174+
}
175+
176+
/// <summary>
177+
/// Gets a null-terminated sequence of UTF-16 characters for a <see cref="string"/>.
178+
/// </summary>
179+
/// <param name="source">The string for which to get the null-terminated UTF-16 character sequence.</param>
180+
/// <returns>A null-terminated UTF-16 character sequence created from <paramref name="source"/>.</returns>
181+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
182+
public static ReadOnlySpan<ushort> GetUtf16Span(this string source)
183+
=> source.AsSpan().As<char, ushort>();
184+
185+
/// <summary>
186+
/// Gets a span for a null-terminated UTF-16 character sequence.
187+
/// </summary>
188+
/// <param name="source">The pointer to a null-terminated UTF-16 string.</param>
189+
/// <param name="maxLength">
190+
/// The maxmimum length of <paramref name="source"/> or <c>-1</c> if the maximum length is unknown.
191+
/// </param>
192+
/// <returns>
193+
/// A <see cref="ReadOnlySpan{UInt16}"/> that starts at <paramref name="source"/> and extends to
194+
/// <paramref name="maxLength"/> or the first null character, whichever comes first.
195+
/// </returns>
196+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
197+
public static ReadOnlySpan<ushort> GetUtf16Span(ushort* source, int maxLength = -1)
198+
=> (source != null)
199+
? GetUtf16Span(in source[0], maxLength)
200+
: null;
201+
202+
/// <summary>
203+
/// Gets a span for a null-terminated UTF-16 character sequence.
204+
/// </summary>
205+
/// <param name="source">The reference to a null-terminated UTF-16 string.</param>
206+
/// <param name="maxLength">
207+
/// The maxmimum length of <paramref name="source"/> or <c>-1</c> if the maximum length is unknown.
208+
/// </param>
209+
/// <returns>
210+
/// A <see cref="ReadOnlySpan{UInt16}"/> that starts at <paramref name="source"/> and extends to
211+
/// <paramref name="maxLength"/> or the first null character, whichever comes first.
212+
/// </returns>
213+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
214+
public static ReadOnlySpan<ushort> GetUtf16Span(in ushort source, int maxLength = -1)
215+
{
216+
ReadOnlySpan<ushort> result;
217+
218+
if (!IsNullRef(in source))
219+
{
220+
if (maxLength < 0)
221+
maxLength = int.MaxValue;
222+
223+
result = CreateReadOnlySpan(in source, maxLength);
224+
var length = result.IndexOf('\0');
225+
226+
if (length != -1)
227+
{
228+
result = result[..length];
229+
}
230+
}
231+
else
232+
{
233+
result = null;
234+
}
235+
236+
return result;
237+
}
238+
}
239+
}

0 commit comments

Comments
 (0)