Skip to content

Commit

Permalink
Generate net48 compliant bindings
Browse files Browse the repository at this point in the history
The following APIs are not available in net48:
- `BitConverter.SingleToInt32Bits`
- `BitConverter.Int32BitsToSingle`
- `Dictionary.TryGetValue`
- `DateTime.UnixEpoch`

Signed-off-by: Kristupas Antanavičius <[email protected]>
  • Loading branch information
arg0d committed Feb 8, 2024
1 parent ce23ac2 commit b6273bd
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 19 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 19 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
</PropertyGroup>
```
```xml
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
```

- .NET framework `4.8`
```xml
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<LangVersion>10.0</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageReference Include="IsExternalInit" Version="1.0.3"/>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
</PropertyGroup>
```

# Unsupported features

Expand Down
9 changes: 7 additions & 2 deletions bindgen/templates/BigEndianStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -116,7 +118,10 @@ public int ReadInt() {
}

public float ReadFloat() {
return BitConverter.Int32BitsToSingle(ReadInt());
unsafe {
int value = ReadInt();
return *((float*)&value);
}
}

public long ReadLong() {
Expand Down
3 changes: 2 additions & 1 deletion bindgen/templates/CallbackInterfaceRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 5 additions & 2 deletions bindgen/templates/TimestampHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ class FfiConverterTimestamp: FfiConverterRustBuffer<DateTime> {
// 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();
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ public void OptionalParameter_CanBeSpecified()
string message = Hello(person);
Assert.Equal("Hello John Connor!", message);
}
}
}
98 changes: 98 additions & 0 deletions dotnet-tests/UniffiCS.BindingTests/TestNumericLimits.cs
Original file line number Diff line number Diff line change
@@ -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<sbyte>(
StringifyMethods.ParseI8,
SByte.MinValue,
SByte.MaxValue,
(sbyte)meanValue);

ParseTest<short>(
StringifyMethods.ParseI16,
Int16.MinValue,
Int16.MaxValue,
(short)meanValue);

ParseTest<int>(
StringifyMethods.ParseI32,
Int32.MinValue,
Int32.MaxValue,
(int)meanValue);

ParseTest<long>(
StringifyMethods.ParseI64,
Int64.MinValue,
Int64.MaxValue,
(long)meanValue);

ParseTest<byte>(
StringifyMethods.ParseU8,
Byte.MinValue,
Byte.MaxValue,
(byte)meanValue);

ParseTest<ushort>(
StringifyMethods.ParseU16,
UInt16.MinValue,
UInt16.MaxValue,
(ushort)meanValue);

ParseTest<uint>(
StringifyMethods.ParseU32,
UInt32.MinValue,
UInt32.MaxValue,
(uint)meanValue);

ParseTest<ulong>(
StringifyMethods.ParseU64,
UInt64.MinValue,
UInt64.MaxValue,
(ulong)meanValue);

ParseTest<float>(
StringifyMethods.ParseF32,
Single.MinValue,
Single.MaxValue,
Single.Epsilon);

ParseTest<double>(
StringifyMethods.ParseF64,
Double.MinValue,
Double.MaxValue,
Double.Epsilon);
}

static void ParseTest<T>(
Func<String, T> 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
}
}
6 changes: 4 additions & 2 deletions dotnet-tests/UniffiCS/UniffiCS.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFrameworks>net48;net6.0</TargetFrameworks>
<LangVersion>10.0</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
</PropertyGroup>

<!-- Allow testing internals of generated code -->
<ItemGroup>
<InternalsVisibleTo Include="UniffiCS.BindingTests" />
<PackageReference Include="IsExternalInit" Version="1.0.3"/>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions fixtures/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
1 change: 1 addition & 0 deletions fixtures/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!();
}
17 changes: 17 additions & 0 deletions fixtures/stringify/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"]}
34 changes: 34 additions & 0 deletions fixtures/stringify/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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 [<to_string_ $type>](value: $type) -> String {
value.to_string()
}

#[uniffi::export]
fn [<parse_ $type>](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);

0 comments on commit b6273bd

Please sign in to comment.