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);