mirror of https://github.com/ppy/SDL3-CS.git
Merge pull request #37 from Susko3/add-Utf8String-helper
Add `Utf8String` helper for safely passing in strings to native methods
This commit is contained in:
commit
f07bc5b0d3
|
|
@ -9,7 +9,7 @@ indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*g.cs]
|
[*.g.cs]
|
||||||
generated_code = true
|
generated_code = true
|
||||||
|
|
||||||
[*.cs]
|
[*.cs]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/contentModel.xml
|
||||||
|
/.idea.SDL3-CS.Desktop.iml
|
||||||
|
/modules.xml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
SDL3-CS.Desktop
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -11,9 +11,9 @@ namespace SDL3.SourceGeneration
|
||||||
None,
|
None,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Change <c>const char*</c> function parameters to <c>ReadOnlySpan<byte></c>.
|
/// Change <c>const char*</c> function parameters to <see cref="Helper.Utf8StringStructName"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ChangeParamsToReadOnlySpan = 1 << 0,
|
ChangeParamsToUtf8String = 1 << 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Change <c>char *</c> or <c>const char *</c> return type to <see cref="string"/>.
|
/// Change <c>char *</c> or <c>const char *</c> return type to <see cref="string"/>.
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,8 @@ using System;
|
||||||
{
|
{
|
||||||
if (param.IsTypeConstCharPtr())
|
if (param.IsTypeConstCharPtr())
|
||||||
{
|
{
|
||||||
Debug.Assert(gm.RequiredChanges.HasFlag(Changes.ChangeParamsToReadOnlySpan));
|
Debug.Assert(gm.RequiredChanges.HasFlag(Changes.ChangeParamsToUtf8String));
|
||||||
yield return param.WithType(SyntaxFactory.ParseTypeName("ReadOnlySpan<byte>"))
|
yield return param.WithType(SyntaxFactory.ParseTypeName(Helper.Utf8StringStructName))
|
||||||
.WithAttributeLists(SyntaxFactory.List<AttributeListSyntax>());
|
.WithAttributeLists(SyntaxFactory.List<AttributeListSyntax>());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -102,7 +102,7 @@ using System;
|
||||||
|
|
||||||
foreach (var param in gm.NativeMethod.ParameterList.Parameters.Where(p => p.IsTypeConstCharPtr()).Reverse())
|
foreach (var param in gm.NativeMethod.ParameterList.Parameters.Where(p => p.IsTypeConstCharPtr()).Reverse())
|
||||||
{
|
{
|
||||||
Debug.Assert(gm.RequiredChanges.HasFlag(Changes.ChangeParamsToReadOnlySpan));
|
Debug.Assert(gm.RequiredChanges.HasFlag(Changes.ChangeParamsToUtf8String));
|
||||||
|
|
||||||
expr = SyntaxFactory.FixedStatement(
|
expr = SyntaxFactory.FixedStatement(
|
||||||
SyntaxFactory.VariableDeclaration(
|
SyntaxFactory.VariableDeclaration(
|
||||||
|
|
@ -161,7 +161,7 @@ using System;
|
||||||
{
|
{
|
||||||
if (param.IsTypeConstCharPtr())
|
if (param.IsTypeConstCharPtr())
|
||||||
{
|
{
|
||||||
Debug.Assert(gm.RequiredChanges.HasFlag(Changes.ChangeParamsToReadOnlySpan));
|
Debug.Assert(gm.RequiredChanges.HasFlag(Changes.ChangeParamsToUtf8String));
|
||||||
yield return SyntaxFactory.Argument(SyntaxFactory.IdentifierName(param.Identifier.ValueText + pointer_suffix));
|
yield return SyntaxFactory.Argument(SyntaxFactory.IdentifierName(param.Identifier.ValueText + pointer_suffix));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ namespace SDL3.SourceGeneration
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public const string UnsafePrefix = "Unsafe_";
|
public const string UnsafePrefix = "Unsafe_";
|
||||||
|
|
||||||
|
public const string Utf8StringStructName = "Utf8String";
|
||||||
|
|
||||||
public static bool IsVoid(this TypeSyntax type) => type is PredefinedTypeSyntax predefined
|
public static bool IsVoid(this TypeSyntax type) => type is PredefinedTypeSyntax predefined
|
||||||
&& predefined.Keyword.IsKind(SyntaxKind.VoidKeyword);
|
&& predefined.Keyword.IsKind(SyntaxKind.VoidKeyword);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ namespace SDL3.SourceGeneration
|
||||||
foreach (var parameter in method.ParameterList.Parameters)
|
foreach (var parameter in method.ParameterList.Parameters)
|
||||||
{
|
{
|
||||||
if (parameter.IsTypeConstCharPtr())
|
if (parameter.IsTypeConstCharPtr())
|
||||||
changes |= Changes.ChangeParamsToReadOnlySpan;
|
changes |= Changes.ChangeParamsToUtf8String;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes != Changes.None)
|
if (changes != Changes.None)
|
||||||
|
|
|
||||||
|
|
@ -14,23 +14,11 @@ namespace SDL3.Tests
|
||||||
{
|
{
|
||||||
Console.OutputEncoding = Encoding.UTF8;
|
Console.OutputEncoding = Encoding.UTF8;
|
||||||
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
// Encoding.UTF8.GetBytes can churn out null pointers and doesn't guarantee null termination
|
|
||||||
fixed (byte* badPointer = Encoding.UTF8.GetBytes(""))
|
|
||||||
Debug.Assert(badPointer == null);
|
|
||||||
|
|
||||||
fixed (byte* pointer = UTF8GetBytes(""))
|
|
||||||
{
|
|
||||||
Debug.Assert(pointer != null);
|
|
||||||
Debug.Assert(pointer[0] == '\0');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_SetHint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, "null byte \0 in string"u8);
|
SDL_SetHint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, "null byte \0 in string"u8);
|
||||||
Debug.Assert(SDL_GetHint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4) == "null byte ");
|
Debug.Assert(SDL_GetHint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4) == "null byte ");
|
||||||
|
|
||||||
SDL_SetHint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, "1"u8);
|
SDL_SetHint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, "1"u8);
|
||||||
|
SDL_SetHint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, "1");
|
||||||
|
|
||||||
using (var window = new MyWindow())
|
using (var window = new MyWindow())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,15 @@
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup Label="Package References">
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0"/>
|
||||||
|
<PackageReference Include="NUnit" Version="3.13.3"/>
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\SDL3-CS\SDL3-CS.csproj"/>
|
<ProjectReference Include="..\SDL3-CS\SDL3-CS.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using SDL;
|
||||||
|
|
||||||
|
namespace SDL3.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestUtf8String
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestNoImplicitConversion()
|
||||||
|
{
|
||||||
|
checkNull(null);
|
||||||
|
checkNull(default);
|
||||||
|
checkNull(new Utf8String()); // don't do this in actual code
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(null, -1)]
|
||||||
|
[TestCase("", 1)]
|
||||||
|
[TestCase("\0", 1)]
|
||||||
|
[TestCase("test", 5)]
|
||||||
|
[TestCase("test\0", 5)]
|
||||||
|
[TestCase("test\0test", 10)]
|
||||||
|
[TestCase("test\0test\0", 10)]
|
||||||
|
public static void TestString(string? str, int expectedLength)
|
||||||
|
{
|
||||||
|
if (str == null)
|
||||||
|
checkNull(str);
|
||||||
|
else
|
||||||
|
check(str, expectedLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public static void TestNullSpan()
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> span = null;
|
||||||
|
checkNull(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public static void TestDefaultSpan()
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> span = default;
|
||||||
|
checkNull(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public static void TestNewSpan()
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>();
|
||||||
|
checkNull(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public static void TestReadOnlySpan()
|
||||||
|
{
|
||||||
|
check(""u8, 1);
|
||||||
|
check("\0"u8, 1);
|
||||||
|
check("test"u8, 5);
|
||||||
|
check("test\0"u8, 5);
|
||||||
|
check("test\0test"u8, 10);
|
||||||
|
check("test\0test\0"u8, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe void checkNull(Utf8String s)
|
||||||
|
{
|
||||||
|
Assert.That(s.Raw == null, "s.Raw == null");
|
||||||
|
Assert.That(s.Raw.Length, Is.EqualTo(0));
|
||||||
|
|
||||||
|
fixed (byte* ptr = s)
|
||||||
|
{
|
||||||
|
Assert.That(ptr == null, "ptr == null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe void check(Utf8String s, int expectedLength)
|
||||||
|
{
|
||||||
|
Assert.That(s.Raw.Length, Is.EqualTo(expectedLength));
|
||||||
|
|
||||||
|
fixed (byte* ptr = s)
|
||||||
|
{
|
||||||
|
Assert.That(ptr != null, "ptr != null");
|
||||||
|
Assert.That(ptr[s.Raw.Length - 1], Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("SDL3-CS.Tests")]
|
||||||
|
|
@ -2,9 +2,7 @@
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace SDL
|
namespace SDL
|
||||||
{
|
{
|
||||||
|
|
@ -27,17 +25,5 @@ namespace SDL
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// UTF8 encodes a managed <c>string</c> to a <c>byte</c> array suitable for use in <c>ReadOnlySpan<byte></c> parameters of SDL functions.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="s">The <c>string</c> to encode.</param>
|
|
||||||
/// <returns>A null-terminated byte array.</returns>
|
|
||||||
public static byte[] UTF8GetBytes(string s)
|
|
||||||
{
|
|
||||||
byte[] array = Encoding.UTF8.GetBytes(s + '\0');
|
|
||||||
Debug.Assert(array[^1] == '\0');
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace SDL
|
namespace SDL
|
||||||
{
|
{
|
||||||
public static partial class SDL3
|
public static partial class SDL3
|
||||||
{
|
{
|
||||||
public static void SDL_LogSetPriority(SDL_LogCategory category, SDL_LogPriority priority) => SDL_LogSetPriority((int)category, priority);
|
public static void SDL_LogSetPriority(SDL_LogCategory category, SDL_LogPriority priority) => SDL_LogSetPriority((int)category, priority);
|
||||||
public static SDL_LogPriority SDL_LogGetPriority(SDL_LogCategory category) => SDL_LogGetPriority((int)category);
|
public static SDL_LogPriority SDL_LogGetPriority(SDL_LogCategory category) => SDL_LogGetPriority((int)category);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace SDL
|
namespace SDL
|
||||||
{
|
{
|
||||||
public partial struct SDL_MessageBoxButtonData
|
public partial struct SDL_MessageBoxButtonData
|
||||||
|
|
@ -18,7 +16,7 @@ namespace SDL
|
||||||
public static partial class SDL3
|
public static partial class SDL3
|
||||||
{
|
{
|
||||||
// public static int SDL_ShowSimpleMessageBox([NativeTypeName("Uint32")] uint flags, [NativeTypeName("const char *")] byte* title, [NativeTypeName("const char *")] byte* message, SDL_Window* window);
|
// public static int SDL_ShowSimpleMessageBox([NativeTypeName("Uint32")] uint flags, [NativeTypeName("const char *")] byte* title, [NativeTypeName("const char *")] byte* message, SDL_Window* window);
|
||||||
public static unsafe int SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags flags, ReadOnlySpan<byte> title, ReadOnlySpan<byte> message, SDL_Window* window)
|
public static unsafe int SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags flags, Utf8String title, Utf8String message, SDL_Window* window)
|
||||||
=> SDL_ShowSimpleMessageBox((uint)flags, title, message, window);
|
=> SDL_ShowSimpleMessageBox((uint)flags, title, message, window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace SDL
|
namespace SDL
|
||||||
{
|
{
|
||||||
public partial struct SDL_RendererInfo
|
public partial struct SDL_RendererInfo
|
||||||
|
|
@ -12,7 +10,7 @@ namespace SDL
|
||||||
|
|
||||||
public static partial class SDL3
|
public static partial class SDL3
|
||||||
{
|
{
|
||||||
public static unsafe SDL_Renderer* SDL_CreateRenderer(SDL_Window* window, ReadOnlySpan<byte> name, SDL_RendererFlags flags)
|
public static unsafe SDL_Renderer* SDL_CreateRenderer(SDL_Window* window, Utf8String name, SDL_RendererFlags flags)
|
||||||
=> SDL_CreateRenderer(window, name, (uint)flags);
|
=> SDL_CreateRenderer(window, name, (uint)flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SDL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Null pointer or a null-byte terminated UTF8 string suitable for use in native methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Should only be instantiated through implicit conversions or with <c>null</c>.</remarks>
|
||||||
|
public readonly ref struct Utf8String
|
||||||
|
{
|
||||||
|
internal readonly ReadOnlySpan<byte> Raw;
|
||||||
|
|
||||||
|
private Utf8String(ReadOnlySpan<byte> raw)
|
||||||
|
{
|
||||||
|
Raw = raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator Utf8String(string? str)
|
||||||
|
{
|
||||||
|
if (str == null)
|
||||||
|
return new Utf8String(null);
|
||||||
|
|
||||||
|
if (str.EndsWith('\0'))
|
||||||
|
return new Utf8String(Encoding.UTF8.GetBytes(str));
|
||||||
|
|
||||||
|
return new Utf8String(Encoding.UTF8.GetBytes(str + '\0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator Utf8String(ReadOnlySpan<byte> raw)
|
||||||
|
{
|
||||||
|
if (raw == null)
|
||||||
|
return new Utf8String(null);
|
||||||
|
|
||||||
|
if (raw.Length == 0)
|
||||||
|
return new Utf8String(new ReadOnlySpan<byte>([0]));
|
||||||
|
|
||||||
|
if (raw[^1] != 0)
|
||||||
|
{
|
||||||
|
byte[] copy = new byte[raw.Length + 1];
|
||||||
|
raw.CopyTo(copy);
|
||||||
|
raw = copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Utf8String(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ref readonly byte GetPinnableReference() => ref Raw.GetPinnableReference();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue