From 4364aa867a73314108cb07985fcf1f98fc69f3ac Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Wed, 12 Feb 2025 13:51:13 +0100 Subject: [PATCH] [Swift language feature] Implement `Swift.String` wrapper in C# (#2983) * Implement Swift.String in C# * Update src/Swift.Runtime/src/Swift/SwiftString.cs Co-authored-by: Vitek Karas <10670590+vitek-karas@users.noreply.github.com> * Remove unnecessary GCHandle * Add PInvoke prefix * Add more tests * Use function pointer for callback to marshall string * Fix formatting * Update src/Swift.Runtime/src/Swift/SwiftString.cs Co-authored-by: Jan Kotas * Update src/Swift.Runtime/src/Swift/SwiftString.cs Co-authored-by: Jan Kotas * Use ref type for the ToStringCallbackContext to take unmanaged pointer * Fix formatting --------- Co-authored-by: Vitek Karas <10670590+vitek-karas@users.noreply.github.com> Co-authored-by: Jan Kotas --- .../FunctionalTests/Runtime/RuntimeTests.cs | 195 ++++++++++++++++++ .../Runtime/RuntimeTests.swift | 27 +++ .../{Structs => Runtime}/TestTypes.cs | 3 + .../FunctionalTests/Structs/StructsTests.cs | 70 ------- .../Structs/StructsTests.swift | 10 - src/Swift.Runtime/src/Swift/Data.cs | 10 + .../src/Swift/FoundationDatabase.xml | 3 + src/Swift.Runtime/src/Swift/SwiftArray.cs | 9 +- src/Swift.Runtime/src/Swift/SwiftSet.cs | 6 +- src/Swift.Runtime/src/Swift/SwiftString.cs | 161 +++++++++++++++ .../tests/LibraryTests/SwiftArrayTests.cs | 3 +- .../tests/LibraryTests/SwiftStringTests.cs | 48 +++++ 12 files changed, 460 insertions(+), 85 deletions(-) create mode 100644 src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Runtime/RuntimeTests.cs create mode 100644 src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Runtime/RuntimeTests.swift rename src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/{Structs => Runtime}/TestTypes.cs (93%) create mode 100644 src/Swift.Runtime/src/Swift/Data.cs create mode 100644 src/Swift.Runtime/src/Swift/SwiftString.cs create mode 100644 src/Swift.Runtime/tests/LibraryTests/SwiftStringTests.cs diff --git a/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Runtime/RuntimeTests.cs b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Runtime/RuntimeTests.cs new file mode 100644 index 000000000000..9f1e5eac155f --- /dev/null +++ b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Runtime/RuntimeTests.cs @@ -0,0 +1,195 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Swift; +using BindingsGeneration.Tests; +using Swift; +using Swift.Runtime; +using Swift.Runtime.InteropServices; +using Xunit; + +using Bindings = Swift.RuntimeTests; + + +namespace BindingsGeneration.FunctionalTests +{ + public class RuntimeTests : IClassFixture + { + private readonly TestFixture _fixture; + + public RuntimeTests(TestFixture fixture) + { + _fixture = fixture; + } + + public class TestFixture + { + static TestFixture() + { + InitializeResources(); + } + + private static void InitializeResources() + { + // Initialize + } + } + + [Fact] + public void SmokeTestArray() + { + var array = GetArray(3); + Assert.Equal(3, array.Count); + Assert.Equal(0, array[0]); + Assert.Equal(1, array[1]); + Assert.Equal(2, array[2]); + Assert.Equal(3, SumArray(array)); + } + + [Fact] + public void TestEmptyArray() + { + var array = GetArray(0); + Assert.Equal(0, array.Count); + Assert.Equal(0, SumArray(array)); + } + + [Fact] + public void TestOneElementArray() + { + var array = GetArray(1); + Assert.Equal(1, array.Count); + Assert.Equal(0, array[0]); + Assert.Equal(0, SumArray(array)); + } + + [Fact] + public void TestBigArray() + { + var array = GetArray(10000); + Assert.Equal(10000, array.Count); + var sum = 0; + for (int i = 0; i < 10000; i++) + { + Assert.Equal(i, array[i]); + sum += i; + } + Assert.Equal(sum, SumArray(array)); + } + + // TODO: Remove helper methods when https://github.com/dotnet/runtimelab/issues/2970 + private static unsafe SwiftArray GetArray(int count) + { + ArrayBuffer buffer = PInvoke_GetArray(count); + return SwiftMarshal.MarshalFromSwift>((SwiftHandle)new IntPtr(&buffer)); + } + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })] + [DllImport("Runtime/libRuntimeTests.dylib", EntryPoint = "$s12RuntimeTests8getArray5countSays5Int32VGAE_tF")] + private static extern ArrayBuffer PInvoke_GetArray(int count); + + private static unsafe int SumArray(SwiftArray array) + { + ArrayBuffer buffer = new ArrayBuffer(); + SwiftMarshal.MarshalToSwift>(array, new IntPtr(&buffer)); + return PInvoke_SumArray(buffer); + } + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })] + [DllImport("Runtime/libRuntimeTests.dylib", EntryPoint = "$s12RuntimeTests8sumArray5arrays5Int32VSayAEG_tF")] + private static extern int PInvoke_SumArray(ArrayBuffer array); + + [Fact] + public void SmokeTestSet() + { + var set = GetSet(3); + Assert.Equal(3, set.Count); + Assert.Equal(3, SumSet(set)); + } + + [Fact] + public void TestEmptySet() + { + var set = GetSet(0); + Assert.Equal(0, set.Count); + Assert.Equal(0, SumSet(set)); + } + + [Fact] + public void TestOneElementSet() + { + var set = GetSet(1); + Assert.Equal(1, set.Count); + Assert.Equal(0, SumSet(set)); + } + + [Fact] + public void TestBigSet() + { + var set = GetSet(10000); + Assert.Equal(10000, set.Count); + Assert.Equal(49995000, SumSet(set)); + + } + + // TODO: Remove helper methods when https://github.com/dotnet/runtimelab/issues/2970 + private static unsafe SwiftSet GetSet(int count) + { + Variant variant = PInvoke_GetSet(count); + return SwiftMarshal.MarshalFromSwift>((SwiftHandle)new IntPtr(&variant)); + } + + private static unsafe int SumSet(SwiftSet set) + { + Variant variant = new Variant(); + SwiftMarshal.MarshalToSwift>(set, new IntPtr(&variant)); + return PInvoke_SumSet(variant); + } + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })] + [DllImport("Runtime/libRuntimeTests.dylib", EntryPoint = "$s12RuntimeTests8getArray5countSays5Int32VGAE_tF")] + private static extern Variant PInvoke_GetSet(int count); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })] + [DllImport("Runtime/libRuntimeTests.dylib", EntryPoint = "$s12RuntimeTests8sumArray5arrays5Int32VSayAEG_tF")] + private static extern int PInvoke_SumSet(Variant set); + + [Fact] + public void SmokeTestString() + { + var str = Bindings.RuntimeTests.getString(3); + Assert.Equal(3, str.Length); + Assert.Equal("aaa", str.ToString()); + } + + [Fact] + public void TestEmptyString() + { + var str = Bindings.RuntimeTests.getString(0); + Assert.Equal(0, str.Length); + Assert.Equal(string.Empty, str.ToString()); + Assert.Equal(str.Length, Bindings.RuntimeTests.verifyString(str)); + } + + [Fact] + public void TestOneElementString() + { + var str = Bindings.RuntimeTests.getString(1); + Assert.Equal(1, str.Length); + Assert.Equal("a", str.ToString()); + Assert.Equal(str.Length, Bindings.RuntimeTests.verifyString(str)); + } + + [Fact] + public void TestBigString() + { + var str = Bindings.RuntimeTests.getString(10000); + Assert.Equal(10000, str.Length); + Assert.Equal(new string('a', 10000), str.ToString()); + Assert.Equal(str.Length, Bindings.RuntimeTests.verifyString(str)); + } + } +} diff --git a/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Runtime/RuntimeTests.swift b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Runtime/RuntimeTests.swift new file mode 100644 index 000000000000..21082a1a278f --- /dev/null +++ b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Runtime/RuntimeTests.swift @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import Foundation + +public func getArray(count: Int32) -> Array { + var array = Array() + for i in 0..) -> Int32 +{ + return array.reduce(0, +) +} + +public func getString(count: Int32) -> String { + return String(repeating: "a", count: Int(count)) +} + +public func verifyString(str: String) -> Int32 { + let count = str.count + return str.allSatisfy { $0 == "a" } ? Int32(count) : -1 +} + diff --git a/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Structs/TestTypes.cs b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Runtime/TestTypes.cs similarity index 93% rename from src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Structs/TestTypes.cs rename to src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Runtime/TestTypes.cs index 066974790dcb..bb3735df9ef0 100644 --- a/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Structs/TestTypes.cs +++ b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Runtime/TestTypes.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using Swift; using Swift.Runtime; diff --git a/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Structs/StructsTests.cs b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Structs/StructsTests.cs index b0802fc2dde4..3ab35a9b53e2 100644 --- a/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Structs/StructsTests.cs +++ b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Structs/StructsTests.cs @@ -323,75 +323,5 @@ public async Task TestAsyncStruct() stopwatch.Stop(); Assert.True(Math.Abs(stopwatch.Elapsed.TotalSeconds - seconds) <= 1); } - - [Fact] - public void TestSwiftArray() - { - var array = GetArray(42, 17); - Assert.Equal(2, array.Count); - Assert.Equal(42, array[0]); - Assert.Equal(17, array[1]); - int sum = SumArray(array); - Assert.Equal(42 + 17, sum); - } - - // TODO: Remove helper methods when https://github.com/dotnet/runtimelab/issues/2970 - private static unsafe SwiftArray GetArray(int a, int b) - { - ArrayBuffer buffer = PInvoke_GetArray(a, b); - return SwiftMarshal.MarshalFromSwift>((SwiftHandle)new IntPtr(&buffer)); - } - - [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })] - [DllImport("Structs/libStructsTests.dylib", EntryPoint = "$s12StructsTests8getArray1a1bSays5Int32VGAF_AFtF")] - private static extern ArrayBuffer PInvoke_GetArray(int a, int b); - - private static unsafe int SumArray(SwiftArray array) - { - ArrayBuffer buffer = new ArrayBuffer(); - SwiftMarshal.MarshalToSwift>(array, new IntPtr(&buffer)); - return PInvoke_SumArray(buffer); - } - - [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })] - [DllImport("Structs/libStructsTests.dylib", EntryPoint = "$s12StructsTests8sumArray5arrays5Int32VSayAEG_tF")] - private static extern int PInvoke_SumArray(ArrayBuffer array); - - [Fact] - public void TestSwiftSet() - { - var set = GetSet(42, 17); - Assert.Equal(2, set.Count); - int sum = SumSet(set); - Assert.Equal(42 + 17, sum); - - set = new SwiftSet(); - sum = SumSet(set); - Assert.Equal(0, sum); - } - - // TODO: Remove helper methods when https://github.com/dotnet/runtimelab/issues/2970 - private static unsafe SwiftSet GetSet(int a, int b) - { - Variant variant = PInvoke_GetSet(a, b); - return SwiftMarshal.MarshalFromSwift>((SwiftHandle)new IntPtr(&variant)); - } - - private static unsafe int SumSet(SwiftSet set) - { - Variant variant = new Variant(); - SwiftMarshal.MarshalToSwift>(set, new IntPtr(&variant)); - return PInvoke_SumSet(variant); - } - - [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })] - [DllImport("Structs/libStructsTests.dylib", EntryPoint = "$s12StructsTests8getArray1a1bSays5Int32VGAF_AFtF")] - private static extern Variant PInvoke_GetSet(int a, int b); - - [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })] - [DllImport("Structs/libStructsTests.dylib", EntryPoint = "$s12StructsTests8sumArray5arrays5Int32VSayAEG_tF")] - private static extern int PInvoke_SumSet(Variant array); - - } } diff --git a/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Structs/StructsTests.swift b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Structs/StructsTests.swift index f065eb747daa..48fa751b3ba5 100644 --- a/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Structs/StructsTests.swift +++ b/src/Swift.Bindings/tests/IntegrationTests/FunctionalTests/Structs/StructsTests.swift @@ -247,13 +247,3 @@ public struct TimerStruct { try? await Task.sleep(nanoseconds: 5_000_000_000) } } - -public func getArray(a: Int32, b: Int32) -> Array -{ - return [a, b] -} - -public func sumArray(array: Array) -> Int32 -{ - return array.reduce(0, +) -} diff --git a/src/Swift.Runtime/src/Swift/Data.cs b/src/Swift.Runtime/src/Swift/Data.cs new file mode 100644 index 000000000000..f73188b7af59 --- /dev/null +++ b/src/Swift.Runtime/src/Swift/Data.cs @@ -0,0 +1,10 @@ +/// +/// Represents Foundation.Data type. +/// + +// TODO: https://github.com/dotnet/runtimelab/issues/2992 +public struct Data +{ + private long _flags; + private IntPtr _object; +} diff --git a/src/Swift.Runtime/src/Swift/FoundationDatabase.xml b/src/Swift.Runtime/src/Swift/FoundationDatabase.xml index d50447b58a06..31835843f590 100644 --- a/src/Swift.Runtime/src/Swift/FoundationDatabase.xml +++ b/src/Swift.Runtime/src/Swift/FoundationDatabase.xml @@ -70,5 +70,8 @@ + + + diff --git a/src/Swift.Runtime/src/Swift/SwiftArray.cs b/src/Swift.Runtime/src/Swift/SwiftArray.cs index d4cf53c3d78e..ff4b510ea2b0 100644 --- a/src/Swift.Runtime/src/Swift/SwiftArray.cs +++ b/src/Swift.Runtime/src/Swift/SwiftArray.cs @@ -65,7 +65,8 @@ public unsafe void Dispose() { if (_buffer.storage != IntPtr.Zero) { - Arc.Release(*(IntPtr*)_buffer.storage); + // TODO: https://github.com/dotnet/runtimelab/issues/2851 + Arc.Release(_buffer.storage); _buffer.storage = IntPtr.Zero; GC.SuppressFinalize(this); } @@ -75,7 +76,8 @@ public unsafe void Dispose() { if (_buffer.storage != IntPtr.Zero) { - Arc.Release(*(IntPtr*)_buffer.storage); + // TODO: https://github.com/dotnet/runtimelab/issues/2851 + Arc.Release(_buffer.storage); _buffer.storage = IntPtr.Zero; } } @@ -272,9 +274,11 @@ public unsafe Element this[int index] internal static class SwiftArrayPInvokes { + [UnmanagedCallConv(CallConvs = [typeof(CallConvSwift)])] [DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSaMa")] public static extern TypeMetadata PInvoke_getMetadata(TypeMetadataRequest request, TypeMetadata typeMetadata); + [UnmanagedCallConv(CallConvs = [typeof(CallConvSwift)])] [DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sS2ayxGycfC")] public static extern ArrayBuffer Init(TypeMetadata typeMetadata); @@ -286,6 +290,7 @@ internal static class SwiftArrayPInvokes [DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSayxSicis")] public static unsafe extern void Set(SwiftHandle value, nint index, TypeMetadata elementMetadata, SwiftSelf self); + [UnmanagedCallConv(CallConvs = [typeof(CallConvSwift)])] [DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSa5countSivg")] public static extern nint Count(ArrayBuffer handle, TypeMetadata elementMetadata); diff --git a/src/Swift.Runtime/src/Swift/SwiftSet.cs b/src/Swift.Runtime/src/Swift/SwiftSet.cs index 383fa282098d..42124119424c 100644 --- a/src/Swift.Runtime/src/Swift/SwiftSet.cs +++ b/src/Swift.Runtime/src/Swift/SwiftSet.cs @@ -66,7 +66,8 @@ public unsafe void Dispose() { if (_variant.rawValue != IntPtr.Zero) { - Arc.Release(*(IntPtr*)_variant.rawValue); + // TODO: https://github.com/dotnet/runtimelab/issues/2851 + Arc.Release(_variant.rawValue); _variant.rawValue = IntPtr.Zero; GC.SuppressFinalize(this); } @@ -76,7 +77,8 @@ public unsafe void Dispose() { if (_variant.rawValue != IntPtr.Zero) { - Arc.Release(*(IntPtr*)_variant.rawValue); + // TODO: https://github.com/dotnet/runtimelab/issues/2851 + Arc.Release(_variant.rawValue); _variant.rawValue = IntPtr.Zero; } } diff --git a/src/Swift.Runtime/src/Swift/SwiftString.cs b/src/Swift.Runtime/src/Swift/SwiftString.cs new file mode 100644 index 000000000000..eb9ecd1cdf8f --- /dev/null +++ b/src/Swift.Runtime/src/Swift/SwiftString.cs @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Swift; +using System.Text; +using Swift.Runtime; +using Swift.Runtime.InteropServices; + +namespace Swift; + +/// +/// Represents a Swift string with Foundation.Data payload. +/// +public struct SwiftString : ISwiftObject +{ + private static nuint _payloadSize = SwiftObjectHelper.GetTypeMetadata().Size; + + private Data _payload; + + private static Dictionary _protocolConformanceSymbols; + + static SwiftString() + { + _protocolConformanceSymbols = new Dictionary { }; + } + + public static nuint PayloadSize => _payloadSize; + + public Data Payload => _payload; + + static TypeMetadata ISwiftObject.GetTypeMetadata() + { + return TypeMetadata.Cache.GetOrAdd(typeof(SwiftString), _ => PInvoke_getMetadata()); + } + + static ISwiftObject ISwiftObject.NewFromPayload(SwiftHandle handle) + { + return new SwiftString(handle); + } + + IntPtr ISwiftObject.MarshalToSwift(IntPtr swiftDest) + { + var metadata = SwiftObjectHelper.GetTypeMetadata(); + unsafe + { + fixed (void* _payloadPtr = &_payload) + { + metadata.ValueWitnessTable->InitializeWithCopy((void*)swiftDest, (void*)_payloadPtr, metadata); + } + } + return swiftDest; + } + + /// + /// Gets the protocol conformance descriptor for the given type. + /// + /// + /// + static ProtocolConformanceDescriptor ISwiftObject.GetProtocolConformanceDescriptor() + where TProtocol : class + { + if (!_protocolConformanceSymbols.TryGetValue(typeof(TProtocol), out var symbolName)) + { + throw new SwiftRuntimeException($"Attempted to retrieve protocol conformance descriptor for type SwiftString and protocol {typeof(TProtocol).Name}, but no conformance was found."); + } + return ProtocolConformanceDescriptor.LoadFromSymbol("/usr/lib/swift/libswiftCore.dylib", symbolName); + } + + /// + /// Constructs a new SwiftString from the given handle. + /// + unsafe SwiftString(SwiftHandle handle) + { + _payload = *(Data*)handle; + } + + /// + /// Constructs a new SwiftString from the C# string. + /// + public SwiftString(string str) + { + byte[] utf8Bytes = Encoding.UTF8.GetBytes(str); + unsafe + { + fixed (byte* utf8BytesPtr = utf8Bytes) + { + _payload = PInvoke_Create(utf8BytesPtr, utf8Bytes.Length, 1); + } + } + } + + /// + /// Gets the length of string. + /// + public int Length => (int)PInvoke_GetLength(_payload); + + /// + /// Converts the SwiftString to a C# string. + /// + public override string ToString() + { + var elementType = TypeMetadata.GetTypeMetadataOrThrow(); + var resultType = TypeMetadata.GetTypeMetadataOrThrow(); + + var length = Length; + if (length <= 0) + return string.Empty; + + var contiguousArray = PInvoke_GetUtf8ContiguousArray(_payload); + unsafe + { + ToStringCallbackContext callbackContext = new ToStringCallbackContext { _length = length }; + GCHandle handle = GCHandle.Alloc(callbackContext); + PInvoke_WithUnsafeBytes(&Callback, GCHandle.ToIntPtr(handle), contiguousArray, elementType, resultType); + handle.Free(); + return callbackContext._returnString!; + + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvSwift) })] + static IntPtr Callback(byte* bytes, SwiftSelf context) + { + GCHandle handle = GCHandle.FromIntPtr((IntPtr)context.Value); + ToStringCallbackContext pContext = (ToStringCallbackContext)handle.Target!; + pContext._returnString = Encoding.UTF8.GetString(new ReadOnlySpan(bytes, pContext._length)); + return default; + } + } + } + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })] + [DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSSMa")] + public static extern TypeMetadata PInvoke_getMetadata(); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })] + [DllImport(KnownLibraries.SwiftCore, CharSet = CharSet.Unicode, EntryPoint = "$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC")] + public static unsafe extern Data PInvoke_Create(byte* str, long len, byte flag); + + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })] + [DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSS5countSivg")] + public static extern long PInvoke_GetLength(Data str); + + // https://developer.apple.com/documentation/swift/string/utf8cstring + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })] + [DllImport(KnownLibraries.SwiftCore, EntryPoint = "$sSS11utf8CStrings15ContiguousArrayVys4Int8VGvg")] + public static unsafe extern IntPtr PInvoke_GetUtf8ContiguousArray(Data str); + + // https://developer.apple.com/documentation/swift/contiguousarray/withunsafebytes(_:) + [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })] + [DllImport(KnownLibraries.SwiftCore, EntryPoint = "$ss15ContiguousArrayV15withUnsafeBytesyqd__qd__SWKXEKlF")] + public static extern unsafe IntPtr PInvoke_WithUnsafeBytes(delegate* unmanaged[Swift] callback, IntPtr context, IntPtr contiguousArray, TypeMetadata elementType, TypeMetadata resultType); + + private class ToStringCallbackContext + { + public int _length; + public string? _returnString; + } +} diff --git a/src/Swift.Runtime/tests/LibraryTests/SwiftArrayTests.cs b/src/Swift.Runtime/tests/LibraryTests/SwiftArrayTests.cs index 905c264551e7..7edc44393cd2 100644 --- a/src/Swift.Runtime/tests/LibraryTests/SwiftArrayTests.cs +++ b/src/Swift.Runtime/tests/LibraryTests/SwiftArrayTests.cs @@ -138,7 +138,7 @@ public unsafe void ArrayDispose() Arc.Release(payload); } - private static void PrimitiveArrayTest(T value1, T value2, T overwriteValue) where T : unmanaged + private static void PrimitiveArrayTest(T value1, T value2, T overwriteValue) { var metadata = TypeMetadata.GetTypeMetadataOrThrow>(); Assert.True(metadata.Size > 0); @@ -181,4 +181,5 @@ private static void PrimitiveArrayTest(T value1, T value2, T overwriteValue) [Fact] public void ArrayTestFloat() => PrimitiveArrayTest(4.2f, 1.7f, 10.0f); [Fact] public void ArrayTestDouble() => PrimitiveArrayTest(4.2, 1.7, 10.0); [Fact] public void ArrayTestBool() => PrimitiveArrayTest(true, false, true); + [Fact] public void ArrayTestString() => PrimitiveArrayTest(new SwiftString("Hello"), new SwiftString("World"), new SwiftString("String")); } diff --git a/src/Swift.Runtime/tests/LibraryTests/SwiftStringTests.cs b/src/Swift.Runtime/tests/LibraryTests/SwiftStringTests.cs new file mode 100644 index 000000000000..f2b81682c7a8 --- /dev/null +++ b/src/Swift.Runtime/tests/LibraryTests/SwiftStringTests.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Reflection; +using System.Runtime.InteropServices; +using Swift; +using Swift.Runtime; +using Xunit; + +namespace BindingsGeneration.Tests; + +public class SwiftStringTests : IClassFixture +{ + private readonly TestFixture _fixture; + + public SwiftStringTests(TestFixture fixture) + { + _fixture = fixture; + } + + public class TestFixture + { + static TestFixture() + { + } + + private static void InitializeResources() + { + } + } + + [Fact] + static void SmokeTest() + { + var metadata = TypeMetadata.GetTypeMetadataOrThrow(); + // sizeof(Data) + Assert.Equal((nuint)16, metadata.Size); + + var str = new SwiftString(); + Assert.Equal(0, str.Length); + + string text = "Hello world!"; + str = new SwiftString(text); + + Assert.Equal(text.Length, str.Length); + Assert.Equal(text, str.ToString()); + } +}