diff --git a/Cargo.lock b/Cargo.lock index dd5db90..03c0aef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1192,6 +1192,7 @@ dependencies = [ "uniffi-cs-custom-types-builtin", "uniffi-cs-disposable-fixture", "uniffi-cs-optional-parameters-fixture", + "uniffi-cs-stringify", "uniffi-example-arithmetic", "uniffi-example-callbacks", "uniffi-example-custom-types", @@ -1235,6 +1236,15 @@ dependencies = [ "uniffi_macros", ] +[[package]] +name = "uniffi-cs-stringify" +version = "1.0.0" +dependencies = [ + "paste", + "uniffi", + "uniffi_macros", +] + [[package]] name = "uniffi-example-arithmetic" version = "0.22.0" diff --git a/README.md b/README.md index 5ce3bc1..35ddd70 100644 --- a/README.md +++ b/README.md @@ -23,18 +23,26 @@ Generates bindings file `path/to/definitions.cs` # How to integrate bindings To integrate the bindings into your projects, simply add the generated bindings file to your project. -There are a couple of requirements to compile the generated bindings file: +There are a few requirements depending on your target framework version. + - .NET core `6.0` or higher -- allow `unsafe` code -- allow `Nullable` - -```xml - - net6.0 - true - enable - -``` + ```xml + + net6.0 + true + + ``` + +- .NET framework `4.8` + ```xml + + net48 + 10.0 + true + + + + ``` # Unsupported features diff --git a/bindgen/templates/BigEndianStream.cs b/bindgen/templates/BigEndianStream.cs index 29118be..45e9155 100644 --- a/bindgen/templates/BigEndianStream.cs +++ b/bindgen/templates/BigEndianStream.cs @@ -63,7 +63,9 @@ public void WriteInt(int value) { } public void WriteFloat(float value) { - WriteInt(BitConverter.SingleToInt32Bits(value)); + unsafe { + WriteInt(*((int*)&value)); + } } public void WriteLong(long value) { @@ -116,7 +118,10 @@ public int ReadInt() { } public float ReadFloat() { - return BitConverter.Int32BitsToSingle(ReadInt()); + unsafe { + int value = ReadInt(); + return *((float*)&value); + } } public long ReadLong() { diff --git a/bindgen/templates/CallbackInterfaceRuntime.cs b/bindgen/templates/CallbackInterfaceRuntime.cs index 92f128d..382c0c8 100644 --- a/bindgen/templates/CallbackInterfaceRuntime.cs +++ b/bindgen/templates/CallbackInterfaceRuntime.cs @@ -43,8 +43,9 @@ public bool Remove(ulong handle, out T result) { lock (lock_) { // Possible null reference assignment #pragma warning disable 8601 - if (leftMap.Remove(handle, out result)) { + if (leftMap.TryGetValue(handle, out result)) { #pragma warning restore 8601 + leftMap.Remove(handle); rightMap.Remove(result); return true; } else { diff --git a/bindgen/templates/TimestampHelper.cs b/bindgen/templates/TimestampHelper.cs index 6991671..1e8fc44 100644 --- a/bindgen/templates/TimestampHelper.cs +++ b/bindgen/templates/TimestampHelper.cs @@ -8,6 +8,9 @@ class FfiConverterTimestamp: FfiConverterRustBuffer { // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/TimeSpan.cs private const uint NanosecondsPerTick = 100; + // DateTime.UnixEpoch is not available in net48 + private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + public override DateTime Read(BigEndianStream stream) { var seconds = stream.ReadLong(); var nanoseconds = stream.ReadUInt(); @@ -17,7 +20,7 @@ public override DateTime Read(BigEndianStream stream) { } var ticks = seconds * TimeSpan.TicksPerSecond; ticks += (nanoseconds / NanosecondsPerTick) * sign; - return DateTime.UnixEpoch.AddTicks(ticks); + return UnixEpoch.AddTicks(ticks); } public override int AllocationSize(DateTime value) { @@ -26,7 +29,7 @@ public override int AllocationSize(DateTime value) { } public override void Write(DateTime value, BigEndianStream stream) { - var epochOffset = value.Subtract(DateTime.UnixEpoch); + var epochOffset = value.Subtract(UnixEpoch); int sign = 1; if (epochOffset.Ticks < 0) { diff --git a/dotnet-tests/UniffiCS.BindingTests/OptionalParameterTests.cs b/dotnet-tests/UniffiCS.BindingTests/OptionalParameterTests.cs index 95c1ab4..25f0484 100644 --- a/dotnet-tests/UniffiCS.BindingTests/OptionalParameterTests.cs +++ b/dotnet-tests/UniffiCS.BindingTests/OptionalParameterTests.cs @@ -24,4 +24,4 @@ public void OptionalParameter_CanBeSpecified() string message = Hello(person); Assert.Equal("Hello John Connor!", message); } -} \ No newline at end of file +} diff --git a/dotnet-tests/UniffiCS.BindingTests/TestNumericLimits.cs b/dotnet-tests/UniffiCS.BindingTests/TestNumericLimits.cs new file mode 100644 index 0000000..c01435e --- /dev/null +++ b/dotnet-tests/UniffiCS.BindingTests/TestNumericLimits.cs @@ -0,0 +1,98 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using uniffi.stringify; + +namespace UniffiCS.BindingTests; + +public class TestNumericLimits +{ + [Fact] + public void NumericLimitsAreTheSame() + { + // At first, I tried to write this test by stringifying values in C# and Rust, then + // comparing the stringified result. Turns out C# and Rust format floating point values + // differently, so comparing the result is useless. I tried to change the formatting + // settings in few ways, but I couldn't find formatting settings that would produce + // exact results. + + var meanValue = 0x1234_5678_9123_4567; + + ParseTest( + StringifyMethods.ParseI8, + SByte.MinValue, + SByte.MaxValue, + (sbyte)meanValue); + + ParseTest( + StringifyMethods.ParseI16, + Int16.MinValue, + Int16.MaxValue, + (short)meanValue); + + ParseTest( + StringifyMethods.ParseI32, + Int32.MinValue, + Int32.MaxValue, + (int)meanValue); + + ParseTest( + StringifyMethods.ParseI64, + Int64.MinValue, + Int64.MaxValue, + (long)meanValue); + + ParseTest( + StringifyMethods.ParseU8, + Byte.MinValue, + Byte.MaxValue, + (byte)meanValue); + + ParseTest( + StringifyMethods.ParseU16, + UInt16.MinValue, + UInt16.MaxValue, + (ushort)meanValue); + + ParseTest( + StringifyMethods.ParseU32, + UInt32.MinValue, + UInt32.MaxValue, + (uint)meanValue); + + ParseTest( + StringifyMethods.ParseU64, + UInt64.MinValue, + UInt64.MaxValue, + (ulong)meanValue); + + ParseTest( + StringifyMethods.ParseF32, + Single.MinValue, + Single.MaxValue, + Single.Epsilon); + + ParseTest( + StringifyMethods.ParseF64, + Double.MinValue, + Double.MaxValue, + Double.Epsilon); + } + + static void ParseTest( + Func parseMethod, + T minValue, + T maxValue, + T meanValue + ) + { + // Possible null reference assignment + #pragma warning disable 8602 + #pragma warning disable 8604 + Assert.Equal(minValue, parseMethod(minValue.ToString())); + Assert.Equal(maxValue, parseMethod(maxValue.ToString())); + Assert.Equal(meanValue, parseMethod(meanValue.ToString())); + #pragma warning restore 8602 + #pragma warning restore 8604 + } +} diff --git a/dotnet-tests/UniffiCS/UniffiCS.csproj b/dotnet-tests/UniffiCS/UniffiCS.csproj index d558178..c1765d4 100644 --- a/dotnet-tests/UniffiCS/UniffiCS.csproj +++ b/dotnet-tests/UniffiCS/UniffiCS.csproj @@ -1,13 +1,15 @@ - net6.0 + net48;net6.0 + 10.0 true - enable + + diff --git a/fixtures/Cargo.toml b/fixtures/Cargo.toml index 21edf7c..94c9635 100644 --- a/fixtures/Cargo.toml +++ b/fixtures/Cargo.toml @@ -13,6 +13,7 @@ global-methods-class-name = { path = "global-methods-class-name" } uniffi-cs-custom-types-builtin = { path = "custom-types-builtin" } uniffi-cs-disposable-fixture = { path = "disposable" } uniffi-cs-optional-parameters-fixture = { path = "optional-parameters" } +uniffi-cs-stringify = { path = "stringify" } uniffi-example-arithmetic = { path = "../3rd-party/uniffi-rs/examples/arithmetic" } uniffi-example-callbacks = { path = "../3rd-party/uniffi-rs/examples/callbacks" } uniffi-example-custom-types = { path = "../3rd-party/uniffi-rs/examples/custom-types" } diff --git a/fixtures/src/lib.rs b/fixtures/src/lib.rs index 13579c5..018f36e 100644 --- a/fixtures/src/lib.rs +++ b/fixtures/src/lib.rs @@ -21,4 +21,5 @@ mod uniffi_fixtures { uniffi_cs_custom_types_builtin::uniffi_reexport_scaffolding!(); uniffi_cs_disposable::uniffi_reexport_scaffolding!(); uniffi_cs_optional_parameters::uniffi_reexport_scaffolding!(); + stringify::uniffi_reexport_scaffolding!(); } diff --git a/fixtures/stringify/Cargo.toml b/fixtures/stringify/Cargo.toml new file mode 100644 index 0000000..fd88f7f --- /dev/null +++ b/fixtures/stringify/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "uniffi-cs-stringify" +version = "1.0.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["lib", "cdylib"] +name = "stringify" + +[dependencies] +paste = "1.0" +uniffi = {path = "../../3rd-party/uniffi-rs/uniffi", features=["build"]} +uniffi_macros = {path = "../../3rd-party/uniffi-rs/uniffi_macros"} + +[build-dependencies] +uniffi = {path = "../../3rd-party/uniffi-rs/uniffi", features=["bindgen-tests"]} diff --git a/fixtures/stringify/src/lib.rs b/fixtures/stringify/src/lib.rs new file mode 100644 index 0000000..fa73ca4 --- /dev/null +++ b/fixtures/stringify/src/lib.rs @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use paste::paste; + +uniffi::setup_scaffolding!(); + +macro_rules! define_parse_function { + ($type:ty) => { + paste! { + #[uniffi::export] + fn [](value: $type) -> String { + value.to_string() + } + + #[uniffi::export] + fn [](value: String) -> $type { + value.parse::<$type>().unwrap() + } + } + }; +} + +define_parse_function!(i8); +define_parse_function!(i16); +define_parse_function!(i32); +define_parse_function!(i64); +define_parse_function!(u8); +define_parse_function!(u16); +define_parse_function!(u32); +define_parse_function!(u64); +define_parse_function!(f32); +define_parse_function!(f64);