From cc6b1a8b39eb6f6c9bb583ca5000f4c008ec8848 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Mon, 5 Sep 2022 09:37:11 +0200 Subject: [PATCH] WIP: Some test for string marshalling (based on TerraFX) --- src/Vortice.Win32/Graphics/Dxgi.Manual.cs | 57 +++++++ src/Vortice.Win32/NetStandard.cs | 23 +++ src/Vortice.Win32/StringUtilities.cs | 176 ++++++++++++++++++++++ src/Vortice.Win32/UnsafeUtilities.cs | 171 +++++++++++++++++++++ src/samples/01-ClearScreen/Program.cs | 26 +++- 5 files changed, 451 insertions(+), 2 deletions(-) create mode 100644 src/Vortice.Win32/StringUtilities.cs create mode 100644 src/Vortice.Win32/UnsafeUtilities.cs diff --git a/src/Vortice.Win32/Graphics/Dxgi.Manual.cs b/src/Vortice.Win32/Graphics/Dxgi.Manual.cs index 6fd69ed..c03ad1b 100644 --- a/src/Vortice.Win32/Graphics/Dxgi.Manual.cs +++ b/src/Vortice.Win32/Graphics/Dxgi.Manual.cs @@ -2,9 +2,66 @@ // Licensed under the MIT License (MIT). See LICENSE in the repository root for more information. using static Win32.Graphics.Dxgi.Apis; +using static Win32.StringUtilities; namespace Win32.Graphics.Dxgi; +public unsafe partial struct AdapterDescription +{ + /// + public readonly string DescriptionStr + { + get + { + fixed (ushort* ptr = Description) + { + return GetString(ptr, 128) ?? string.Empty; + } + } + } +} + +public unsafe partial struct AdapterDescription1 +{ + /// + public readonly string DescriptionStr + { + get + { + fixed (ushort* ptr = Description) + { + return GetString(ptr, 128) ?? string.Empty; + } + } + } +} + +public unsafe partial struct AdapterDescription2 +{ + public readonly ReadOnlySpan DescriptionSpan + { + get + { + fixed (ushort* ptr = Description) + { + return GetUtf16Span(ptr, 128); + } + } + } + + /// + public readonly string DescriptionStr + { + get + { + fixed (ushort* ptr = Description) + { + return GetString(ptr, 128) ?? string.Empty; + } + } + } +} + public unsafe partial struct IDXGIFactory5 { public TFeature CheckFeatureSupport(Feature feature) diff --git a/src/Vortice.Win32/NetStandard.cs b/src/Vortice.Win32/NetStandard.cs index e3a0272..e005052 100644 --- a/src/Vortice.Win32/NetStandard.cs +++ b/src/Vortice.Win32/NetStandard.cs @@ -23,6 +23,17 @@ internal static class MemoryMarshal return ref global::System.Runtime.InteropServices.MemoryMarshal.GetReference(span); } + /// + /// Returns a reference to the 0th element of . If the array is empty, returns a reference + /// to where the 0th element would have been stored. Such a reference may be used for pinning but must never be dereferenced. + /// + /// is . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetArrayDataReference(T[] array) + { + return ref global::System.Runtime.InteropServices.MemoryMarshal.GetReference(array.AsSpan()); + } + /// /// Creates a new from a given reference. /// @@ -34,6 +45,18 @@ internal static class MemoryMarshal { return new(Unsafe.AsPointer(ref value), length); } + + /// + /// Creates a new from a given reference. + /// + /// The type of reference to wrap. + /// The target reference. + /// The length of the to create. + /// A new wrapping . + public static unsafe ReadOnlySpan CreateReadOnlySpan(ref T value, int length) + { + return new(Unsafe.AsPointer(ref value), length); + } } #endif diff --git a/src/Vortice.Win32/StringUtilities.cs b/src/Vortice.Win32/StringUtilities.cs new file mode 100644 index 0000000..9a1ac1a --- /dev/null +++ b/src/Vortice.Win32/StringUtilities.cs @@ -0,0 +1,176 @@ +// Copyright © Amer Koleci and Contributors. +// Licensed under the MIT License (MIT). See LICENSE in the repository root for more information. +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Runtime.CompilerServices; +using System.Text; +using static Win32.UnsafeUtilities; + +namespace Win32; + +/// +/// Provides a set of methods to supplement for . +/// +public static unsafe class StringUtilities +{ + public static string? GetString(sbyte* pointer, int maxLength = -1) + { + return GetUtf8Span(pointer, maxLength).GetString(); + } + + public static string? GetString(ushort* pointer, int maxLength = -1) + { + return GetUtf16Span(pointer, maxLength).GetString(); + } + + /// Gets a null-terminated sequence of UTF8 characters for a string. + /// The string for which to get the null-terminated UTF8 character sequence. + /// A null-terminated UTF8 character sequence created from . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetUtf8Span(this string? source) + { + ReadOnlySpan result; + + if (source is not null) + { + int maxLength = Encoding.UTF8.GetMaxByteCount(source.Length); +#if NET6_0_OR_GREATER + var bytes = new byte[maxLength + 1]; + var length = Encoding.UTF8.GetBytes(source, bytes); + result = bytes.AsSpan(0, length); +#else + byte* bytes = stackalloc byte[maxLength + 1]; + fixed (char* namePtr = source) + { + Encoding.UTF8.GetBytes(namePtr, source.Length, bytes, maxLength); + } + bytes[maxLength] = 0; + result = new(bytes, source.Length); +#endif + } + else + { + result = null; + } + + return result.As(); + } + + /// Gets a span for a null-terminated UTF8 character sequence. + /// The pointer to a null-terminated UTF8 character sequence. + /// The maxmimum length of or -1 if the maximum length is unknown. + /// A span that starts at and extends to or the first null character, whichever comes first. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetUtf8Span(sbyte* source, int maxLength = -1) + => (source != null) ? GetUtf8Span(in source[0], maxLength) : null; + + /// Gets a span for a null-terminated UTF8 character sequence. + /// The reference to a null-terminated UTF8 character sequence. + /// The maxmimum length of or -1 if the maximum length is unknown. + /// A span that starts at and extends to or the first null character, whichever comes first. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetUtf8Span(in sbyte source, int maxLength = -1) + { + ReadOnlySpan result; + + if (!IsNullRef(in source)) + { + if (maxLength < 0) + { + maxLength = int.MaxValue; + } + + result = CreateReadOnlySpan(in source, maxLength); + var length = result.IndexOf((sbyte)'\0'); + + if (length != -1) + { + result = result.Slice(0, length); + } + } + else + { + result = null; + } + + return result; + } + + /// Gets a null-terminated sequence of UTF16 characters for a string. + /// The string for which to get the null-terminated UTF16 character sequence. + /// A null-terminated UTF16 character sequence created from . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetUtf16Span(this string? source) => source.AsSpan().As(); + + /// Marshals a null-terminated UTF16 string to a . + /// The pointer to a null-terminated UTF16 string. + /// The maxmimum length of or -1 if the maximum length is unknown. + /// A that starts at and extends to or the first null character, whichever comes first. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetUtf16Span(ushort* source, int maxLength = -1) + => (source != null) ? GetUtf16Span(in source[0], maxLength) : null; + + /// Marshals a null-terminated UTF16 string to a . + /// The reference to a null-terminated UTF16 string. + /// The maxmimum length of or -1 if the maximum length is unknown. + /// A that starts at and extends to or the first null character, whichever comes first. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetUtf16Span(in ushort source, int maxLength = -1) + { + ReadOnlySpan result; + + if (!IsNullRef(in source)) + { + if (maxLength < 0) + { + maxLength = int.MaxValue; + } + + result = CreateReadOnlySpan(in source, maxLength); + var length = result.IndexOf('\0'); + + if (length != -1) + { + result = result.Slice(0, length); + } + } + else + { + result = null; + } + + return result; + } + + /// Gets a string for a given span. + /// The span for which to create the string. + /// A string created from . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string? GetString(this ReadOnlySpan span) + { + if (span.GetPointer() == null) + return null; + +#if NET6_0_OR_GREATER + return new string(span.As()); +#else + return new string(span.As().GetPointer(), 0, span.Length); +#endif + } + + /// Gets a string for a given span. + /// The span for which to create the string. + /// A string created from . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string? GetString(this ReadOnlySpan span) + { + if (span.GetPointer() == null) + return null; + +#if NET6_0_OR_GREATER + return Encoding.UTF8.GetString(span.As()); +#else + return Encoding.UTF8.GetString(span.As().GetPointer(), span.Length); +#endif + } +} diff --git a/src/Vortice.Win32/UnsafeUtilities.cs b/src/Vortice.Win32/UnsafeUtilities.cs new file mode 100644 index 0000000..85f40c3 --- /dev/null +++ b/src/Vortice.Win32/UnsafeUtilities.cs @@ -0,0 +1,171 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. +// Copyright © Amer Koleci and Contributors. +// Licensed under the MIT License (MIT). See LICENSE in the repository root for more information. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +#if !NET6_0_OR_GREATER +using MemoryMarshal = Win32.MemoryMarshal; +#endif + +namespace Win32; + +/// Provides a set of methods to supplement or replace and . +public static unsafe class UnsafeUtilities +{ + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if NET6_0_OR_GREATER + [return: NotNullIfNotNull("o")] +#endif + public static T? As(this object? o) + where T : class? + { + //Assert(o is null or T); + return Unsafe.As(o); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref TTo As(ref TFrom source) + => ref Unsafe.As(ref source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span As(this Span span) + where TFrom : unmanaged + where TTo : unmanaged + { + //Assert(AssertionsEnabled && (SizeOf() == SizeOf())); + return CreateSpan(ref As(ref span.GetReference()), span.Length); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan As(this ReadOnlySpan span) + where TFrom : unmanaged + where TTo : unmanaged + { + //Assert(AssertionsEnabled && (SizeOf() == SizeOf())); + return CreateReadOnlySpan(in AsReadOnly(in span.GetReference()), span.Length); + } + + /// Reinterprets the given native integer as a reference. + /// The type of the reference. + /// The native integer to reinterpret. + /// A reference to a value of type . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AsRef(nint source) => ref Unsafe.AsRef((void*)source); + + /// Reinterprets the given native unsigned integer as a reference. + /// The type of the reference. + /// The native unsigned integer to reinterpret. + /// A reference to a value of type . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AsRef(nuint source) => ref Unsafe.AsRef((void*)source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AsRef(in T source) => ref Unsafe.AsRef(in source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref TTo AsRef(in TFrom source) + { + ref var mutable = ref AsRef(in source); + return ref As(ref mutable); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T* AsPointer(ref T source) + where T : unmanaged => (T*)Unsafe.AsPointer(ref source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref readonly TTo AsReadOnly(in TFrom source) + => ref Unsafe.As(ref AsRef(in source)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T* AsReadOnlyPointer(in T source) + where T : unmanaged => AsPointer(ref AsRef(in source)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AsRef(void* source) => ref Unsafe.AsRef(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetReference(this T[] array) => ref MemoryMarshal.GetArrayDataReference(array); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetReference(this T[] array, int index) => ref Unsafe.Add(ref array.GetReference(), index); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetReference(this T[] array, nuint index) => ref Unsafe.Add(ref array.GetReference(), index); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetReference(this Span span) => ref MemoryMarshal.GetReference(span); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetReference(this Span span, int index) => ref Unsafe.Add(ref MemoryMarshal.GetReference(span), index); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetReference(this Span span, nuint index) => ref Unsafe.Add(ref MemoryMarshal.GetReference(span), index); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref readonly T GetReference(this ReadOnlySpan span) => ref MemoryMarshal.GetReference(span); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref readonly T GetReference(this ReadOnlySpan span, int index) => ref Unsafe.Add(ref MemoryMarshal.GetReference(span), index); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref readonly T GetReference(this ReadOnlySpan span, nuint index) => ref Unsafe.Add(ref MemoryMarshal.GetReference(span), index); + + /// Determines if a given reference to a value of type is not a null reference. + /// The type of the reference + /// The reference to check. + /// true if is not a null reference; otherwise, false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNotNullRef(in T source) => !IsNullRef(in source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNullRef(in T source) => Unsafe.IsNullRef(ref AsRef(in source)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T NullRef() => ref Unsafe.NullRef(); + + /// + public static Span CreateSpan(ref T reference, int length) => MemoryMarshal.CreateSpan(ref reference, length); + + /// + public static ReadOnlySpan CreateReadOnlySpan(in T reference, int length) => MemoryMarshal.CreateReadOnlySpan(ref AsRef(in reference), length); + + /// Returns a pointer to the element of the span at index zero. + /// The type of items in . + /// The span from which the pointer is retrieved. + /// A pointer to the item at index zero of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T* GetPointer(this Span span) + where T : unmanaged => AsPointer(ref span.GetReference()); + + /// Returns a pointer to the element of the span at index zero. + /// The type of items in . + /// The span from which the pointer is retrieved. + /// A pointer to the item at index zero of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T* GetPointer(this ReadOnlySpan span) + where T : unmanaged => AsPointer(ref AsRef(in span.GetReference())); +} diff --git a/src/samples/01-ClearScreen/Program.cs b/src/samples/01-ClearScreen/Program.cs index c2c9401..8a83346 100644 --- a/src/samples/01-ClearScreen/Program.cs +++ b/src/samples/01-ClearScreen/Program.cs @@ -12,6 +12,9 @@ public static unsafe class Program { public static void Main() { + string test = StringUtilities.GetString(new sbyte[] { (sbyte)'A', (sbyte)'B', (sbyte)'C' }); + test = StringUtilities.GetString(new ushort[] { 'A', 'B', 'C' }); + using ComPtr factory = default; HResult hr = CreateDXGIFactory1(__uuidof(), (void**)&factory); @@ -24,15 +27,34 @@ public static unsafe class Program } using ComPtr adapter = default; + + using ComPtr factory6 = default; + if (factory.CopyTo(&factory6).Success) + { + for (uint adapterIndex = 0; + factory6.Get()->EnumAdapterByGpuPreference( + adapterIndex, + GpuPreference.HighPerformance, + __uuidof(), + (void**)adapter.ReleaseAndGetAddressOf()).Success; + adapterIndex++) + { + AdapterDescription1 desc = default; + adapter.Get()->GetDesc1(&desc); + + string name = desc.DescriptionStr; + } + } + for (uint adapterIndex = 0; factory.Get()->EnumAdapters1(adapterIndex, adapter.ReleaseAndGetAddressOf()).Success; adapterIndex++) { AdapterDescription1 desc = default; adapter.Get()->GetDesc1(&desc); + + string name = desc.DescriptionStr; } - - } [DllImport("dxgi", ExactSpelling = true)]