├── .cargo
└── config.toml
├── .gitallowed
├── .github
└── workflows
│ └── cs.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Cargo.lock
├── Cargo.toml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── bindgen
├── Cargo.toml
├── askama.toml
├── src
│ ├── gen_cs
│ │ ├── callback_interface.rs
│ │ ├── compounds.rs
│ │ ├── custom.rs
│ │ ├── enum_.rs
│ │ ├── external.rs
│ │ ├── filters.rs
│ │ ├── formatting.rs
│ │ ├── miscellany.rs
│ │ ├── mod.rs
│ │ ├── object.rs
│ │ ├── primitives.rs
│ │ └── record.rs
│ ├── lib.rs
│ └── main.rs
└── templates
│ ├── Async.cs
│ ├── BigEndianStream.cs
│ ├── BooleanHelper.cs
│ ├── BytesTemplate.cs
│ ├── CallbackInterfaceImpl.cs
│ ├── CallbackInterfaceTemplate.cs
│ ├── CallbackResponseStatus.cs
│ ├── ConcurrentHandleMap.cs
│ ├── CustomTypeTemplate.cs
│ ├── DurationHelper.cs
│ ├── EnumTemplate.cs
│ ├── ErrorTemplate.cs
│ ├── ExternalTypeTemplate.cs
│ ├── FfiConverterTemplate.cs
│ ├── Float32Helper.cs
│ ├── Float64Helper.cs
│ ├── Helpers.cs
│ ├── Int16Helper.cs
│ ├── Int32Helper.cs
│ ├── Int64Helper.cs
│ ├── Int8Helper.cs
│ ├── MapTemplate.cs
│ ├── NamespaceLibraryTemplate.cs
│ ├── ObjectTemplate.cs
│ ├── OptionalTemplate.cs
│ ├── RecordTemplate.cs
│ ├── RustBufferTemplate.cs
│ ├── SequenceTemplate.cs
│ ├── StringHelper.cs
│ ├── TimestampHelper.cs
│ ├── TopLevelFunctionTemplate.cs
│ ├── Types.cs
│ ├── UInt16Helper.cs
│ ├── UInt32Helper.cs
│ ├── UInt64Helper.cs
│ ├── UInt8Helper.cs
│ ├── macros.cs
│ └── wrapper.cs
├── build.sh
├── details
└── 1-empty-list-as-default-method-parameter.md
├── docker.sh
├── docs
├── CONFIGURATION.md
├── RELEASE.md
└── VERSION_UPGRADE.md
├── dotnet-tests
├── .editorconfig
├── UniffiCS.BindingTests
│ ├── CustomTestFramework.cs
│ ├── OptionalParameterTests.cs
│ ├── TestArithmetic.cs
│ ├── TestBigEndianStream.cs
│ ├── TestCallbacks.cs
│ ├── TestCallbacksFixture.cs
│ ├── TestChronological.cs
│ ├── TestCoverall.cs
│ ├── TestCustomTypes.cs
│ ├── TestCustomTypesBuiltin.cs
│ ├── TestDisposable.cs
│ ├── TestDocstring.cs
│ ├── TestErrorTypes.cs
│ ├── TestFutures.cs
│ ├── TestGeometry.cs
│ ├── TestGlobalMethodsClassName.cs
│ ├── TestIssue110.cs
│ ├── TestIssue28.cs
│ ├── TestIssue60.cs
│ ├── TestIssue75.cs
│ ├── TestIssue76.cs
│ ├── TestNullToEmptyString.cs
│ ├── TestNumericLimits.cs
│ ├── TestPositionalEnums.cs
│ ├── TestRondpoint.cs
│ ├── TestSprites.cs
│ ├── TestTodoList.cs
│ ├── TestTraitMethod.cs
│ ├── TestTraits.cs
│ ├── UniffiCS.BindingTests.csproj
│ ├── Usings.cs
│ └── xunit.runner.json
├── UniffiCS.sln
└── UniffiCS
│ └── UniffiCS.csproj
├── fixtures
├── Cargo.toml
├── custom-types-builtin
│ ├── Cargo.toml
│ ├── build.rs
│ └── src
│ │ ├── custom_types_builtin.udl
│ │ └── lib.rs
├── disposable
│ ├── Cargo.toml
│ ├── build.rs
│ └── src
│ │ ├── disposable.udl
│ │ └── lib.rs
├── global-methods-class-name
│ ├── Cargo.toml
│ ├── build.rs
│ ├── src
│ │ ├── global_methods_class_name.udl
│ │ └── lib.rs
│ └── uniffi.toml
├── null-to-empty-string
│ ├── Cargo.toml
│ ├── build.rs
│ ├── src
│ │ ├── lib.rs
│ │ └── null_to_empty_string.udl
│ └── uniffi.toml
├── optional-parameters
│ ├── Cargo.toml
│ └── src
│ │ └── lib.rs
├── positional-enums
│ ├── Cargo.toml
│ └── src
│ │ └── lib.rs
├── regressions
│ ├── issue-110
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ ├── build.rs
│ │ └── src
│ │ │ ├── issue-110.udl
│ │ │ └── lib.rs
│ ├── issue-28
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ ├── build.rs
│ │ └── src
│ │ │ ├── issue-28.udl
│ │ │ └── lib.rs
│ ├── issue-60
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ └── src
│ │ │ └── lib.rs
│ ├── issue-75
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ └── src
│ │ │ └── lib.rs
│ └── issue-76
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ └── src
│ │ └── lib.rs
├── src
│ └── lib.rs
└── stringify
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── generate_bindings.sh
├── rust-toolchain.toml
├── test_bindings.sh
└── uniffi-test-fixtures.toml
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [net]
2 | git-fetch-with-cli = true
3 |
--------------------------------------------------------------------------------
/.gitallowed:
--------------------------------------------------------------------------------
1 | @nordsec\.com
2 |
--------------------------------------------------------------------------------
/.github/workflows/cs.yml:
--------------------------------------------------------------------------------
1 | name: C#
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 | container:
16 | image: rust:1.81
17 | steps:
18 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
19 | with:
20 | submodules: 'true'
21 | - name: Build
22 | run: |
23 | cargo build --release --package uniffi-bindgen-cs
24 |
25 | test-bindings:
26 | runs-on: ubuntu-latest
27 | container:
28 | image: ghcr.io/nordsecurity/uniffi-bindgen-cs-test-runner:v0.1.0
29 | steps:
30 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
31 | with:
32 | submodules: 'true'
33 | - name: Test bindings
34 | shell: bash
35 | env:
36 | # Github sets HOME to /github/home and breaks dependencies in Docker image..
37 | # https://github.com/actions/runner/issues/863
38 | HOME: /root
39 | run: |
40 | source ~/.bashrc
41 | ./build.sh
42 | ./generate_bindings.sh
43 | ./test_bindings.sh
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /testing
3 | bin/
4 | gen/
5 | obj/
6 | .idea/
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### UNRELEASED
2 |
3 | ### v0.9.1+v0.28.3
4 | - Add calling convention cdecl to DllImport
5 | - User type "String" shadows native type System.String [#110](https://github.com/NordSecurity/uniffi-bindgen-cs/issues/110)
6 |
7 | ### v0.9.0+v0.28.3
8 | - **BREAKING** Update to a newer version of uniffi-rs
9 | - **NEW**: Traits/WithForeign
10 | - **NEW**: Objects as errors
11 | - **NEW**: Async traits
12 |
13 | ### v0.8.4+v0.25.0
14 |
15 | - **NEW**: Implement async methods [#101](https://github.com/NordSecurity/uniffi-bindgen-cs/pull/101)
16 | - Fix enums/errors case names shadowing parameters to top level type names [#95](https://github.com/NordSecurity/uniffi-bindgen-cs/pull/95)
17 | - Ensure type names are generated uniformly independent of capitalization [#103](https://github.com/NordSecurity/uniffi-bindgen-cs/pull/103)
18 | - Implement Eq and Hash trait methods [#97](https://github.com/NordSecurity/uniffi-bindgen-cs/pull/97)
19 | - Fix empty struct [#94](https://github.com/NordSecurity/uniffi-bindgen-cs/pull/94)
20 | - Fix error type named "Error" [#93](https://github.com/NordSecurity/uniffi-bindgen-cs/pull/93)
21 |
22 | ### v0.8.3+v0.25.0
23 |
24 | - **IMPORTANT**: Fix short-lived callback lifetimes [#79](https://github.com/NordSecurity/uniffi-bindgen-cs/issues/79)
25 | - Fix xml doc generation for fields in a record (#87)
26 |
27 | ### v0.8.2+v0.25.0
28 |
29 | - Update C# callback syntax to work on iOS [#84](https://github.com/NordSecurity/uniffi-bindgen-cs/issues/84)
30 |
31 | ### v0.8.1+v0.25.0
32 |
33 | - Add a configuration option `null_string_to_empty` to convert non-optional null strings to empty strings
34 |
35 | ### v0.8.0+v0.25.0
36 |
37 | - **BREAKING**: Change default access modifier from `public` to `internal` for "exported" uniffi symbols
38 | - Fix incorrect ordering of record fields with default values
39 | - Make generated bindings compatible with .NET framework `4.8`
40 | - `uniffi-bindgen-cs` will format generated bindings using csharpier, if it's installed
41 | - Prefix generated bindings file with `// `
42 |
43 | ----
44 |
45 | ### v0.7.0+v0.25.0
46 |
47 | - **BREAKING**: Emit `byte[]` instead of `List` for `bytes` type [#53](https://github.com/NordSecurity/uniffi-bindgen-cs/pull/53).
48 |
49 | ----
50 |
51 | ### v0.6.0+v0.25.0
52 |
53 | - **BREAKING**: Update to uniffi v0.25.0.
54 | - Implement Display trait method for objects. Override ToString() C# object method.
55 |
56 | ----
57 |
58 | ### v0.5.1+v0.24.0
59 |
60 | - Bump to correct crate version.
61 |
62 | ### v0.5.0+v0.24.0
63 |
64 | - **BREAKING**: Update uniffi to 0.24.
65 | - **BREAKING**: Remove `package` configuration option, use `namespace` instead.
66 | - Implement `bytes` type.
67 | - Implement `--library` command line option.
68 | - Default config file to `uniffi.toml` in crate root, if no config file is specified in
69 | command line options.
70 |
71 | ----
72 |
73 | ### v0.4.1+v0.23.0
74 |
75 | - Bump to correct crate version.
76 |
77 | ### v0.4.0+v0.23.0
78 |
79 | - **BREAKING**: Emit flat enum variants using `PascalCase` instead of `SCREAMING_SNAKE_CASE`.
80 | - Lowercase numeric types in generated bindings code.
81 |
82 | ----
83 |
84 | ### v0.3.0, v0.3.1
85 |
86 | This is a version somewhere between 0.23.0 and 0.24.0. This was supposed to be a temporary stepping
87 | stone for the actual 0.24.0 version, but ended up never being actually used (at least by us). It
88 | is reverted in main branch. Use v0.2.0 instead.
89 |
90 | - **DO NOT USE, UNFINISHED**
91 |
92 | ----
93 |
94 | ### v0.2.4+v0.23.0
95 |
96 | - Fix missing imports when ImplicitUsings is not enabled.
97 | - Allow configuration of global methods class name (uniffi.toml: global_methods_class_name).
98 |
99 | ### v0.2.3+v0.23.0
100 |
101 | - Fix 0.2 release to be compatible with mozilla/uniffi-rs 0.23.0 after docstring changes.
102 |
103 | ### v0.2.2+v0.23.0
104 |
105 | - Implement docstrings.
106 |
107 | ### v0.2.1+v0.23.0
108 |
109 | - Add `namespace` configuration option to `uniffi.toml`.
110 |
111 | ### v0.2.0+v0.23.0
112 |
113 | - **BREAKING**: Update uniffi to 0.23.0.
114 | - Allow `uniffi-bindgen-cs` to be used as a library.
115 |
116 | ----
117 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributor Covenant Code of Conduct
3 |
4 | ## Our Pledge
5 |
6 | We as members, contributors, and leaders pledge to make participation in our
7 | community a harassment-free experience for everyone, regardless of age, body
8 | size, visible or invisible disability, ethnicity, sex characteristics, gender
9 | identity and expression, level of experience, education, socio-economic status,
10 | nationality, personal appearance, race, caste, color, religion, or sexual
11 | identity and orientation.
12 |
13 | We pledge to act and interact in ways that contribute to an open, welcoming,
14 | diverse, inclusive, and healthy community.
15 |
16 | ## Our Standards
17 |
18 | Examples of behavior that contributes to a positive environment for our
19 | community include:
20 |
21 | * Demonstrating empathy and kindness toward other people
22 | * Being respectful of differing opinions, viewpoints, and experiences
23 | * Giving and gracefully accepting constructive feedback
24 | * Accepting responsibility and apologizing to those affected by our mistakes,
25 | and learning from the experience
26 | * Focusing on what is best not just for us as individuals, but for the overall
27 | community
28 |
29 | Examples of unacceptable behavior include:
30 |
31 | * The use of sexualized language or imagery, and sexual attention or advances of
32 | any kind
33 | * Trolling, insulting or derogatory comments, and personal or political attacks
34 | * Public or private harassment
35 | * Publishing others' private information, such as a physical or email address,
36 | without their explicit permission
37 | * Other conduct which could reasonably be considered inappropriate in a
38 | professional setting
39 |
40 | ## Enforcement Responsibilities
41 |
42 | Community leaders are responsible for clarifying and enforcing our standards of
43 | acceptable behavior and will take appropriate and fair corrective action in
44 | response to any behavior that they deem inappropriate, threatening, offensive,
45 | or harmful.
46 |
47 | Community leaders have the right and responsibility to remove, edit, or reject
48 | comments, commits, code, wiki edits, issues, and other contributions that are
49 | not aligned to this Code of Conduct, and will communicate reasons for moderation
50 | decisions when appropriate.
51 |
52 | ## Scope
53 |
54 | This Code of Conduct applies within all community spaces, and also applies when
55 | an individual is officially representing the community in public spaces.
56 | Examples of representing our community include using an official e-mail address,
57 | posting via an official social media account, or acting as an appointed
58 | representative at an online or offline event.
59 |
60 | ## Enforcement
61 |
62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
63 | reported to the community leaders responsible for enforcement at
64 | .
65 | All complaints will be reviewed and investigated promptly and fairly.
66 |
67 | All community leaders are obligated to respect the privacy and security of the
68 | reporter of any incident.
69 |
70 | ## Enforcement Guidelines
71 |
72 | Community leaders will follow these Community Impact Guidelines in determining
73 | the consequences for any action they deem in violation of this Code of Conduct:
74 |
75 | ### 1. Correction
76 |
77 | **Community Impact**: Use of inappropriate language or other behavior deemed
78 | unprofessional or unwelcome in the community.
79 |
80 | **Consequence**: A private, written warning from community leaders, providing
81 | clarity around the nature of the violation and an explanation of why the
82 | behavior was inappropriate. A public apology may be requested.
83 |
84 | ### 2. Warning
85 |
86 | **Community Impact**: A violation through a single incident or series of
87 | actions.
88 |
89 | **Consequence**: A warning with consequences for continued behavior. No
90 | interaction with the people involved, including unsolicited interaction with
91 | those enforcing the Code of Conduct, for a specified period of time. This
92 | includes avoiding interactions in community spaces as well as external channels
93 | like social media. Violating these terms may lead to a temporary or permanent
94 | ban.
95 |
96 | ### 3. Temporary Ban
97 |
98 | **Community Impact**: A serious violation of community standards, including
99 | sustained inappropriate behavior.
100 |
101 | **Consequence**: A temporary ban from any sort of interaction or public
102 | communication with the community for a specified period of time. No public or
103 | private interaction with the people involved, including unsolicited interaction
104 | with those enforcing the Code of Conduct, is allowed during this period.
105 | Violating these terms may lead to a permanent ban.
106 |
107 | ### 4. Permanent Ban
108 |
109 | **Community Impact**: Demonstrating a pattern of violation of community
110 | standards, including sustained inappropriate behavior, harassment of an
111 | individual, or aggression toward or disparagement of classes of individuals.
112 |
113 | **Consequence**: A permanent ban from any sort of public interaction within the
114 | community.
115 |
116 | ## Attribution
117 |
118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119 | version 2.1, available at
120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
121 |
122 | Community Impact Guidelines were inspired by
123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
124 |
125 | For answers to common questions about this code of conduct, see the FAQ at
126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
127 | [https://www.contributor-covenant.org/translations][translations].
128 |
129 | [homepage]: https://www.contributor-covenant.org
130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
131 | [Mozilla CoC]: https://github.com/mozilla/diversity
132 | [FAQ]: https://www.contributor-covenant.org/faq
133 | [translations]: https://www.contributor-covenant.org/translations
134 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to submit changes
2 |
3 | Pull requests are welcome!
4 |
5 | # How to report issues/bugs?
6 |
7 | Create an issue on Github, we will try to get back to you ASAP.
8 |
9 | # Checkout the code
10 |
11 | ```
12 | git clone https://github.com/NordSecurity/uniffi-bindgen-cs.git
13 | cd uniffi-bindgen-cs
14 | git submodule update --init --recursive
15 | ```
16 |
17 |
18 | # Committing rules
19 |
20 | [DCO](https://probot.github.io/apps/dco/) sign off is enforced for all commits. Be sure to use the `-s` option to add a `Signed-off-by` line to your commit messages.
21 |
22 | ```
23 | git commit -s -m 'This is my commit message'
24 | ```
25 |
26 | # Run tests
27 |
28 | To run tests, `dotnet` installation is required. Unlike `uniffi-rs`, there is no integration with
29 | `cargo test`. Tests are written using `xunit`.
30 |
31 | - Build `uniffi-bindgen-cs` executable, and `libuniffi_fixtures.so` shared library.
32 | ```
33 | ./build.sh
34 | ```
35 |
36 | - Generate test bindings using `uniffi-bindgen-cs`, and run `dotnet test` command.
37 | ```
38 | ./test_bindings.sh
39 | ```
40 |
41 | # Run tests in Docker
42 |
43 | Running tests in Docker containers is easier, because manual `dotnet` installation is not required.
44 |
45 | ```
46 | ./docker_build.sh
47 | ./docker_test_bindings.sh
48 | ```
49 |
50 | # Hanging tests
51 |
52 | To print test case names when a test starts, update `diagnosticMessages` to `true` in
53 | [xunit.runner.json](dotnet-tests/UniffiCS.BindingTests/xunit.runner.json). Xunit, the test framework
54 | used by this project, does not provide functionality to print test case names when a test starts,
55 | making it difficult to debug hanging tests without using a debugger.
56 | [CustomTestFramework.cs](dotnet-tests/UniffiCS.BindingTests/CustomTestFramework.cs) plugs into Xunit
57 | to print test case names when the tests start.
58 |
59 | # Directory structure
60 |
61 | | Directory | Description |
62 | |------------------------------------------|--------------------------------------------------|
63 | | 3rd-party/uniffi-rs/ | fork of uniffi-rs, used for tests |
64 | | dotnet-tests/UniffiCS/gen/ | generated test bindings |
65 | | dotnet-tests/UniffiCS.binding_tests/ | C# tests for bindings |
66 | | fixtures/ | additional test fixtures specific to C# bindings |
67 | | src/gen_cs/ | generator CLI code |
68 | | templates/ | generator C# templates |
69 |
70 |
71 | # Thank you!
72 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "2"
3 |
4 | members = [
5 | "bindgen",
6 | "fixtures",
7 | ]
8 |
9 | [workspace.dependencies]
10 | uniffi = { version = "0.28.3", features = ["build"] }
11 | uniffi_testing = "0.28.3"
12 | uniffi_macros = "0.28.3"
13 | uniffi_build = { version = "0.28.3", features=["builtin-bindgen"] }
14 | uniffi_bindgen = "0.28.3"
15 | uniffi_meta = "0.28.3"
16 | uniffi_udl = "0.28.3"
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/sdk:6.0
2 |
3 | LABEL org.opencontainers.image.source=https://github.com/NordSecurity/uniffi-bindgen-cs
4 |
5 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain=1.72
6 |
7 | RUN apt-get update && apt-get install -y --no-install-recommends build-essential && apt-get clean
8 |
9 | RUN dotnet tool install -g csharpier
10 | RUN echo 'export PATH="$PATH:/root/.dotnet/tools"' >> /root/.bashrc
11 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: build generate test
2 |
3 | build:
4 | ./build.sh
5 |
6 | generate:
7 | ./generate_bindings.sh
8 |
9 | test:
10 | ./test_bindings.sh
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # uniffi-bindgen-cs - UniFFI C# bindings generator
2 |
3 | Generate [UniFFI](https://github.com/mozilla/uniffi-rs) bindings for C#. `uniffi-bindgen-cs` lives
4 | as a separate project from `uniffi-rs`, as per
5 | [uniffi-rs #1355](https://github.com/mozilla/uniffi-rs/issues/1355).
6 |
7 | # How to install
8 |
9 | Minimum Rust version required to install `uniffi-bindgen-cs` is `1.81`.
10 | Newer Rust versions should also work fine.
11 |
12 | ```bash
13 | cargo install uniffi-bindgen-cs --git https://github.com/NordSecurity/uniffi-bindgen-cs --tag v0.9.1+v0.28.3
14 | ```
15 |
16 | # How to generate bindings
17 |
18 | ```bash
19 | uniffi-bindgen-cs path/to/definitions.udl
20 | ```
21 | Generates bindings file `path/to/definitions.cs`
22 |
23 | # How to integrate bindings
24 |
25 | To integrate the bindings into your projects, simply add the generated bindings file to your project.
26 | There are a few requirements depending on your target framework version.
27 |
28 | - .NET core `6.0` or higher
29 | ```xml
30 |
31 | net6.0
32 | true
33 |
34 | ```
35 |
36 | - .NET framework `4.6.1`
37 | ```xml
38 |
39 | net461
40 | 10.0
41 | true
42 |
43 |
44 |
45 | ```
46 |
47 | # Unsupported features
48 |
49 | The following uniffi features are unsupported.
50 |
51 | - External types [#40](https://github.com/NordSecurity/uniffi-bindgen-cs/issues/40)
52 |
53 | # Known Limitations
54 |
55 | ### String/byte[]/lists size limit
56 |
57 | Currently size of strings/byte[]/lists is limited to `i32: 2^31`. Exceeding this limit will result in exceptions.
58 |
59 | # Configuration options
60 |
61 | It's possible to [configure some settings](docs/CONFIGURATION.md) by passing `--config`
62 | argument to the generator.
63 | ```bash
64 | uniffi-bindgen-cs path/to/definitions.udl --config path/to/uniffi.toml
65 | ```
66 |
67 | # Versioning
68 |
69 | `uniffi-bindgen-cs` is versioned separately from `uniffi-rs`. UniFFI follows the [SemVer rules from
70 | the Cargo Book](https://doc.rust-lang.org/cargo/reference/resolver.html#semver-compatibility)
71 | which states "Versions are considered compatible if their left-most non-zero
72 | major/minor/patch component is the same". A breaking change is any modification to the C# bindings
73 | that demands the consumer of the bindings to make corresponding changes to their code to ensure that
74 | the bindings continue to function properly. `uniffi-bindgen-cs` is young, and it's unclear how stable
75 | the generated bindings are going to be between versions. For this reason, major version is currently
76 | 0, and most changes are probably going to bump minor version.
77 |
78 | To ensure consistent feature set across external binding generators, `uniffi-bindgen-cs` targets
79 | a specific `uniffi-rs` version. A consumer using Go bindings (in `uniffi-bindgen-go`) and C#
80 | bindings (in `uniffi-bindgen-cs`) expects the same features to be available across multiple bindings
81 | generators. This means that the consumer should choose external binding generator versions such that
82 | each generator targets the same `uniffi-rs` version.
83 |
84 | To simplify this choice `uniffi-bindgen-cs` and `uniffi-bindgen-go` use tag naming convention
85 | as follows: `vX.Y.Z+vA.B.C`, where `X.Y.Z` is the version of the generator itself, and `A.B.C` is
86 | the version of uniffi-rs it is based on.
87 |
88 | The table shows `uniffi-rs` version history for tags that were published before tag naming convention described above was introduced.
89 |
90 | | uniffi-bindgen-cs version | uniffi-rs version |
91 | |------------------------------------------|--------------------------------------------------|
92 | | v0.9.0 | v0.28.3 |
93 | | v0.6.0 | v0.25.0 |
94 | | v0.5.0 | v0.24.0 |
95 | | ~~v0.3.0~~ (DONT USE, UNFINISHED) | ~~3142151e v0.24.0?~~ |
96 | | v0.2.0 | v0.23.0 |
97 | | v0.1.0 | v0.20.0 |
98 |
99 | # Documentation
100 |
101 | More documentation is available in [docs](docs) directory.
102 |
103 | # Contributing
104 |
105 | For contribution guidelines, read [CONTRIBUTING.md](CONTRIBUTING.md).
106 |
--------------------------------------------------------------------------------
/bindgen/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "uniffi-bindgen-cs"
3 | version = "0.9.0+v0.28.3"
4 | edition = "2021"
5 |
6 | [lib]
7 | name = "uniffi_bindgen_cs"
8 | path = "src/lib.rs"
9 |
10 | [[bin]]
11 | name = "uniffi-bindgen-cs"
12 | path = "src/main.rs"
13 |
14 | [dependencies]
15 | anyhow = "1"
16 | askama = { version = "0.13", default-features = false, features = ["config", "derive", "alloc"] }
17 | camino = "1.0.8"
18 | cargo_metadata = "0.15"
19 | clap = { version = "3.1", features = ["cargo", "std", "derive"] }
20 | extend = "1.1"
21 | fs-err = "2.7.0"
22 | heck = "0.4"
23 | paste = "1.0"
24 | serde = "1"
25 | serde_json = "1.0.0"
26 | textwrap = "0.16"
27 | toml = "0.5"
28 |
29 | uniffi_bindgen.workspace = true
30 | uniffi_meta.workspace = true
31 | uniffi_udl.workspace = true
--------------------------------------------------------------------------------
/bindgen/askama.toml:
--------------------------------------------------------------------------------
1 | [[syntax]]
2 | name = "cs"
3 |
--------------------------------------------------------------------------------
/bindgen/src/gen_cs/callback_interface.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | use super::CodeType;
6 | use uniffi_bindgen::{backend::Literal, ComponentInterface};
7 |
8 | #[derive(Debug)]
9 | pub struct CallbackInterfaceCodeType {
10 | id: String,
11 | }
12 |
13 | impl CallbackInterfaceCodeType {
14 | pub fn new(id: String) -> Self {
15 | Self { id }
16 | }
17 | }
18 |
19 | impl CodeType for CallbackInterfaceCodeType {
20 | fn type_label(&self, ci: &ComponentInterface) -> String {
21 | super::CsCodeOracle.class_name(&self.id, ci)
22 | }
23 |
24 | fn canonical_name(&self) -> String {
25 | format!("Type{}", self.id)
26 | }
27 |
28 | fn literal(&self, _literal: &Literal, _ci: &ComponentInterface) -> String {
29 | unreachable!();
30 | }
31 |
32 | fn initialization_fn(&self) -> Option {
33 | super::filters::ffi_callback_registration(&self.id).ok()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/bindgen/src/gen_cs/compounds.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | use super::CodeType;
6 | use paste::paste;
7 | use uniffi_bindgen::{
8 | backend::{Literal, Type},
9 | ComponentInterface,
10 | };
11 |
12 | fn render_literal(literal: &Literal, inner: &Type, ci: &ComponentInterface) -> String {
13 | match literal {
14 | Literal::None => "null".into(),
15 | Literal::Some { inner: meta } => super::CsCodeOracle.find(inner).literal(meta, ci),
16 |
17 | // details/1-empty-list-as-default-method-parameter.md
18 | Literal::EmptySequence => "null".into(),
19 | Literal::EmptyMap => "null".into(),
20 |
21 | // For optionals
22 | _ => super::CsCodeOracle.find(inner).literal(literal, ci),
23 | }
24 | }
25 |
26 | macro_rules! impl_code_type_for_compound {
27 | ($T:ty, $type_label_pattern:literal, $canonical_name_pattern: literal) => {
28 | paste! {
29 | #[derive(Debug)]
30 | pub struct $T {
31 | inner: Type,
32 | }
33 |
34 | impl $T {
35 | pub fn new(inner: Type) -> Self {
36 | Self { inner }
37 | }
38 | fn inner(&self) -> &Type {
39 | &self.inner
40 | }
41 | }
42 |
43 | impl CodeType for $T {
44 | fn type_label(&self, ci: &ComponentInterface) -> String {
45 | format!($type_label_pattern, super::CsCodeOracle.find(self.inner()).type_label(ci))
46 | }
47 |
48 | fn canonical_name(&self) -> String {
49 | format!($canonical_name_pattern, super::CsCodeOracle.find(self.inner()).canonical_name())
50 | }
51 |
52 | fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
53 | render_literal(literal, self.inner(), ci)
54 | }
55 | }
56 | }
57 | }
58 | }
59 |
60 | impl_code_type_for_compound!(OptionalCodeType, "{}?", "Optional{}");
61 | impl_code_type_for_compound!(SequenceCodeType, "List<{}>", "Sequence{}");
62 |
63 | #[derive(Debug)]
64 | pub struct MapCodeType {
65 | key: Type,
66 | value: Type,
67 | }
68 |
69 | impl MapCodeType {
70 | pub fn new(key: Type, value: Type) -> Self {
71 | Self { key, value }
72 | }
73 |
74 | fn key(&self) -> &Type {
75 | &self.key
76 | }
77 |
78 | fn value(&self) -> &Type {
79 | &self.value
80 | }
81 | }
82 |
83 | impl CodeType for MapCodeType {
84 | fn type_label(&self, ci: &ComponentInterface) -> String {
85 | format!(
86 | "Dictionary<{}, {}>",
87 | super::CsCodeOracle.find(self.key()).type_label(ci),
88 | super::CsCodeOracle.find(self.value()).type_label(ci),
89 | )
90 | }
91 |
92 | fn canonical_name(&self) -> String {
93 | format!(
94 | "Dictionary{}{}",
95 | super::CsCodeOracle.find(self.key()).canonical_name(),
96 | super::CsCodeOracle.find(self.value()).canonical_name(),
97 | )
98 | }
99 |
100 | fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
101 | render_literal(literal, &self.value, ci)
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/bindgen/src/gen_cs/custom.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | use super::CodeType;
6 | use uniffi_bindgen::{backend::Literal, ComponentInterface};
7 |
8 | #[derive(Debug)]
9 | pub struct CustomCodeType {
10 | name: String,
11 | }
12 |
13 | impl CustomCodeType {
14 | pub fn new(name: String) -> Self {
15 | CustomCodeType { name }
16 | }
17 | }
18 |
19 | impl CodeType for CustomCodeType {
20 | fn type_label(&self, _ci: &ComponentInterface) -> String {
21 | self.name.clone()
22 | }
23 |
24 | fn canonical_name(&self) -> String {
25 | format!("Type{}", self.name)
26 | }
27 |
28 | fn literal(&self, _literal: &Literal, _ci: &ComponentInterface) -> String {
29 | unreachable!("Can't have a literal of a custom type");
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/bindgen/src/gen_cs/enum_.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | use super::CodeType;
6 | use uniffi_bindgen::{backend::Literal, ComponentInterface};
7 |
8 | #[derive(Debug)]
9 | pub struct EnumCodeType {
10 | id: String,
11 | }
12 |
13 | impl EnumCodeType {
14 | pub fn new(id: String) -> Self {
15 | Self { id }
16 | }
17 | }
18 |
19 | impl CodeType for EnumCodeType {
20 | fn type_label(&self, ci: &ComponentInterface) -> String {
21 | super::CsCodeOracle.class_name(&self.id, ci)
22 | }
23 |
24 | fn canonical_name(&self) -> String {
25 | format!("Type{}", self.id)
26 | }
27 |
28 | fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
29 | if let Literal::Enum(v, _) = literal {
30 | format!(
31 | "{}.{}",
32 | self.type_label(ci),
33 | super::CsCodeOracle.enum_variant_name(v)
34 | )
35 | } else {
36 | unreachable!();
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/bindgen/src/gen_cs/external.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | use super::CodeType;
6 | use uniffi_bindgen::{backend::Literal, ComponentInterface};
7 |
8 | #[derive(Debug)]
9 | pub struct ExternalCodeType {
10 | name: String,
11 | }
12 |
13 | impl ExternalCodeType {
14 | pub fn new(name: String) -> Self {
15 | Self { name }
16 | }
17 | }
18 |
19 | impl CodeType for ExternalCodeType {
20 | fn type_label(&self, _ci: &ComponentInterface) -> String {
21 | self.name.clone()
22 | }
23 |
24 | fn canonical_name(&self) -> String {
25 | format!("Type{}", self.name)
26 | }
27 |
28 | fn literal(&self, _literal: &Literal, _ci: &ComponentInterface) -> String {
29 | unreachable!("Can't have a literal of an external type");
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/bindgen/src/gen_cs/formatting.rs:
--------------------------------------------------------------------------------
1 | use std::io::Write;
2 | use std::process::{Command, Stdio};
3 |
4 | pub fn format(bindings: String) -> Result {
5 | let mut csharpier = Command::new("dotnet-csharpier")
6 | .stdin(Stdio::piped())
7 | .stdout(Stdio::piped())
8 | .spawn()?;
9 |
10 | csharpier
11 | .stdin
12 | .take()
13 | .ok_or(anyhow::anyhow!("Failed to open stdin"))?
14 | .write_all(bindings.as_bytes())?;
15 |
16 | let output = csharpier.wait_with_output()?;
17 | if !output.status.success() {
18 | Err(std::io::Error::new(
19 | std::io::ErrorKind::Other,
20 | format!(
21 | "command returned non-zero exit status: {:?}",
22 | output.status.code()
23 | ),
24 | ))?;
25 | }
26 |
27 | let formatted = String::from_utf8(output.stdout)?;
28 |
29 | Ok(formatted)
30 | }
31 |
32 | pub fn add_header(bindings: String) -> String {
33 | let version = env!("CARGO_PKG_VERSION");
34 | let header = format!(
35 | r##"//
36 | // This file was generated by uniffi-bindgen-cs v{version}
37 | // See https://github.com/NordSecurity/uniffi-bindgen-cs for more information.
38 | //
39 |
40 | #nullable enable
41 |
42 | "##
43 | );
44 |
45 | header + &bindings
46 | }
47 |
--------------------------------------------------------------------------------
/bindgen/src/gen_cs/miscellany.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | use super::CodeType;
6 | use paste::paste;
7 | use uniffi_bindgen::{backend::Literal, ComponentInterface};
8 |
9 | macro_rules! impl_code_type_for_miscellany {
10 | ($T:ty, $class_name:literal, $canonical_name:literal) => {
11 | paste! {
12 | #[derive(Debug)]
13 | pub struct $T;
14 |
15 | impl CodeType for $T {
16 | fn type_label(&self, _ci: &ComponentInterface) -> String {
17 | $class_name.into()
18 | }
19 |
20 | fn canonical_name(&self) -> String {
21 | $canonical_name.into()
22 | }
23 |
24 | fn literal(&self, _literal: &Literal, _ci: &ComponentInterface) -> String {
25 | unreachable!()
26 | }
27 | }
28 | }
29 | };
30 | }
31 |
32 | impl_code_type_for_miscellany!(TimestampCodeType, "DateTime", "Timestamp");
33 |
34 | impl_code_type_for_miscellany!(DurationCodeType, "TimeSpan", "Duration");
35 |
--------------------------------------------------------------------------------
/bindgen/src/gen_cs/object.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | use super::CodeType;
6 | use uniffi_bindgen::{backend::Literal, interface::ObjectImpl, ComponentInterface};
7 |
8 | #[derive(Debug)]
9 | pub struct ObjectCodeType {
10 | id: String,
11 | imp: ObjectImpl,
12 | }
13 |
14 | impl ObjectCodeType {
15 | pub fn new(id: String, imp: ObjectImpl) -> Self {
16 | Self { id, imp }
17 | }
18 | }
19 |
20 | impl CodeType for ObjectCodeType {
21 | fn type_label(&self, ci: &ComponentInterface) -> String {
22 | super::CsCodeOracle.class_name(&self.id, ci)
23 | }
24 |
25 | fn canonical_name(&self) -> String {
26 | format!("Type{}", self.id)
27 | }
28 |
29 | fn error_converter_name(&self) -> String {
30 | format!("{}ErrorHandler", self.ffi_converter_name())
31 | }
32 |
33 | fn literal(&self, _literal: &Literal, _ci: &ComponentInterface) -> String {
34 | unreachable!();
35 | }
36 |
37 | fn initialization_fn(&self) -> Option {
38 | self.imp
39 | .has_callback_interface()
40 | .then(|| format!("UniffiCallbackInterface{}.Register", self.id))
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/bindgen/src/gen_cs/primitives.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | use super::CodeType;
6 | use paste::paste;
7 | use uniffi_bindgen::interface::{Radix, Type};
8 | use uniffi_bindgen::{backend::Literal, ComponentInterface};
9 |
10 | fn render_literal(literal: &Literal) -> String {
11 | fn typed_number(type_: &Type, num_str: String) -> String {
12 | let unwrapped_type = match type_ {
13 | Type::Optional { inner_type } => inner_type,
14 | t => t,
15 | };
16 | match unwrapped_type {
17 | // Bytes, Shorts and Ints can all be inferred from the type.
18 | Type::Int8 | Type::UInt8 | Type::Int16 | Type::UInt16 | Type::Int32 => num_str,
19 | Type::Int64 => format!("{num_str}L"),
20 |
21 | Type::UInt32 => format!("{num_str}u"),
22 | Type::UInt64 => format!("{num_str}uL"),
23 |
24 | Type::Float32 => format!("{num_str}f"),
25 | Type::Float64 => num_str,
26 | _ => panic!("Unexpected literal: {num_str} for type: {type_:?}"),
27 | }
28 | }
29 |
30 | match literal {
31 | Literal::Boolean(v) => format!("{v}"),
32 | Literal::String(s) => format!("\"{s}\""),
33 | Literal::Int(i, radix, type_) => typed_number(
34 | type_,
35 | match radix {
36 | Radix::Octal => format!("{i:#x}"),
37 | Radix::Decimal => format!("{i}"),
38 | Radix::Hexadecimal => format!("{i:#x}"),
39 | },
40 | ),
41 | Literal::UInt(i, radix, type_) => typed_number(
42 | type_,
43 | match radix {
44 | Radix::Octal => format!("{i:#x}"),
45 | Radix::Decimal => format!("{i}"),
46 | Radix::Hexadecimal => format!("{i:#x}"),
47 | },
48 | ),
49 | Literal::Float(string, type_) => typed_number(type_, string.clone()),
50 |
51 | _ => unreachable!("Literal: {:?}", literal),
52 | }
53 | }
54 |
55 | macro_rules! impl_code_type_for_primitive {
56 | ($T:ty, $type_label:literal, $canonical_name:literal) => {
57 | paste! {
58 | #[derive(Debug)]
59 | pub struct $T;
60 |
61 | impl CodeType for $T {
62 | fn type_label(&self, _ci: &ComponentInterface) -> String {
63 | $type_label.into()
64 | }
65 |
66 | fn canonical_name(&self) -> String {
67 | $canonical_name.into()
68 | }
69 |
70 | fn literal(&self, literal: &Literal, _ci: &ComponentInterface) -> String {
71 | render_literal(&literal)
72 | }
73 | }
74 | }
75 | };
76 | }
77 |
78 | impl_code_type_for_primitive!(BooleanCodeType, "bool", "Boolean");
79 | impl_code_type_for_primitive!(StringCodeType, "string", "String");
80 | impl_code_type_for_primitive!(Int8CodeType, "sbyte", "Int8");
81 | impl_code_type_for_primitive!(Int16CodeType, "short", "Int16");
82 | impl_code_type_for_primitive!(Int32CodeType, "int", "Int32");
83 | impl_code_type_for_primitive!(Int64CodeType, "long", "Int64");
84 | impl_code_type_for_primitive!(UInt8CodeType, "byte", "UInt8");
85 | impl_code_type_for_primitive!(UInt16CodeType, "ushort", "UInt16");
86 | impl_code_type_for_primitive!(UInt32CodeType, "uint", "UInt32");
87 | impl_code_type_for_primitive!(UInt64CodeType, "ulong", "UInt64");
88 | impl_code_type_for_primitive!(Float32CodeType, "float", "Float");
89 | impl_code_type_for_primitive!(Float64CodeType, "double", "Double");
90 | impl_code_type_for_primitive!(BytesCodeType, "byte[]", "ByteArray");
91 |
--------------------------------------------------------------------------------
/bindgen/src/gen_cs/record.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | use super::CodeType;
6 | use uniffi_bindgen::{backend::Literal, ComponentInterface};
7 |
8 | #[derive(Debug)]
9 | pub struct RecordCodeType {
10 | id: String,
11 | }
12 |
13 | impl RecordCodeType {
14 | pub fn new(id: String) -> Self {
15 | Self { id }
16 | }
17 | }
18 |
19 | impl CodeType for RecordCodeType {
20 | fn type_label(&self, ci: &ComponentInterface) -> String {
21 | super::CsCodeOracle.class_name(&self.id, ci)
22 | }
23 |
24 | fn canonical_name(&self) -> String {
25 | format!("Type{}", self.id)
26 | }
27 |
28 | fn literal(&self, _literal: &Literal, _ci: &ComponentInterface) -> String {
29 | unreachable!();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/bindgen/src/lib.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | pub mod gen_cs;
6 |
7 | use anyhow::Result;
8 | use camino::Utf8PathBuf;
9 | use clap::Parser;
10 | use fs_err::File;
11 | pub use gen_cs::generate_bindings;
12 | use serde::{Deserialize, Serialize};
13 | use std::io::Write;
14 | use uniffi_bindgen::{Component, GenerationSettings};
15 |
16 | #[derive(Parser)]
17 | #[clap(name = "uniffi-bindgen")]
18 | #[clap(version = clap::crate_version!())]
19 | #[clap(propagate_version = true)]
20 | struct Cli {
21 | /// Directory in which to write generated files. Default is same folder as .udl file.
22 | #[clap(long, short)]
23 | out_dir: Option,
24 |
25 | /// Path to the optional uniffi config file. If not provided, uniffi-bindgen will try to guess it from the UDL's file location.
26 | #[clap(long, short)]
27 | config: Option,
28 |
29 | /// Extract proc-macro metadata from cdylib for this crate.
30 | #[clap(long)]
31 | lib_file: Option,
32 |
33 | /// Pass in a cdylib path rather than a UDL file
34 | #[clap(long = "library", conflicts_with_all = &["config", "lib-file"], requires = "out-dir")]
35 | library_mode: bool,
36 |
37 | /// When `--library` is passed, only generate bindings for one crate
38 | #[clap(long = "crate", requires = "library-mode")]
39 | crate_name: Option,
40 |
41 | /// Path to the UDL file, or cdylib if `library-mode` is specified
42 | source: Utf8PathBuf,
43 |
44 | /// Do not try to format the generated bindings.
45 | #[clap(long, short)]
46 | no_format: bool,
47 | }
48 |
49 | #[derive(Debug, Clone, Default, Serialize, Deserialize)]
50 | pub struct ConfigRoot {
51 | #[serde(default)]
52 | bindings: ConfigBindings,
53 | }
54 |
55 | #[derive(Debug, Clone, Default, Serialize, Deserialize)]
56 | pub struct ConfigBindings {
57 | #[serde(default)]
58 | csharp: gen_cs::Config,
59 | }
60 |
61 | struct BindingGenerator {
62 | try_format_code: bool,
63 | }
64 |
65 | impl uniffi_bindgen::BindingGenerator for BindingGenerator {
66 | type Config = gen_cs::Config;
67 |
68 | fn new_config(&self, root_toml: &toml::Value) -> Result {
69 | Ok(match root_toml.get("bindings").and_then(|b| b.get("csharp")) {
70 | Some(v) => v.clone().try_into()?,
71 | None => Default::default(),
72 | })
73 | }
74 |
75 | fn write_bindings(
76 | &self,
77 | settings: &GenerationSettings,
78 | components: &[Component],
79 | ) -> anyhow::Result<()> {
80 | for Component { ci, config, .. } in components {
81 | let bindings_file = settings.out_dir.join(format!("{}.cs", ci.namespace()));
82 | println!("Writing bindings file {}", bindings_file);
83 | let mut f = File::create(&bindings_file)?;
84 |
85 | let mut bindings = generate_bindings(config, ci)?;
86 |
87 | if self.try_format_code {
88 | match gen_cs::formatting::format(bindings.clone()) {
89 | Ok(formatted) => bindings = formatted,
90 | Err(e) => {
91 | println!(
92 | "Warning: Unable to auto-format {} using CSharpier (hint: 'dotnet tool install -g csharpier'): {e:?}",
93 | bindings_file.file_name().unwrap(),
94 | );
95 | }
96 | }
97 | }
98 |
99 | bindings = gen_cs::formatting::add_header(bindings);
100 | write!(f, "{}", bindings)?;
101 | }
102 | Ok(())
103 | }
104 |
105 | fn update_component_configs(
106 | &self,
107 | settings: &GenerationSettings,
108 | components: &mut Vec>,
109 | ) -> Result<()> {
110 | for c in &mut *components {
111 | c.config
112 | .namespace
113 | .get_or_insert_with(|| format!("uniffi.{}", c.ci.namespace()));
114 |
115 | c.config.cdylib_name.get_or_insert_with(|| {
116 | settings.cdylib.clone().unwrap_or_else(|| format!("uniffi_{}", c.ci.namespace()))
117 | });
118 | }
119 | // TODO: external types are not supported
120 | // let packages = HashMap::::from_iter(
121 | // components
122 | // .iter()
123 | // .map(|c| (c.ci.crate_name().to_string(), c.config.package_name())),
124 | // );
125 | // for c in components {
126 | // for (ext_crate, ext_package) in &packages {
127 | // if ext_crate != c.ci.crate_name()
128 | // && !c.config.external_packages.contains_key(ext_crate)
129 | // {
130 | // c.config
131 | // .external_packages
132 | // .insert(ext_crate.to_string(), ext_package.clone());
133 | // }
134 | // }
135 | // }
136 | Ok(())
137 | }
138 | }
139 |
140 | pub fn main() -> Result<()> {
141 | let cli = Cli::parse();
142 |
143 | if cli.library_mode {
144 | let out_dir = cli
145 | .out_dir
146 | .expect("--out-dir is required when using --library");
147 |
148 | let config_supplier = {
149 | use uniffi_bindgen::cargo_metadata::CrateConfigSupplier;
150 | let cmd = ::cargo_metadata::MetadataCommand::new();
151 | let metadata = cmd.exec().unwrap();
152 | CrateConfigSupplier::from(metadata)
153 | };
154 |
155 | uniffi_bindgen::library_mode::generate_bindings(
156 | &cli.source,
157 | cli.crate_name,
158 | &BindingGenerator {
159 | try_format_code: !cli.no_format,
160 | },
161 | &config_supplier,
162 | cli.config.as_deref(),
163 | &out_dir,
164 | !cli.no_format,
165 | )
166 | .map(|_| ())
167 | } else {
168 | uniffi_bindgen::generate_external_bindings(
169 | &BindingGenerator {
170 | try_format_code: !cli.no_format,
171 | },
172 | &cli.source,
173 | cli.config.as_deref(),
174 | cli.out_dir.as_deref(),
175 | cli.lib_file.as_deref(),
176 | cli.crate_name.as_deref(),
177 | !cli.no_format,
178 | )
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/bindgen/src/main.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | pub fn main() -> anyhow::Result<()> {
6 | uniffi_bindgen_cs::main()
7 | }
8 |
--------------------------------------------------------------------------------
/bindgen/templates/Async.cs:
--------------------------------------------------------------------------------
1 | {{ self.add_import("System.Threading")}}
2 | {{ self.add_import("System.Threading.Tasks")}}
3 |
4 | {% if self.include_once_check("ConcurrentHandleMap.cs") %}{% include "ConcurrentHandleMap.cs" %}{% endif %}
5 |
6 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
7 | delegate void UniFfiFutureCallback(IntPtr continuationHandle, byte pollResult);
8 |
9 | internal static class _UniFFIAsync {
10 | internal const byte UNIFFI_RUST_FUTURE_POLL_READY = 0;
11 | // internal const byte UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1;
12 |
13 | internal static ConcurrentHandleMap> _async_handle_map = new ConcurrentHandleMap>();
14 | public static ConcurrentHandleMap _foreign_futures_map = new ConcurrentHandleMap();
15 |
16 | // FFI type for Rust future continuations
17 | internal class UniffiRustFutureContinuationCallback
18 | {
19 | public static UniFfiFutureCallback callback = Callback;
20 |
21 | public static void Callback(IntPtr continuationHandle, byte pollResult)
22 | {
23 | if (_async_handle_map.Remove((ulong)continuationHandle.ToInt64(), out TaskCompletionSource task))
24 | {
25 | task.SetResult(pollResult);
26 | }
27 | else
28 | {
29 | throw new InternalException($"Unable to find continuation handle: {continuationHandle}");
30 | }
31 | }
32 | }
33 |
34 | public class UniffiForeignFutureFreeCallback
35 | {
36 | public static _UniFFILib.UniffiForeignFutureFree callback = Callback;
37 |
38 | public static void Callback(ulong handle)
39 | {
40 | if (_foreign_futures_map.Remove(handle, out CancellationTokenSource task))
41 | {
42 | task.Cancel();
43 | }
44 | else
45 | {
46 | throw new InternalException($"Unable to find cancellation token: {handle}");
47 | }
48 | }
49 | }
50 |
51 | public delegate F CompleteFuncDelegate(IntPtr ptr, ref UniffiRustCallStatus status);
52 |
53 | public delegate void CompleteActionDelegate(IntPtr ptr, ref UniffiRustCallStatus status);
54 |
55 | private static async Task PollFuture(IntPtr rustFuture, Action pollFunc)
56 | {
57 | byte pollResult;
58 | do
59 | {
60 | var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
61 | IntPtr callback = Marshal.GetFunctionPointerForDelegate(UniffiRustFutureContinuationCallback.callback);
62 | ulong mapEntry = _async_handle_map.Insert(tcs);
63 | pollFunc(rustFuture, callback, (IntPtr)mapEntry);
64 | pollResult = await tcs.Task;
65 | }
66 | while(pollResult != UNIFFI_RUST_FUTURE_POLL_READY);
67 | }
68 |
69 | public static async Task UniffiRustCallAsync(
70 | IntPtr rustFuture,
71 | Action pollFunc,
72 | CompleteFuncDelegate completeFunc,
73 | Action freeFunc,
74 | Func liftFunc,
75 | CallStatusErrorHandler errorHandler
76 | ) where E : UniffiException
77 | {
78 | try {
79 | await PollFuture(rustFuture, pollFunc);
80 | var result = _UniffiHelpers.RustCallWithError(errorHandler, (ref UniffiRustCallStatus status) => completeFunc(rustFuture, ref status));
81 | return liftFunc(result);
82 | }
83 | finally
84 | {
85 | freeFunc(rustFuture);
86 | }
87 | }
88 |
89 | public static async Task UniffiRustCallAsync(
90 | IntPtr rustFuture,
91 | Action pollFunc,
92 | CompleteActionDelegate completeFunc,
93 | Action freeFunc,
94 | CallStatusErrorHandler errorHandler
95 | ) where E : UniffiException
96 | {
97 | try {
98 | await PollFuture(rustFuture, pollFunc);
99 | _UniffiHelpers.RustCallWithError(errorHandler, (ref UniffiRustCallStatus status) => completeFunc(rustFuture, ref status));
100 |
101 | }
102 | finally
103 | {
104 | freeFunc(rustFuture);
105 | }
106 | }
107 | }
--------------------------------------------------------------------------------
/bindgen/templates/BigEndianStream.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | // Big endian streams are not yet available in dotnet :'(
6 | // https://github.com/dotnet/runtime/issues/26904
7 |
8 | class StreamUnderflowException: System.Exception {
9 | public StreamUnderflowException() {
10 | }
11 | }
12 |
13 | class BigEndianStream {
14 | Stream stream;
15 | public BigEndianStream(Stream stream) {
16 | this.stream = stream;
17 | }
18 |
19 | public bool HasRemaining() {
20 | return (stream.Length - stream.Position) > 0;
21 | }
22 |
23 | public long Position {
24 | get => stream.Position;
25 | set => stream.Position = value;
26 | }
27 |
28 | public void WriteBytes(byte[] value) {
29 | stream.Write(value, 0, value.Length);
30 | }
31 |
32 | public void WriteByte(byte value) {
33 | stream.WriteByte(value);
34 | }
35 |
36 | public void WriteUShort(ushort value) {
37 | stream.WriteByte((byte)(value >> 8));
38 | stream.WriteByte((byte)value);
39 | }
40 |
41 | public void WriteUInt(uint value) {
42 | stream.WriteByte((byte)(value >> 24));
43 | stream.WriteByte((byte)(value >> 16));
44 | stream.WriteByte((byte)(value >> 8));
45 | stream.WriteByte((byte)value);
46 | }
47 |
48 | public void WriteULong(ulong value) {
49 | WriteUInt((uint)(value >> 32));
50 | WriteUInt((uint)value);
51 | }
52 |
53 | public void WriteSByte(sbyte value) {
54 | stream.WriteByte((byte)value);
55 | }
56 |
57 | public void WriteShort(short value) {
58 | WriteUShort((ushort)value);
59 | }
60 |
61 | public void WriteInt(int value) {
62 | WriteUInt((uint)value);
63 | }
64 |
65 | public void WriteFloat(float value) {
66 | unsafe {
67 | WriteInt(*((int*)&value));
68 | }
69 | }
70 |
71 | public void WriteLong(long value) {
72 | WriteULong((ulong)value);
73 | }
74 |
75 | public void WriteDouble(double value) {
76 | WriteLong(BitConverter.DoubleToInt64Bits(value));
77 | }
78 |
79 | public byte[] ReadBytes(int length) {
80 | CheckRemaining(length);
81 | byte[] result = new byte[length];
82 | stream.Read(result, 0, length);
83 | return result;
84 | }
85 |
86 | public byte ReadByte() {
87 | CheckRemaining(1);
88 | return Convert.ToByte(stream.ReadByte());
89 | }
90 |
91 | public ushort ReadUShort() {
92 | CheckRemaining(2);
93 | return (ushort)(stream.ReadByte() << 8 | stream.ReadByte());
94 | }
95 |
96 | public uint ReadUInt() {
97 | CheckRemaining(4);
98 | return (uint)(stream.ReadByte() << 24
99 | | stream.ReadByte() << 16
100 | | stream.ReadByte() << 8
101 | | stream.ReadByte());
102 | }
103 |
104 | public ulong ReadULong() {
105 | return (ulong)ReadUInt() << 32 | (ulong)ReadUInt();
106 | }
107 |
108 | public sbyte ReadSByte() {
109 | return (sbyte)ReadByte();
110 | }
111 |
112 | public short ReadShort() {
113 | return (short)ReadUShort();
114 | }
115 |
116 | public int ReadInt() {
117 | return (int)ReadUInt();
118 | }
119 |
120 | public float ReadFloat() {
121 | unsafe {
122 | int value = ReadInt();
123 | return *((float*)&value);
124 | }
125 | }
126 |
127 | public long ReadLong() {
128 | return (long)ReadULong();
129 | }
130 |
131 | public double ReadDouble() {
132 | return BitConverter.Int64BitsToDouble(ReadLong());
133 | }
134 |
135 | private void CheckRemaining(int length) {
136 | if (stream.Length - stream.Position < length) {
137 | throw new StreamUnderflowException();
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/bindgen/templates/BooleanHelper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class FfiConverterBoolean: FfiConverter {
6 | public static FfiConverterBoolean INSTANCE = new FfiConverterBoolean();
7 |
8 | public override bool Lift(sbyte value) {
9 | return value != 0;
10 | }
11 |
12 | public override bool Read(BigEndianStream stream) {
13 | return Lift(stream.ReadSByte());
14 | }
15 |
16 | public override sbyte Lower(bool value) {
17 | return value ? (sbyte)1 : (sbyte)0;
18 | }
19 |
20 | public override int AllocationSize(bool value) {
21 | return (sbyte)1;
22 | }
23 |
24 | public override void Write(bool value, BigEndianStream stream) {
25 | stream.WriteSByte(Lower(value));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/bindgen/templates/BytesTemplate.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
6 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
7 |
8 | public override {{ type_name }} Read(BigEndianStream stream) {
9 | var length = stream.ReadInt();
10 | return stream.ReadBytes(length);
11 | }
12 |
13 | public override int AllocationSize({{ type_name }} value) {
14 | return 4 + value.Length;
15 | }
16 |
17 | public override void Write({{ type_name }} value, BigEndianStream stream) {
18 | stream.WriteInt(value.Length);
19 | stream.WriteBytes(value);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/bindgen/templates/CallbackInterfaceTemplate.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | {%- let cbi = ci.get_callback_interface_definition(name).unwrap() %}
6 | {%- let type_name = cbi|type_name(ci) %}
7 | {%- let callback_impl_name = type_name|ffi_callback_impl %}
8 |
9 | {%- let vtable = cbi.vtable() %}
10 | {%- let vtable_methods = cbi.vtable_methods() %}
11 |
12 | {%- let ffi_converter_var = format!("{}.INSTANCE", ffi_converter_name) %}
13 | {%- let ffi_init_callback = cbi.ffi_init_callback() %}
14 |
15 | {%- call cs::docstring(cbi, 0) %}
16 | {{ config.access_modifier() }} interface {{ type_name }} {
17 | {%- for meth in cbi.methods() %}
18 | {%- call cs::docstring(meth, 4) %}
19 | {%- call cs::method_throws_annotation(meth.throws_type()) %}
20 | {%- match meth.return_type() %}
21 | {%- when Some with (return_type) %}
22 | {{ return_type|type_name(ci) }} {{ meth.name()|fn_name }}({% call cs::arg_list_decl(meth) %});
23 | {%- else %}
24 | void {{ meth.name()|fn_name }}({% call cs::arg_list_decl(meth) %});
25 | {%- endmatch %}
26 | {%- endfor %}
27 | }
28 |
29 | {% include "CallbackInterfaceImpl.cs" %}
30 |
31 | // The ffiConverter which transforms the Callbacks in to Handles to pass to Rust.
32 | class {{ ffi_converter_name }}: FfiConverter<{{ type_name }}, ulong> {
33 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
34 |
35 | public ConcurrentHandleMap<{{ type_name }}> handleMap = new ConcurrentHandleMap<{{ type_name }}>();
36 |
37 | public override ulong Lower({{ type_name }} value) {
38 | return handleMap.Insert(value);
39 | }
40 |
41 | public override {{ type_name }} Lift(ulong value) {
42 | if (handleMap.TryGet(value, out var uniffiCallback)) {
43 | return uniffiCallback;
44 | } else {
45 | throw new InternalException($"No callback in handlemap '{value}'");
46 | }
47 | }
48 |
49 | public override {{ type_name }} Read(BigEndianStream stream) {
50 | return Lift(stream.ReadULong());
51 | }
52 |
53 | public override int AllocationSize({{ type_name }} value) {
54 | return 8;
55 | }
56 |
57 | public override void Write({{ type_name }} value, BigEndianStream stream) {
58 | stream.WriteULong(Lower(value));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/bindgen/templates/CallbackResponseStatus.cs:
--------------------------------------------------------------------------------
1 | static class UniffiCallbackResponseStatus {
2 | public static sbyte SUCCESS = 0;
3 | public static sbyte ERROR = 1;
4 | public static sbyte UNEXPECTED_ERROR = 2;
5 | }
--------------------------------------------------------------------------------
/bindgen/templates/ConcurrentHandleMap.cs:
--------------------------------------------------------------------------------
1 | class ConcurrentHandleMap where T: notnull {
2 | Dictionary map = new Dictionary();
3 |
4 | Object lock_ = new Object();
5 | ulong currentHandle = 0;
6 |
7 | public ulong Insert(T obj) {
8 | lock (lock_) {
9 | currentHandle += 1;
10 | map[currentHandle] = obj;
11 | return currentHandle;
12 | }
13 | }
14 |
15 | public bool TryGet(ulong handle, out T result) {
16 | lock (lock_) {
17 | #pragma warning disable 8601 // Possible null reference assignment
18 | return map.TryGetValue(handle, out result);
19 | #pragma warning restore 8601
20 | }
21 | }
22 |
23 | public T Get(ulong handle) {
24 | if (TryGet(handle, out var result)) {
25 | return result;
26 | } else {
27 | throw new InternalException("ConcurrentHandleMap: Invalid handle");
28 | }
29 | }
30 |
31 | public bool Remove(ulong handle) {
32 | return Remove(handle, out T result);
33 | }
34 |
35 | public bool Remove(ulong handle, out T result) {
36 | lock (lock_) {
37 | // Possible null reference assignment
38 | #pragma warning disable 8601
39 | if (map.TryGetValue(handle, out result)) {
40 | #pragma warning restore 8601
41 | map.Remove(handle);
42 | return true;
43 | } else {
44 | return false;
45 | }
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/bindgen/templates/CustomTypeTemplate.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | {%- match config.custom_types.get(name.as_str()) %}
6 | {%- when None %}
7 | {#- Define the type using typealiases to the builtin #}
8 | /**
9 | * Typealias from the type name used in the UDL file to the builtin type. This
10 | * is needed because the UDL type name is used in function/method signatures.
11 | * It's also what we have an external type that references a custom type.
12 | */
13 | {% let type_name_custom = builtin|type_name_custom(ci) %}
14 | {% let type_name_converter = builtin|ffi_converter_name %}
15 | {{- self.add_type_alias(name, type_name_custom) }}
16 | {{- self.add_type_alias(ffi_converter_name, type_name_converter) }}
17 |
18 | {%- when Some with (config) %}
19 |
20 | {%- let ffi_type = builtin|ffi_type %}
21 | {%- let ffi_type_name = ffi_type|ffi_type_name %}
22 |
23 | {# When the config specifies a different type name, create a typealias for it #}
24 | {%- match config.type_name %}
25 | {%- when Some(concrete_type_name) %}
26 | /**
27 | * Typealias from the type name used in the UDL file to the custom type. This
28 | * is needed because the UDL type name is used in function/method signatures.
29 | * It's also what we have an external type that references a custom type.
30 | */
31 | {{- self.add_type_alias(name, concrete_type_name) }}
32 | {%- else %}
33 | {%- endmatch %}
34 |
35 | {%- match config.imports %}
36 | {%- when Some(imports) %}
37 | {%- for import_name in imports %}
38 | {{ self.add_import(import_name) }}
39 | {%- endfor %}
40 | {%- else %}
41 | {%- endmatch %}
42 |
43 | class {{ ffi_converter_name }}: FfiConverter<{{ name }}, {{ ffi_type_name }}> {
44 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
45 |
46 | public override {{ name }} Lift({{ ffi_type_name }} value) {
47 | var builtinValue = {{ builtin|lift_fn }}(value);
48 | return {{ config.into_custom.render("builtinValue") }};
49 | }
50 |
51 | public override {{ ffi_type_name }} Lower({{ name }} value) {
52 | var builtinValue = {{ config.from_custom.render("value") }};
53 | return {{ builtin|lower_fn }}(builtinValue);
54 | }
55 |
56 | public override {{ name }} Read(BigEndianStream stream) {
57 | var builtinValue = {{ builtin|read_fn }}(stream);
58 | return {{ config.into_custom.render("builtinValue") }};
59 | }
60 |
61 | public override int AllocationSize({{ name }} value) {
62 | var builtinValue = {{ config.from_custom.render("value") }};
63 | return {{ builtin|allocation_size_fn }}(builtinValue);
64 | }
65 |
66 | public override void Write({{ name }} value, BigEndianStream stream) {
67 | var builtinValue = {{ config.from_custom.render("value") }};
68 | {{ builtin|write_fn }}(builtinValue, stream);
69 | }
70 | }
71 | {%- endmatch %}
72 |
--------------------------------------------------------------------------------
/bindgen/templates/DurationHelper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class FfiConverterDuration: FfiConverterRustBuffer {
6 | public static FfiConverterDuration INSTANCE = new FfiConverterDuration();
7 |
8 | // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/TimeSpan.cs
9 | private const uint NanosecondsPerTick = 100;
10 |
11 | public override TimeSpan Read(BigEndianStream stream) {
12 | var seconds = stream.ReadULong();
13 | var nanoseconds = stream.ReadUInt();
14 | var ticks = seconds * TimeSpan.TicksPerSecond;
15 | ticks += nanoseconds / NanosecondsPerTick;
16 | return new TimeSpan(Convert.ToInt64(ticks));
17 | }
18 |
19 | public override int AllocationSize(TimeSpan value) {
20 | // 8 bytes for seconds, 4 bytes for nanoseconds
21 | return 12;
22 | }
23 |
24 | public override void Write(TimeSpan value, BigEndianStream stream) {
25 | stream.WriteULong(Convert.ToUInt64(value.Ticks / TimeSpan.TicksPerSecond));
26 | stream.WriteUInt(Convert.ToUInt32(value.Ticks % TimeSpan.TicksPerSecond * NanosecondsPerTick));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/bindgen/templates/EnumTemplate.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | {#
6 | // C# doesn't support enums with associated data. Use regular `enum` for flat
7 | // enums, and `record` for enums with associated data.
8 | #}
9 |
10 | {%- if e.is_flat() %}
11 |
12 | {%- call cs::docstring(e, 0) %}
13 | {{ config.access_modifier() }} enum {{ type_name }}: int {
14 | {% for variant in e.variants() -%}
15 | {%- call cs::docstring(variant, 4) %}
16 | {{ variant.name()|enum_variant }}{% if !loop.last %},{% endif %}
17 | {%- endfor %}
18 | }
19 |
20 | class {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
21 | public static {{ e|ffi_converter_name }} INSTANCE = new {{ e|ffi_converter_name }}();
22 |
23 | public override {{ type_name }} Read(BigEndianStream stream) {
24 | var value = stream.ReadInt() - 1;
25 | if (Enum.IsDefined(typeof({{ type_name }}), value)) {
26 | return ({{ type_name }})value;
27 | } else {
28 | throw new InternalException(String.Format("invalid enum value '{0}' in {{ e|ffi_converter_name }}.Read()", value));
29 | }
30 | }
31 |
32 | public override int AllocationSize({{ type_name }} value) {
33 | return 4;
34 | }
35 |
36 | public override void Write({{ type_name }} value, BigEndianStream stream) {
37 | stream.WriteInt((int)value + 1);
38 | }
39 | }
40 |
41 | {% else %}
42 |
43 | {%- call cs::docstring(e, 0) %}
44 | {{ config.access_modifier() }} record {{ type_name }}{% if contains_object_references %}: IDisposable {% endif %} {
45 | {% for variant in e.variants() -%}
46 | {%- call cs::docstring(variant, 4) %}
47 | {% if !variant.has_fields() -%}
48 | public record {{ variant.name()|class_name(ci) }}: {{ type_name }} {}
49 | {% else -%}
50 | public record {{ variant.name()|class_name(ci) }} (
51 | {%- for field in variant.fields() %}
52 | {%- let field_name = field.name()|or_pos_var(loop.index)|var_name %}
53 | {% call cs::enum_parameter_type_name(field|type_name(ci), variant.name()|class_name(ci)) %} {{ field_name }}{% if !loop.last %},{% endif %}
54 | {%- endfor %}
55 | ) : {{ type_name }} {}
56 | {%- endif %}
57 | {% endfor %}
58 |
59 | {% if contains_object_references %}
60 | public void Dispose() {
61 | switch (this) {
62 | {%- for variant in e.variants() %}
63 | case {{ type_name }}.{{ variant.name()|class_name(ci) }} variant_value:
64 | {%- if variant.has_fields() %}
65 | {% call cs::destroy_fields(variant, "variant_value") %}
66 | {%- endif %}
67 | break;
68 | {%- endfor %}
69 | default:
70 | throw new InternalException(String.Format("invalid enum value '{0}' in {{ type_name }}.Dispose()", this));
71 | }
72 | }
73 | {% endif %}
74 | }
75 |
76 | class {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}>{
77 | public static FfiConverterRustBuffer<{{ type_name }}> INSTANCE = new {{ e|ffi_converter_name }}();
78 |
79 | public override {{ type_name }} Read(BigEndianStream stream) {
80 | var value = stream.ReadInt();
81 | switch (value) {
82 | {%- for variant in e.variants() %}
83 | case {{ loop.index }}:
84 | return new {{ type_name }}.{{ variant.name()|class_name(ci) }}(
85 | {%- for field in variant.fields() %}
86 | {{ field|read_fn }}(stream){% if !loop.last %},{% endif %}
87 | {%- endfor %}
88 | );
89 | {%- endfor %}
90 | default:
91 | throw new InternalException(String.Format("invalid enum value '{0}' in {{ e|ffi_converter_name }}.Read()", value));
92 | }
93 | }
94 |
95 | public override int AllocationSize({{ type_name }} value) {
96 | switch (value) {
97 | {%- for variant in e.variants() %}
98 | case {{ type_name }}.{{ variant.name()|class_name(ci) }} variant_value:
99 | return 4
100 | {%- for field in variant.fields() %}
101 | {%- let field_name = field.name()|or_pos_var(loop.index)|var_name %}
102 | + {{ field|allocation_size_fn }}(variant_value.{{ field_name }})
103 | {%- endfor %};
104 | {%- endfor %}
105 | default:
106 | throw new InternalException(String.Format("invalid enum value '{0}' in {{ e|ffi_converter_name }}.AllocationSize()", value));
107 | }
108 | }
109 |
110 | public override void Write({{ type_name }} value, BigEndianStream stream) {
111 | switch (value) {
112 | {%- for variant in e.variants() %}
113 | case {{ type_name }}.{{ variant.name()|class_name(ci) }} variant_value:
114 | stream.WriteInt({{ loop.index }});
115 | {%- for field in variant.fields() %}
116 | {%- let field_name = field.name()|or_pos_var(loop.index)|var_name %}
117 | {{ field|write_fn }}(variant_value.{{ field_name }}, stream);
118 | {%- endfor %}
119 | break;
120 | {%- endfor %}
121 | default:
122 | throw new InternalException(String.Format("invalid enum value '{0}' in {{ e|ffi_converter_name }}.Write()", value));
123 | }
124 | }
125 | }
126 |
127 | {% endif %}
128 |
--------------------------------------------------------------------------------
/bindgen/templates/ExternalTypeTemplate.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | {%- let package_name=self.external_type_package_name(module_path, namespace) %}
6 | {%- let fully_qualified_type_name = "{}.{}"|format(package_name, name) %}
7 | {%- let fully_qualified_ffi_converter_name = "{}.FfiConverterType{}"|format(package_name, name) %}
8 |
9 | {{- self.add_import(fully_qualified_type_name) }}
10 | {{ self.add_import(fully_qualified_ffi_converter_name) }}
11 |
--------------------------------------------------------------------------------
/bindgen/templates/FfiConverterTemplate.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | // The FfiConverter interface handles converter types to and from the FFI
6 | //
7 | // All implementing objects should be public to support external types. When a
8 | // type is external we need to import it's FfiConverter.
9 | internal abstract class FfiConverter {
10 | // Convert an FFI type to a C# type
11 | public abstract CsType Lift(FfiType value);
12 |
13 | // Convert C# type to an FFI type
14 | public abstract FfiType Lower(CsType value);
15 |
16 | // Read a C# type from a `ByteBuffer`
17 | public abstract CsType Read(BigEndianStream stream);
18 |
19 | // Calculate bytes to allocate when creating a `RustBuffer`
20 | //
21 | // This must return at least as many bytes as the write() function will
22 | // write. It can return more bytes than needed, for example when writing
23 | // Strings we can't know the exact bytes needed until we the UTF-8
24 | // encoding, so we pessimistically allocate the largest size possible (3
25 | // bytes per codepoint). Allocating extra bytes is not really a big deal
26 | // because the `RustBuffer` is short-lived.
27 | public abstract int AllocationSize(CsType value);
28 |
29 | // Write a C# type to a `ByteBuffer`
30 | public abstract void Write(CsType value, BigEndianStream stream);
31 |
32 | // Lower a value into a `RustBuffer`
33 | //
34 | // This method lowers a value into a `RustBuffer` rather than the normal
35 | // FfiType. It's used by the callback interface code. Callback interface
36 | // returns are always serialized into a `RustBuffer` regardless of their
37 | // normal FFI type.
38 | public RustBuffer LowerIntoRustBuffer(CsType value) {
39 | var rbuf = RustBuffer.Alloc(AllocationSize(value));
40 | try {
41 | var stream = rbuf.AsWriteableStream();
42 | Write(value, stream);
43 | rbuf.len = Convert.ToUInt64(stream.Position);
44 | return rbuf;
45 | } catch {
46 | RustBuffer.Free(rbuf);
47 | throw;
48 | }
49 | }
50 |
51 | // Lift a value from a `RustBuffer`.
52 | //
53 | // This here mostly because of the symmetry with `lowerIntoRustBuffer()`.
54 | // It's currently only used by the `FfiConverterRustBuffer` class below.
55 | protected CsType LiftFromRustBuffer(RustBuffer rbuf) {
56 | var stream = rbuf.AsStream();
57 | try {
58 | var item = Read(stream);
59 | if (stream.HasRemaining()) {
60 | throw new InternalException("junk remaining in buffer after lifting, something is very wrong!!");
61 | }
62 | return item;
63 | } finally {
64 | RustBuffer.Free(rbuf);
65 | }
66 | }
67 | }
68 |
69 | // FfiConverter that uses `RustBuffer` as the FfiType
70 | internal abstract class FfiConverterRustBuffer: FfiConverter {
71 | public override CsType Lift(RustBuffer value) {
72 | return LiftFromRustBuffer(value);
73 | }
74 | public override RustBuffer Lower(CsType value) {
75 | return LowerIntoRustBuffer(value);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/bindgen/templates/Float32Helper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class {{ ffi_converter_name }}: FfiConverter {
6 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
7 |
8 | public override float Lift(float value) {
9 | return value;
10 | }
11 |
12 | public override float Read(BigEndianStream stream) {
13 | return stream.ReadFloat();
14 | }
15 |
16 | public override float Lower(float value) {
17 | return value;
18 | }
19 |
20 | public override int AllocationSize(float value) {
21 | return 4;
22 | }
23 |
24 | public override void Write(float value, BigEndianStream stream) {
25 | stream.WriteFloat(value);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/bindgen/templates/Float64Helper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class {{ ffi_converter_name }}: FfiConverter {
6 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
7 |
8 | public override double Lift(double value) {
9 | return value;
10 | }
11 |
12 | public override double Read(BigEndianStream stream) {
13 | return stream.ReadDouble();
14 | }
15 |
16 | public override double Lower(double value) {
17 | return value;
18 | }
19 |
20 | public override int AllocationSize(double value) {
21 | return 8;
22 | }
23 |
24 | public override void Write(double value, BigEndianStream stream) {
25 | stream.WriteDouble(value);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/bindgen/templates/Helpers.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | // A handful of classes and functions to support the generated data structures.
6 | // This would be a good candidate for isolating in its own ffi-support lib.
7 | // Error runtime.
8 | [StructLayout(LayoutKind.Sequential)]
9 | struct UniffiRustCallStatus {
10 | public sbyte code;
11 | public RustBuffer error_buf;
12 |
13 | public bool IsSuccess() {
14 | return code == 0;
15 | }
16 |
17 | public bool IsError() {
18 | return code == 1;
19 | }
20 |
21 | public bool IsPanic() {
22 | return code == 2;
23 | }
24 | }
25 |
26 | // Base class for all uniffi exceptions
27 | {{ config.access_modifier() }} class UniffiException: System.Exception {
28 | public UniffiException(): base() {}
29 | public UniffiException(string message): base(message) {}
30 | }
31 |
32 | {{ config.access_modifier() }} class UndeclaredErrorException: UniffiException {
33 | public UndeclaredErrorException(string message): base(message) {}
34 | }
35 |
36 | {{ config.access_modifier() }} class PanicException: UniffiException {
37 | public PanicException(string message): base(message) {}
38 | }
39 |
40 | {{ config.access_modifier() }} class AllocationException: UniffiException {
41 | public AllocationException(string message): base(message) {}
42 | }
43 |
44 | {{ config.access_modifier() }} class InternalException: UniffiException {
45 | public InternalException(string message): base(message) {}
46 | }
47 |
48 | {{ config.access_modifier() }} class InvalidEnumException: InternalException {
49 | public InvalidEnumException(string message): base(message) {
50 | }
51 | }
52 |
53 | {{ config.access_modifier() }} class UniffiContractVersionException: UniffiException {
54 | public UniffiContractVersionException(string message): base(message) {
55 | }
56 | }
57 |
58 | {{ config.access_modifier() }} class UniffiContractChecksumException: UniffiException {
59 | public UniffiContractChecksumException(string message): base(message) {
60 | }
61 | }
62 |
63 | // Each top-level error class has a companion object that can lift the error from the call status's rust buffer
64 | interface CallStatusErrorHandler where E: System.Exception {
65 | E Lift(RustBuffer error_buf);
66 | }
67 |
68 | // CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
69 | class NullCallStatusErrorHandler: CallStatusErrorHandler {
70 | public static NullCallStatusErrorHandler INSTANCE = new NullCallStatusErrorHandler();
71 |
72 | public UniffiException Lift(RustBuffer error_buf) {
73 | RustBuffer.Free(error_buf);
74 | return new UndeclaredErrorException("library has returned an error not declared in UNIFFI interface file");
75 | }
76 | }
77 |
78 | // Helpers for calling Rust
79 | // In practice we usually need to be synchronized to call this safely, so it doesn't
80 | // synchronize itself
81 | class _UniffiHelpers {
82 | public delegate void RustCallAction(ref UniffiRustCallStatus status);
83 | public delegate U RustCallFunc(ref UniffiRustCallStatus status);
84 |
85 | // Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err
86 | public static U RustCallWithError(CallStatusErrorHandler errorHandler, RustCallFunc callback)
87 | where E: UniffiException
88 | {
89 | var status = new UniffiRustCallStatus();
90 | var return_value = callback(ref status);
91 | if (status.IsSuccess()) {
92 | return return_value;
93 | } else if (status.IsError()) {
94 | throw errorHandler.Lift(status.error_buf);
95 | } else if (status.IsPanic()) {
96 | // when the rust code sees a panic, it tries to construct a rustbuffer
97 | // with the message. but if that code panics, then it just sends back
98 | // an empty buffer.
99 | if (status.error_buf.len > 0) {
100 | throw new PanicException({{ Type::String.borrow()|lift_fn }}(status.error_buf));
101 | } else {
102 | throw new PanicException("Rust panic");
103 | }
104 | } else {
105 | throw new InternalException($"Unknown rust call status: {status.code}");
106 | }
107 | }
108 |
109 | // Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err
110 | public static void RustCallWithError(CallStatusErrorHandler errorHandler, RustCallAction callback)
111 | where E: UniffiException
112 | {
113 | _UniffiHelpers.RustCallWithError(errorHandler, (ref UniffiRustCallStatus status) => {
114 | callback(ref status);
115 | return 0;
116 | });
117 | }
118 |
119 | // Call a rust function that returns a plain value
120 | public static U RustCall(RustCallFunc callback) {
121 | return _UniffiHelpers.RustCallWithError(NullCallStatusErrorHandler.INSTANCE, callback);
122 | }
123 |
124 | // Call a rust function that returns a plain value
125 | public static void RustCall(RustCallAction callback) {
126 | _UniffiHelpers.RustCall((ref UniffiRustCallStatus status) => {
127 | callback(ref status);
128 | return 0;
129 | });
130 | }
131 | }
132 |
133 | static class FFIObjectUtil {
134 | public static void DisposeAll(params Object?[] list) {
135 | foreach (var obj in list) {
136 | Dispose(obj);
137 | }
138 | }
139 |
140 | // Dispose is implemented by recursive type inspection at runtime. This is because
141 | // generating correct Dispose calls for recursive complex types, e.g. List>
142 | // is quite cumbersome.
143 | private static void Dispose(dynamic? obj) {
144 | if (obj == null) {
145 | return;
146 | }
147 |
148 | if (obj is IDisposable disposable) {
149 | disposable.Dispose();
150 | return;
151 | }
152 |
153 | var type = obj.GetType();
154 | if (type != null) {
155 | if (type.IsGenericType) {
156 | if (type.GetGenericTypeDefinition().IsAssignableFrom(typeof(List<>))) {
157 | foreach (var value in obj) {
158 | Dispose(value);
159 | }
160 | } else if (type.GetGenericTypeDefinition().IsAssignableFrom(typeof(Dictionary<,>))) {
161 | foreach (var value in obj.Values) {
162 | Dispose(value);
163 | }
164 | }
165 | }
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/bindgen/templates/Int16Helper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class {{ ffi_converter_name }}: FfiConverter {
6 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
7 |
8 | public override short Lift(short value) {
9 | return value;
10 | }
11 |
12 | public override short Read(BigEndianStream stream) {
13 | return stream.ReadShort();
14 | }
15 |
16 | public override short Lower(short value) {
17 | return value;
18 | }
19 |
20 | public override int AllocationSize(short value) {
21 | return 2;
22 | }
23 |
24 | public override void Write(short value, BigEndianStream stream) {
25 | stream.WriteShort(value);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/bindgen/templates/Int32Helper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class {{ ffi_converter_name }}: FfiConverter {
6 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
7 |
8 | public override int Lift(int value) {
9 | return value;
10 | }
11 |
12 | public override int Read(BigEndianStream stream) {
13 | return stream.ReadInt();
14 | }
15 |
16 | public override int Lower(int value) {
17 | return value;
18 | }
19 |
20 | public override int AllocationSize(int value) {
21 | return 4;
22 | }
23 |
24 | public override void Write(int value, BigEndianStream stream) {
25 | stream.WriteInt(value);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/bindgen/templates/Int64Helper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class {{ ffi_converter_name }}: FfiConverter {
6 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
7 |
8 | public override long Lift(long value) {
9 | return value;
10 | }
11 |
12 | public override long Read(BigEndianStream stream) {
13 | return stream.ReadLong();
14 | }
15 |
16 | public override long Lower(long value) {
17 | return value;
18 | }
19 |
20 | public override int AllocationSize(long value) {
21 | return 8;
22 | }
23 |
24 | public override void Write(long value, BigEndianStream stream) {
25 | stream.WriteLong(value);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/bindgen/templates/Int8Helper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class {{ ffi_converter_name }}: FfiConverter {
6 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
7 |
8 | public override sbyte Lift(sbyte value) {
9 | return value;
10 | }
11 |
12 | public override sbyte Read(BigEndianStream stream) {
13 | return stream.ReadSByte();
14 | }
15 |
16 | public override sbyte Lower(sbyte value) {
17 | return value;
18 | }
19 |
20 | public override int AllocationSize(sbyte value) {
21 | return 1;
22 | }
23 |
24 | public override void Write(sbyte value, BigEndianStream stream) {
25 | stream.WriteSByte(value);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/bindgen/templates/MapTemplate.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
6 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
7 |
8 | public override {{ type_name }} Read(BigEndianStream stream) {
9 | var result = new {{ type_name }}();
10 | var len = stream.ReadInt();
11 | for (int i = 0; i < len; i++) {
12 | var key = {{ key_type|read_fn }}(stream);
13 | var value = {{ value_type|read_fn }}(stream);
14 | result[key] = value;
15 | }
16 | return result;
17 | }
18 |
19 | public override int AllocationSize({{ type_name }} value) {
20 | var sizeForLength = 4;
21 |
22 | // details/1-empty-list-as-default-method-parameter.md
23 | if (value == null) {
24 | return sizeForLength;
25 | }
26 |
27 | var sizeForItems = value.Select(item => {
28 | return {{ key_type|allocation_size_fn }}(item.Key) +
29 | {{ value_type|allocation_size_fn }}(item.Value);
30 | }).Sum();
31 | return sizeForLength + sizeForItems;
32 | }
33 |
34 | public override void Write({{ type_name }} value, BigEndianStream stream) {
35 | // details/1-empty-list-as-default-method-parameter.md
36 | if (value == null) {
37 | stream.WriteInt(0);
38 | return;
39 | }
40 |
41 | stream.WriteInt(value.Count);
42 | foreach (var item in value) {
43 | {{ key_type|write_fn }}(item.Key, stream);
44 | {{ value_type|write_fn }}(item.Value, stream);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/bindgen/templates/NamespaceLibraryTemplate.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | // This is an implementation detail that will be called internally by the public API.
6 | static class _UniFFILib {
7 | {%- for def in ci.ffi_definitions() %}
8 | {%- match def %}
9 | {%- when FfiDefinition::CallbackFunction(callback) %}
10 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
11 | public delegate {% call cs::ffi_return_type(callback) %} {{ callback.name()|ffi_callback_name }}(
12 | {% call cs::arg_list_ffi_decl(callback) %}
13 | );
14 | {%- when FfiDefinition::Struct(ffi_struct) %}
15 | [StructLayout(LayoutKind.Sequential)]
16 | public struct {{ ffi_struct.name()|ffi_struct_name }}
17 | {
18 | {%- for field in ffi_struct.fields() %}
19 | public {{ field.type_().borrow()|ffi_type_name }} {{ field.name()|var_name }};
20 | {%- endfor %}
21 | }
22 | {%- when FfiDefinition::Function(_) %}
23 | {# functions are handled below #}
24 | {%- endmatch %}
25 | {%- endfor %}
26 |
27 | static _UniFFILib() {
28 | _UniFFILib.uniffiCheckContractApiVersion();
29 | _UniFFILib.uniffiCheckApiChecksums();
30 | {% let initialization_fns = self.initialization_fns() %}
31 | {% for func in initialization_fns -%}
32 | {{ func }}();
33 | {% endfor -%}
34 | }
35 |
36 | {% for func in ci.iter_ffi_function_definitions() -%}
37 | [DllImport("{{ config.cdylib_name() }}", CallingConvention = CallingConvention.Cdecl)]
38 | public static extern {%- match func.return_type() -%}{%- when Some with (type_) %} {{ type_.borrow()|ffi_type_name }}{% when None %} void{% endmatch %} {{ func.name() }}(
39 | {%- call cs::arg_list_ffi_decl(func) %}
40 | );
41 |
42 | {% endfor %}
43 |
44 | static void uniffiCheckContractApiVersion() {
45 | var scaffolding_contract_version = _UniFFILib.{{ ci.ffi_uniffi_contract_version().name() }}();
46 | if ({{ ci.uniffi_contract_version() }} != scaffolding_contract_version) {
47 | throw new UniffiContractVersionException($"{{ config.namespace() }}: uniffi bindings expected version `{{ ci.uniffi_contract_version() }}`, library returned `{scaffolding_contract_version}`");
48 | }
49 | }
50 |
51 | static void uniffiCheckApiChecksums() {
52 | {%- for (name, expected_checksum) in ci.iter_checksums() %}
53 | {
54 | var checksum = _UniFFILib.{{ name }}();
55 | if (checksum != {{ expected_checksum }}) {
56 | throw new UniffiContractChecksumException($"{{ config.namespace() }}: uniffi bindings expected function `{{ name }}` checksum `{{ expected_checksum }}`, library returned `{checksum}`");
57 | }
58 | }
59 | {%- endfor %}
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/bindgen/templates/OptionalTemplate.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | {%- let inner_type_name = inner_type|type_name(ci) %}
6 |
7 | class {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_name }}?> {
8 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
9 |
10 | public override {{ inner_type_name }}? Read(BigEndianStream stream) {
11 | if (stream.ReadByte() == 0) {
12 | return null;
13 | }
14 | return {{ inner_type|read_fn }}(stream);
15 | }
16 |
17 | public override int AllocationSize({{ inner_type_name }}? value) {
18 | if (value == null) {
19 | return 1;
20 | } else {
21 | return 1 + {{ inner_type|allocation_size_fn }}(({{ inner_type_name }})value);
22 | }
23 | }
24 |
25 | public override void Write({{ inner_type_name }}? value, BigEndianStream stream) {
26 | if (value == null) {
27 | stream.WriteByte(0);
28 | } else {
29 | stream.WriteByte(1);
30 | {{ inner_type|write_fn }}(({{ inner_type_name }})value, stream);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/bindgen/templates/RecordTemplate.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | {%- let rec = ci.get_record_definition(name).unwrap() %}
6 | {%- let (ordered_fields, is_reordered) = rec.fields()|order_fields %}
7 |
8 | {%- call cs::docstring(rec, 0) %}
9 | {%- for field in ordered_fields %}
10 | {%- match field.docstring() %}
11 | {%- when Some with(docstring) %}
12 | ///
13 | {%- let docstring = textwrap::dedent(docstring) %}
14 | {%- for line in docstring.lines() %}
15 | /// {{ line.trim_end() }}
16 | {%- endfor %}
17 | ///
18 | {%- else %}
19 | {%- endmatch %}
20 | {%- endfor %}
21 | {%- let (ordered_fields, is_reordered) = rec.fields()|order_fields %}
22 | {%- if is_reordered %}
23 | ///
24 | /// UniFFI Warning: Optional parameters have been reordered because
25 | /// of a C# syntax limitation. Use named parameters for compatibility with
26 | /// future ordering changes.
27 | ///
28 | {%- endif %}
29 | {{ config.access_modifier() }} record {{ type_name }} (
30 | {%- for field in ordered_fields %}
31 | {%- call cs::docstring(field, 4) %}
32 | {{ field|type_name(ci) }} {{ field.name()|var_name -}}
33 | {%- match field.default_value() %}
34 | {%- when Some with(literal) %} = {{ literal|render_literal(field, ci) }}
35 | {%- else %}
36 | {%- endmatch -%}
37 | {% if !loop.last %}, {% endif %}
38 | {%- endfor %}
39 | ) {% if contains_object_references %}: IDisposable {% endif %}{
40 | {%- if contains_object_references %}
41 | public void Dispose() {
42 | {%- call cs::destroy_fields(rec, "this") %}
43 | }
44 | {%- endif %}
45 | }
46 |
47 | class {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
48 | public static {{ rec|ffi_converter_name }} INSTANCE = new {{ rec|ffi_converter_name }}();
49 |
50 | public override {{ type_name }} Read(BigEndianStream stream) {
51 | return new {{ type_name }}(
52 | {%- for field in rec.fields() %}
53 | {{ field.name()|var_name }}: {{ field|read_fn }}(stream){% if !loop.last %},{% endif%}
54 | {%- endfor %}
55 | );
56 | }
57 |
58 | public override int AllocationSize({{ type_name }} value) {
59 | return 0
60 | {%- for field in rec.fields() %}
61 | + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }})
62 | {%- endfor -%};
63 | }
64 |
65 | public override void Write({{ type_name }} value, BigEndianStream stream) {
66 | {%- for field in rec.fields() %}
67 | {{ field|write_fn }}(value.{{ field.name()|var_name }}, stream);
68 | {%- endfor %}
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/bindgen/templates/RustBufferTemplate.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | // This is a helper for safely working with byte buffers returned from the Rust code.
6 | // A rust-owned buffer is represented by its capacity, its current length, and a
7 | // pointer to the underlying data.
8 |
9 | [StructLayout(LayoutKind.Sequential)]
10 | internal struct RustBuffer {
11 | public ulong capacity;
12 | public ulong len;
13 | public IntPtr data;
14 |
15 | public static RustBuffer Alloc(int size) {
16 | return _UniffiHelpers.RustCall((ref UniffiRustCallStatus status) => {
17 | var buffer = _UniFFILib.{{ ci.ffi_rustbuffer_alloc().name() }}(Convert.ToUInt64(size), ref status);
18 | if (buffer.data == IntPtr.Zero) {
19 | throw new AllocationException($"RustBuffer.Alloc() returned null data pointer (size={size})");
20 | }
21 | return buffer;
22 | });
23 | }
24 |
25 | public static void Free(RustBuffer buffer) {
26 | _UniffiHelpers.RustCall((ref UniffiRustCallStatus status) => {
27 | _UniFFILib.{{ ci.ffi_rustbuffer_free().name() }}(buffer, ref status);
28 | });
29 | }
30 |
31 | public static BigEndianStream MemoryStream(IntPtr data, long length)
32 | {
33 | unsafe
34 | {
35 | return new BigEndianStream(new UnmanagedMemoryStream((byte*)data.ToPointer(), length));
36 | }
37 | }
38 |
39 | public BigEndianStream AsStream()
40 | {
41 | unsafe
42 | {
43 | return new BigEndianStream(
44 | new UnmanagedMemoryStream((byte*)data.ToPointer(), Convert.ToInt64(len))
45 | );
46 | }
47 | }
48 |
49 | public BigEndianStream AsWriteableStream()
50 | {
51 | unsafe
52 | {
53 | return new BigEndianStream(
54 | new UnmanagedMemoryStream(
55 | (byte*)data.ToPointer(),
56 | Convert.ToInt64(capacity),
57 | Convert.ToInt64(capacity),
58 | FileAccess.Write
59 | )
60 | );
61 | }
62 | }
63 | }
64 |
65 | // This is a helper for safely passing byte references into the rust code.
66 | // It's not actually used at the moment, because there aren't many things that you
67 | // can take a direct pointer to managed memory, and if we're going to copy something
68 | // then we might as well copy it into a `RustBuffer`. But it's here for API
69 | // completeness.
70 |
71 | [StructLayout(LayoutKind.Sequential)]
72 | internal struct ForeignBytes {
73 | public int length;
74 | public IntPtr data;
75 | }
76 |
--------------------------------------------------------------------------------
/bindgen/templates/SequenceTemplate.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | {%- let inner_type_name = inner_type|type_name(ci) %}
6 |
7 | class {{ ffi_converter_name }}: FfiConverterRustBuffer> {
8 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
9 |
10 | public override List<{{ inner_type_name }}> Read(BigEndianStream stream) {
11 | var length = stream.ReadInt();
12 | var result = new List<{{ inner_type_name }}>(length);
13 | for (int i = 0; i < length; i++) {
14 | result.Add({{ inner_type|read_fn }}(stream));
15 | }
16 | return result;
17 | }
18 |
19 | public override int AllocationSize(List<{{ inner_type_name }}> value) {
20 | var sizeForLength = 4;
21 |
22 | // details/1-empty-list-as-default-method-parameter.md
23 | if (value == null) {
24 | return sizeForLength;
25 | }
26 |
27 | var sizeForItems = value.Select(item => {{ inner_type|allocation_size_fn }}(item)).Sum();
28 | return sizeForLength + sizeForItems;
29 | }
30 |
31 | public override void Write(List<{{ inner_type_name }}> value, BigEndianStream stream) {
32 | // details/1-empty-list-as-default-method-parameter.md
33 | if (value == null) {
34 | stream.WriteInt(0);
35 | return;
36 | }
37 |
38 | stream.WriteInt(value.Count);
39 | value.ForEach(item => {{ inner_type|write_fn }}(item, stream));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/bindgen/templates/StringHelper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class FfiConverterString: FfiConverter {
6 | public static FfiConverterString INSTANCE = new FfiConverterString();
7 |
8 | // Note: we don't inherit from FfiConverterRustBuffer, because we use a
9 | // special encoding when lowering/lifting. We can use `RustBuffer.len` to
10 | // store our length and avoid writing it out to the buffer.
11 | public override string Lift(RustBuffer value) {
12 | try {
13 | var bytes = value.AsStream().ReadBytes(Convert.ToInt32(value.len));
14 | return System.Text.Encoding.UTF8.GetString(bytes);
15 | } finally {
16 | RustBuffer.Free(value);
17 | }
18 | }
19 |
20 | public override string Read(BigEndianStream stream) {
21 | var length = stream.ReadInt();
22 | var bytes = stream.ReadBytes(length);
23 | return System.Text.Encoding.UTF8.GetString(bytes);
24 | }
25 |
26 | public override RustBuffer Lower(string value) {
27 | {%- match config.null_string_to_empty %}
28 | {%- when Some(true) %}
29 | if (value == null) {
30 | value = "";
31 | }
32 | {%- when _ %}
33 | {%- endmatch %}
34 | var bytes = System.Text.Encoding.UTF8.GetBytes(value);
35 | var rbuf = RustBuffer.Alloc(bytes.Length);
36 | rbuf.AsWriteableStream().WriteBytes(bytes);
37 | return rbuf;
38 | }
39 |
40 | // TODO(CS)
41 | // We aren't sure exactly how many bytes our string will be once it's UTF-8
42 | // encoded. Allocate 3 bytes per unicode codepoint which will always be
43 | // enough.
44 | public override int AllocationSize(string value) {
45 | const int sizeForLength = 4;
46 | var sizeForString = System.Text.Encoding.UTF8.GetByteCount(value);
47 | return sizeForLength + sizeForString;
48 | }
49 |
50 | public override void Write(string value, BigEndianStream stream) {
51 | var bytes = System.Text.Encoding.UTF8.GetBytes(value);
52 | stream.WriteInt(bytes.Length);
53 | stream.WriteBytes(bytes);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/bindgen/templates/TimestampHelper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class FfiConverterTimestamp: FfiConverterRustBuffer {
6 | public static FfiConverterTimestamp INSTANCE = new FfiConverterTimestamp();
7 |
8 | // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/TimeSpan.cs
9 | private const uint NanosecondsPerTick = 100;
10 |
11 | // DateTime.UnixEpoch is not available in net48
12 | private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
13 |
14 | public override DateTime Read(BigEndianStream stream) {
15 | var seconds = stream.ReadLong();
16 | var nanoseconds = stream.ReadUInt();
17 | var sign = 1;
18 | if (seconds < 0) {
19 | sign = -1;
20 | }
21 | var ticks = seconds * TimeSpan.TicksPerSecond;
22 | ticks += (nanoseconds / NanosecondsPerTick) * sign;
23 | return UnixEpoch.AddTicks(ticks);
24 | }
25 |
26 | public override int AllocationSize(DateTime value) {
27 | // 8 bytes for seconds, 4 bytes for nanoseconds
28 | return 12;
29 | }
30 |
31 | public override void Write(DateTime value, BigEndianStream stream) {
32 | var epochOffset = value.Subtract(UnixEpoch);
33 |
34 | int sign = 1;
35 | if (epochOffset.Ticks < 0) {
36 | epochOffset = epochOffset.Negate();
37 | sign = -1;
38 | }
39 |
40 | stream.WriteLong(epochOffset.Ticks / TimeSpan.TicksPerSecond * sign);
41 | stream.WriteUInt(Convert.ToUInt32(epochOffset.Ticks % TimeSpan.TicksPerSecond * NanosecondsPerTick));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/bindgen/templates/TopLevelFunctionTemplate.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | {%- call cs::docstring(func, 4) %}
6 | {%- call cs::method_throws_annotation(func.throws_type()) %}
7 | {%- if func.is_async() %}
8 | public static async {% call cs::return_type(func) %} {{ func.name()|fn_name }}({%- call cs::arg_list_decl(func) -%})
9 | {
10 | {%- call cs::async_call(func, false) %}
11 | }
12 | {%- else %}
13 | {%- match func.return_type() -%}
14 | {%- when Some with (return_type) %}
15 | public static {{ return_type|type_name(ci) }} {{ func.name()|fn_name }}({%- call cs::arg_list_decl(func) -%}) {
16 | return {{ return_type|lift_fn }}({% call cs::to_ffi_call(func) %});
17 | }
18 | {% when None %}
19 | public static void {{ func.name()|fn_name }}({% call cs::arg_list_decl(func) %}) {
20 | {% call cs::to_ffi_call(func) %};
21 | }
22 | {% endmatch %}
23 | {% endif %}
24 |
--------------------------------------------------------------------------------
/bindgen/templates/Types.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | {%- import "macros.cs" as cs %}
6 |
7 | {%- for type_ in ci.iter_types() %}
8 | {%- let type_name = type_|type_name(ci) %}
9 | {%- let ffi_converter_name = type_|ffi_converter_name %}
10 | {%- let canonical_type_name = type_|canonical_name %}
11 | {%- let contains_object_references = ci.item_contains_object_references(type_) %}
12 |
13 | {#
14 | # Map `Type` instances to an include statement for that type.
15 | #
16 | # There is a companion match in `CsCodeOracle::create_code_type()` which performs a similar function for the
17 | # Rust code.
18 | #
19 | # - When adding additional types here, make sure to also add a match arm to that function.
20 | # - To keep things managable, let's try to limit ourselves to these 2 mega-matches
21 | #}
22 | {%- match type_ %}
23 |
24 | {%- when Type::Boolean %}
25 | {%- include "BooleanHelper.cs" %}
26 |
27 | {%- when Type::Int8 %}
28 | {%- include "Int8Helper.cs" %}
29 |
30 | {%- when Type::Int16 %}
31 | {%- include "Int16Helper.cs" %}
32 |
33 | {%- when Type::Int32 %}
34 | {%- include "Int32Helper.cs" %}
35 |
36 | {%- when Type::Int64 %}
37 | {%- include "Int64Helper.cs" %}
38 |
39 | {%- when Type::UInt8 %}
40 | {%- include "UInt8Helper.cs" %}
41 |
42 | {%- when Type::UInt16 %}
43 | {%- include "UInt16Helper.cs" %}
44 |
45 | {%- when Type::UInt32 %}
46 | {%- include "UInt32Helper.cs" %}
47 |
48 | {%- when Type::UInt64 %}
49 | {%- include "UInt64Helper.cs" %}
50 |
51 | {%- when Type::Float32 %}
52 | {%- include "Float32Helper.cs" %}
53 |
54 | {%- when Type::Float64 %}
55 | {%- include "Float64Helper.cs" %}
56 |
57 | {%- when Type::String %}
58 | {%- include "StringHelper.cs" %}
59 |
60 | {%- when Type::Enum { name, module_path } %}
61 | {%- let e = ci.get_enum_definition(name).unwrap() %}
62 | {%- if !ci.is_name_used_as_error(name) %}
63 | {% include "EnumTemplate.cs" %}
64 | {%- else %}
65 | {% include "ErrorTemplate.cs" %}
66 | {%- endif -%}
67 |
68 | {%- when Type::Object{ name, imp, module_path } %}
69 | {% include "ObjectTemplate.cs" %}
70 |
71 | {%- when Type::Record{ name, module_path } %}
72 | {% include "RecordTemplate.cs" %}
73 |
74 | {%- when Type::Optional { inner_type } %}
75 | {% include "OptionalTemplate.cs" %}
76 |
77 | {%- when Type::Sequence { inner_type } %}
78 | {% include "SequenceTemplate.cs" %}
79 |
80 | {%- when Type::Bytes %}
81 | {% include "BytesTemplate.cs" %}
82 |
83 | {%- when Type::Map { key_type, value_type } %}
84 | {% include "MapTemplate.cs" %}
85 |
86 | {%- when Type::CallbackInterface { name, module_path } %}
87 | {% include "CallbackInterfaceTemplate.cs" %}
88 |
89 | {%- when Type::Timestamp %}
90 | {% include "TimestampHelper.cs" %}
91 |
92 | {%- when Type::Duration %}
93 | {% include "DurationHelper.cs" %}
94 |
95 | {%- when Type::Custom { module_path, name, builtin } %}
96 | {% include "CustomTypeTemplate.cs" %}
97 |
98 | {%- when Type::External { module_path, name, namespace, kind, tagged } %}
99 | {% include "ExternalTypeTemplate.cs" %}
100 |
101 | {%- endmatch %}
102 | {%- endfor %}
103 |
104 | {%- if ci.has_async_fns() %}
105 | {% include "Async.cs" %}
106 | {%- endif %}
--------------------------------------------------------------------------------
/bindgen/templates/UInt16Helper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class {{ ffi_converter_name }}: FfiConverter {
6 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
7 |
8 | public override ushort Lift(ushort value) {
9 | return value;
10 | }
11 |
12 | public override ushort Read(BigEndianStream stream) {
13 | return stream.ReadUShort();
14 | }
15 |
16 | public override ushort Lower(ushort value) {
17 | return value;
18 | }
19 |
20 | public override int AllocationSize(ushort value) {
21 | return 2;
22 | }
23 |
24 | public override void Write(ushort value, BigEndianStream stream) {
25 | stream.WriteUShort(value);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/bindgen/templates/UInt32Helper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class {{ ffi_converter_name }}: FfiConverter {
6 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
7 |
8 | public override uint Lift(uint value) {
9 | return value;
10 | }
11 |
12 | public override uint Read(BigEndianStream stream) {
13 | return stream.ReadUInt();
14 | }
15 |
16 | public override uint Lower(uint value) {
17 | return value;
18 | }
19 |
20 | public override int AllocationSize(uint value) {
21 | return 4;
22 | }
23 |
24 | public override void Write(uint value, BigEndianStream stream) {
25 | stream.WriteUInt(value);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/bindgen/templates/UInt64Helper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class {{ ffi_converter_name }}: FfiConverter {
6 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
7 |
8 | public override ulong Lift(ulong value) {
9 | return value;
10 | }
11 |
12 | public override ulong Read(BigEndianStream stream) {
13 | return stream.ReadULong();
14 | }
15 |
16 | public override ulong Lower(ulong value) {
17 | return value;
18 | }
19 |
20 | public override int AllocationSize(ulong value) {
21 | return 8;
22 | }
23 |
24 | public override void Write(ulong value, BigEndianStream stream) {
25 | stream.WriteULong(value);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/bindgen/templates/UInt8Helper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#}
4 |
5 | class {{ ffi_converter_name }}: FfiConverter {
6 | public static {{ ffi_converter_name }} INSTANCE = new {{ ffi_converter_name }}();
7 |
8 | public override byte Lift(byte value) {
9 | return value;
10 | }
11 |
12 | public override byte Read(BigEndianStream stream) {
13 | return stream.ReadByte();
14 | }
15 |
16 | public override byte Lower(byte value) {
17 | return value;
18 | }
19 |
20 | public override int AllocationSize(byte value) {
21 | return 1;
22 | }
23 |
24 | public override void Write(byte value, BigEndianStream stream) {
25 | stream.WriteByte(value);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/bindgen/templates/wrapper.cs:
--------------------------------------------------------------------------------
1 | {#/* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | // Common helper code.
6 | //
7 | // Ideally this would live in a separate .cs file where it can be unittested etc
8 | // in isolation, and perhaps even published as a re-useable package.
9 | //
10 | // However, it's important that the detils of how this helper code works (e.g. the
11 | // way that different builtin types are passed across the FFI) exactly match what's
12 | // expected by the Rust code on the other side of the interface. In practice right
13 | // now that means coming from the exact some version of `uniffi` that was used to
14 | // compile the Rust component. The easiest way to ensure this is to bundle the C#
15 | // helpers directly inline like we're doing here.
16 | #}
17 |
18 | {#
19 | // Don't import directly to dedup and sort imports together with user defined
20 | // imports for custom types, e.g. `System` from `/uniffi-test-fixtures.toml`.
21 | #}
22 | {{- self.add_import("System") }}
23 | {{- self.add_import("System.Collections.Generic") }}
24 | {{- self.add_import("System.IO") }}
25 | {{- self.add_import("System.Linq") }}
26 | {{- self.add_import("System.Runtime.InteropServices") }}
27 |
28 | {%- for imported_class in self.imports() %}
29 | using {{ imported_class }};
30 | {%- endfor %}
31 |
32 | {%- call cs::docstring_value(ci.namespace_docstring(), 0) %}
33 | namespace {{ config.namespace() }};
34 |
35 | {%- for alias in self.type_aliases() %}
36 | using {{ alias.alias }} = {{ alias.original_type }};
37 | {%- endfor %}
38 |
39 | {% include "RustBufferTemplate.cs" %}
40 | {% include "FfiConverterTemplate.cs" %}
41 | {% include "Helpers.cs" %}
42 | {% include "BigEndianStream.cs" %}
43 |
44 | // Contains loading, initialization code,
45 | // and the FFI Function declarations in a com.sun.jna.Library.
46 | {% include "NamespaceLibraryTemplate.cs" %}
47 |
48 | // Public interface members begin here.
49 | {# details/1-empty-list-as-default-method-parameter.md #}
50 | #pragma warning disable 8625
51 | {{ type_helper_code }}
52 | #pragma warning restore 8625
53 |
54 | {%- match config.global_methods_class_name %}
55 | {%- when Some(class_name) %}
56 | {{ config.access_modifier() }} static class {{ class_name }} {
57 | {%- when None %}
58 | {{ config.access_modifier() }} static class {{ ci.namespace().to_upper_camel_case() }}Methods {
59 | {%- endmatch %}
60 |
61 | {%- for func in ci.function_definitions() %}
62 | {%- include "TopLevelFunctionTemplate.cs" %}
63 | {%- endfor %}
64 | }
65 |
66 | {% import "macros.cs" as cs %}
67 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euxo pipefail
3 |
4 | cargo build --package uniffi-bindgen-cs --package uniffi-bindgen-cs-fixtures
5 |
--------------------------------------------------------------------------------
/details/1-empty-list-as-default-method-parameter.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | C# does not support initializing an empty list in a default parameter, i.e.
4 | ```
5 | void Method(List parameter = new List());
6 | ```
7 |
8 | # Proposal
9 |
10 | Use `null` to represent an empty list in default method parameters, i.e.
11 | ```
12 | void Method(List parameter = null);
13 | ```
14 |
15 | # Considerations
16 |
17 | Using `null` to represent an empty list is a code smell. Its very easy to forget
18 | to check for null, especially when using a list. However, in this case its possible
19 | to workaround this issue:
20 | - Lower: check for null, write empty list into RustBuffer instead of null
21 | - Lift: lifting is not affected by this problem, since lifting is not affected by
22 | default parameter limitations, and can easily return an empty list instead of null.
23 |
24 | # Links
25 |
26 | [stackoverflow](https://stackoverflow.com/questions/6947470/c-how-to-use-empty-liststring-as-optional-parameter)
27 |
--------------------------------------------------------------------------------
/docker.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euxo pipefail
3 |
4 | docker run \
5 | -ti --rm \
6 | --volume $PWD:/mounted_workdir \
7 | --workdir /mounted_workdir \
8 | ghcr.io/nordsecurity/uniffi-bindgen-cs-test-runner:v0.1.0 bash
9 |
--------------------------------------------------------------------------------
/docs/CONFIGURATION.md:
--------------------------------------------------------------------------------
1 | # Configuration options
2 |
3 | It's possible to configure some settings by passing `--config` argument to the generator. All
4 | configuration keys are defined in `bindings.csharp` section.
5 | ```bash
6 | uniffi-bindgen-cs path/to/definitions.udl --config path/to/uniffi.toml
7 | ```
8 |
9 | - `cdylib_name` - override the dynamic library name linked by generated bindings, excluding `lib`
10 | prefix and `.dll` file extension. When using `--library` mode, defaults to library's name.
11 | In standalone mode this value is required, and error will be produced if its missing.
12 | ```toml
13 | # For library `libgreeter.dll`
14 | [bindings.csharp]
15 | cdylib_name = "greeter"
16 | ```
17 |
18 | - `custom_types` - properties for custom type defined in UDL with `[Custom] typedef string Url;`.
19 | ```toml
20 | # Represent URL as a C# native `Uri` class. The underlying type of URL is a string.
21 | [bindings.csharp.custom_types.Url]
22 | imports = ["System"]
23 | type_name = "Uri"
24 | into_custom = "new Uri({})"
25 | from_custom = "{}.AbsoluteUri"
26 | ```
27 |
28 | - `imports` (optional) - any imports required to satisfy this type.
29 |
30 | - `type_name` (optional) - the name to represent the type in generated bindings. Default is the
31 | type alias name from UDL, e.g. `Url`.
32 |
33 | - `into_custom` (required) - an expression to convert from the underlying type into custom type. `{}` will
34 | will be expanded into variable containing the underlying value. The expression is used in a
35 | return statement, i.e. `return ;`.
36 |
37 | - `from_custom` (required) - an expression to convert from the custom type into underlying type. `{}` will
38 | will be expanded into variable containing the custom value. The expression is used in a
39 | return statement, i.e. `return `.
40 |
41 | - `namespace` - override the `namespace ..;` declaration in generated bindings file. The default is
42 | `uniffi.{{namespace}}`, where `namespace` is the namespace from UDL file.
43 | ```toml
44 | # emits `namespace com.example.greeter;` in generated bindings file
45 | [bindings.csharp]
46 | namespace = "com.example.greeter"
47 | ```
48 |
49 | - `global_methods_class_name` - override the class name containing top level functions. The default
50 | is `{{namespace}}Methods`, where `namespace` is the namespace from UDL file.
51 | ```toml
52 | # emits `public static class LibGreeter { .. }` in generated bindings file
53 | [bindings.csharp]
54 | namespace = "LibGreeter"
55 | ```
56 |
57 | - `access_modifier` - override the default `internal` access modifier for "exported" uniffi symbols.
58 |
59 | - `null_string_to_empty` - when set to `true`, `null` strings will be converted to empty strings even if they are not optional.
60 |
--------------------------------------------------------------------------------
/docs/RELEASE.md:
--------------------------------------------------------------------------------
1 | ## Figure out next version
2 |
3 | Following [versioning rules](../README.md/#versioning), figure
4 | out the version to be made. If there were any breaking changes since last version, bump the minor
5 | component. If there weren't any breaking changes, bump the patch component.
6 |
7 | The version follows semver, and consists of `uniffi-bindgen-cs` base version, followed by
8 | upstream `uniffi-rs` version in the build metadata component (denoted as `+`). The explicit upstream
9 | `uniffi-rs` version aids the consumer to target the same upstream version when mixing multiple
10 | generators, e.g. `uniffi-bindgen-cs` and `uniffi-bindgen-go`.
11 | ```
12 | v0.6.0+v0.25.0
13 | ```
14 |
15 | ## Update version in [bindgen/Cargo.toml](../bindgen/Cargo.toml)
16 |
17 | Note that the version in [bindgen/Cargo.toml](../bindgen/Cargo.toml) is not prefixed with `v`,
18 | i.e. `0.6.0+v0.25.0` instead of `v0.6.0+v0.25.0`.
19 |
20 | ## Update version in [Cargo.lock](../Cargo.lock)
21 |
22 | Run any `cargo` command to include the new version in `Cargo.lock` file, e.g.
23 | ```
24 | cargo build
25 | ```
26 |
27 | ## Update version in [README.md](../README.md)
28 |
29 | - Update the [installation command](../README.md#how-to-install) to use the new version.
30 | - If upstream uniffi-rs version was updated, add new entry to [versioning table](../README.md#versioning).
31 |
32 | ## Update [CHANGELOG.md](../CHANGELOG.md)
33 |
34 | Inspect Git history, create a list of changes since last version.
35 | - For breaking changes, prefix the change with `**BREAKING**:`
36 | - For important changes, such as memory leak or race condition fixes, prefix the change with `**IMPORTANT**:`
37 |
38 | ## Create PR
39 |
40 | Create PR, get the PR reviewed, and merge into `main`.
41 |
42 | ## Create tag
43 |
44 | Once the PR is merged into `main`, create new tag in `main` branch.
45 |
--------------------------------------------------------------------------------
/docs/VERSION_UPGRADE.md:
--------------------------------------------------------------------------------
1 | # Uprading upstream version
2 |
3 | ## C ABI
4 |
5 | Check that C ABI still matches between C# and Rust code. This includes , ,
6 | `FfiConverter*` types (`List`, `Map`, `Duration`, etc..), FFI types generated for
7 | functions in `NamespaceLibraryTemplate.cs`.
8 |
9 | - C# structs annotated with `StructLayout`.
10 | - [`RustBuffer`](https://github.com/mozilla/uniffi-rs/blob/v0.24.3/uniffi_core/src/ffi/rustbuffer.rs#L52)
11 | - [`RustCallStatus`](https://github.com/mozilla/uniffi-rs/blob/v0.24.3/uniffi_core/src/ffi/rustcalls.rs#L53)
12 | - [`ForeignBytes`](https://github.com/mozilla/uniffi-rs/blob/v0.24.3/uniffi_core/src/ffi/foreignbytes.rs#L26)
13 |
14 | - C# delegates annotated with `UnmanagedFunctionPointer`.
15 | - [`ForeignCallback`](https://github.com/mozilla/uniffi-rs/blob/v0.24.3/uniffi_core/src/ffi/foreigncallbacks.rs#L143)
16 |
17 | - `FfiConverter*` types.
18 | - [`CallbackInterfaceRuntime.cs`](https://github.com/mozilla/uniffi-rs/blob/v0.24.3/uniffi_core/src/lib.rs#L476)
19 | - [`DurationHelper.cs`](https://github.com/mozilla/uniffi-rs/blob/v0.24.3/uniffi_core/src/ffi_converter_impls.rs#L298)
20 | - [`EnumTemplate.cs`](https://github.com/mozilla/uniffi-rs/blob/v0.24.3/uniffi_macros/src/enum_.rs#L78)
21 | - [`ErrorTemplate.cs`](https://github.com/mozilla/uniffi-rs/blob/v0.24.3/uniffi_macros/src/error.rs#L67)
22 | - [`MapTemplate.cs`](https://github.com/mozilla/uniffi-rs/blob/v0.24.3/uniffi_core/src/ffi_converter_impls.rs#L395)
23 | - [`OptionalTemplate.cs`](https://github.com/mozilla/uniffi-rs/blob/v0.24.3/uniffi_core/src/ffi_converter_impls.rs#L324)
24 | - [`SequenceTemplate.cs`](https://github.com/mozilla/uniffi-rs/blob/v0.24.3/uniffi_core/src/ffi_converter_impls.rs#L360)
25 | - [`TimestampHelper.cs`](https://github.com/mozilla/uniffi-rs/blob/v0.24.3/uniffi_core/src/ffi_converter_impls.rs#L252)
26 |
27 | - FFI types generated for functions in [`NamespaceLibraryTemplate.cs`](https://github.com/mozilla/uniffi-rs/blob/v0.24.3/uniffi_bindgen/src/scaffolding/mod.rs#L67).
28 |
--------------------------------------------------------------------------------
/dotnet-tests/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 |
8 | # License header
9 | file_header_template = This Source Code Form is subject to the terms of the Mozilla Public\nLicense, v. 2.0. If a copy of the MPL was not distributed with this\nfile, You can obtain one at http://mozilla.org/MPL/2.0/.
10 |
11 | # CSharpier: https://csharpier.com/docs/Configuration
12 | ## Non-configurable behaviors
13 | charset = utf-8
14 | insert_final_newline = true
15 | trim_trailing_whitespace = true
16 |
17 | ## Configurable behaviors
18 | end_of_line = lf
19 | indent_style = space
20 | indent_size = 4
21 | max_line_length = 120
22 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/CustomTestFramework.cs:
--------------------------------------------------------------------------------
1 | // Print when tests start/end.
2 | // Xunit is quite inconvenient for hanging tests.. It doesn't come with an option to print a message before test starts.
3 | // Should we switch to another test framework??
4 | //
5 | // This code is ripped from:
6 | // https://andrewlock.net/tracking-down-a-hanging-xunit-test-in-ci-building-a-custom-test-framework/
7 |
8 | using System.Collections.Generic;
9 | using System.Linq;
10 | using System.Reflection;
11 | using System.Threading.Tasks;
12 | using System.Threading;
13 | using System;
14 | using Xunit.Abstractions;
15 | using Xunit.Sdk;
16 |
17 | [assembly: Xunit.TestFramework("CustomTestFramework", "UniffiCS.BindingTests")]
18 |
19 | public class CustomTestFramework : XunitTestFramework
20 | {
21 | public CustomTestFramework(IMessageSink messageSink)
22 | : base(messageSink)
23 | {
24 | messageSink.OnMessage(new DiagnosticMessage("Using CustomTestFramework"));
25 | }
26 |
27 | protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName)
28 | => new CustomExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink);
29 |
30 | private class CustomExecutor : XunitTestFrameworkExecutor
31 | {
32 | public CustomExecutor(AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider, IMessageSink diagnosticMessageSink)
33 | : base(assemblyName, sourceInformationProvider, diagnosticMessageSink)
34 | {
35 | }
36 |
37 | protected override async void RunTestCases(IEnumerable testCases, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions)
38 | {
39 | using var assemblyRunner = new CustomAssemblyRunner(TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, executionOptions);
40 | await assemblyRunner.RunAsync();
41 | }
42 | }
43 |
44 | private class CustomAssemblyRunner : XunitTestAssemblyRunner
45 | {
46 | public CustomAssemblyRunner(ITestAssembly testAssembly, IEnumerable testCases, IMessageSink diagnosticMessageSink, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions)
47 | : base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions)
48 | {
49 | }
50 |
51 | protected override Task RunTestCollectionAsync(IMessageBus messageBus, ITestCollection testCollection, IEnumerable testCases, CancellationTokenSource cancellationTokenSource)
52 | => new CustomTestCollectionRunner(testCollection, testCases, DiagnosticMessageSink, messageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), cancellationTokenSource).RunAsync();
53 | }
54 |
55 | private class CustomTestCollectionRunner : XunitTestCollectionRunner
56 | {
57 | public CustomTestCollectionRunner(ITestCollection testCollection, IEnumerable testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ITestCaseOrderer testCaseOrderer, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource)
58 | : base(testCollection, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource)
59 | {
60 | }
61 |
62 | protected override Task RunTestClassAsync(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable testCases)
63 | => new CustomTestClassRunner(testClass, @class, testCases, DiagnosticMessageSink, MessageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), CancellationTokenSource, CollectionFixtureMappings)
64 | .RunAsync();
65 | }
66 |
67 | private class CustomTestClassRunner : XunitTestClassRunner
68 | {
69 | public CustomTestClassRunner(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ITestCaseOrderer testCaseOrderer, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource, IDictionary collectionFixtureMappings)
70 | : base(testClass, @class, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource, collectionFixtureMappings)
71 | {
72 | }
73 |
74 | protected override Task RunTestMethodAsync(ITestMethod testMethod, IReflectionMethodInfo method, IEnumerable testCases, object[] constructorArguments)
75 | => new CustomTestMethodRunner(testMethod, this.Class, method, testCases, this.DiagnosticMessageSink, this.MessageBus, new ExceptionAggregator(this.Aggregator), this.CancellationTokenSource, constructorArguments)
76 | .RunAsync();
77 | }
78 |
79 | private class CustomTestMethodRunner : XunitTestMethodRunner
80 | {
81 | private readonly IMessageSink _diagnosticMessageSink;
82 |
83 | public CustomTestMethodRunner(ITestMethod testMethod, IReflectionTypeInfo @class, IReflectionMethodInfo method, IEnumerable testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource, object[] constructorArguments)
84 | : base(testMethod, @class, method, testCases, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource, constructorArguments)
85 | {
86 | _diagnosticMessageSink = diagnosticMessageSink;
87 | }
88 |
89 | protected override async Task RunTestCaseAsync(IXunitTestCase testCase)
90 | {
91 | var parameters = string.Empty;
92 |
93 | if (testCase.TestMethodArguments != null)
94 | {
95 | parameters = string.Join(", ", testCase.TestMethodArguments.Select(a => a?.ToString() ?? "null"));
96 | }
97 |
98 | var test = $"{TestMethod.TestClass.Class.Name}.{TestMethod.Method.Name}({parameters})";
99 |
100 | _diagnosticMessageSink.OnMessage(new DiagnosticMessage($"STARTED: {test}"));
101 |
102 | try
103 | {
104 | var result = await base.RunTestCaseAsync(testCase);
105 |
106 | var status = result.Failed > 0
107 | ? "FAILURE"
108 | : (result.Skipped > 0 ? "SKIPPED" : "SUCCESS");
109 |
110 | _diagnosticMessageSink.OnMessage(new DiagnosticMessage($"{status}: {test} ({result.Time}s)"));
111 |
112 | return result;
113 | }
114 | catch (Exception ex)
115 | {
116 | _diagnosticMessageSink.OnMessage(new DiagnosticMessage($"ERROR: {test} ({ex.Message})"));
117 | throw;
118 | }
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/OptionalParameterTests.cs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | using uniffi.uniffi_cs_optional_parameters;
6 | using static uniffi.uniffi_cs_optional_parameters.UniffiCsOptionalParametersMethods;
7 |
8 | namespace UniffiCS.BindingTests;
9 |
10 | public class OptionalParameterTests
11 | {
12 | [Fact]
13 | public void OptionalParameter_CanBeOmitted()
14 | {
15 | var person = new Person(isSpecial: false);
16 | string message = Hello(person);
17 | Assert.Equal("Hello stranger!", message);
18 | }
19 |
20 | [Fact]
21 | public void OptionalParameter_CanBeSpecified()
22 | {
23 | var person = new Person(name: "John Connor", isSpecial: false);
24 | string message = Hello(person);
25 | Assert.Equal("Hello John Connor!", message);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestArithmetic.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using uniffi.arithmetic;
6 | using ArithmeticException = uniffi.arithmetic.ArithmeticException;
7 |
8 | namespace UniffiCS.BindingTests;
9 |
10 | public class TestArithmetic
11 | {
12 | [Fact]
13 | public void ArithmeticWorks()
14 | {
15 | Assert.Equal(6ul, ArithmeticMethods.Add(2, 4));
16 | Assert.Equal(12ul, ArithmeticMethods.Add(4, 8));
17 |
18 | Assert.Throws(() => ArithmeticMethods.Sub(0, 2));
19 |
20 | Assert.Equal(2ul, ArithmeticMethods.Sub(4, 2));
21 | Assert.Equal(4ul, ArithmeticMethods.Sub(8, 4));
22 |
23 | Assert.Equal(2ul, ArithmeticMethods.Div(8, 4));
24 |
25 | Assert.Throws(() => ArithmeticMethods.Div(8, 0));
26 |
27 | Assert.True(ArithmeticMethods.Equal(2, 2));
28 | Assert.True(ArithmeticMethods.Equal(4, 4));
29 |
30 | Assert.False(ArithmeticMethods.Equal(2, 4));
31 | Assert.False(ArithmeticMethods.Equal(4, 8));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestCallbacks.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using uniffi.callbacks;
7 |
8 | namespace UniffiCS.BindingTests;
9 |
10 | class SomeOtherError : Exception { }
11 |
12 | class CallAnswererImpl : CallAnswerer
13 | {
14 | public String mode;
15 |
16 | public CallAnswererImpl(String mode)
17 | {
18 | this.mode = mode;
19 | }
20 |
21 | public String Answer()
22 | {
23 | if (mode == "normal")
24 | {
25 | return "Bonjour";
26 | }
27 | else if (mode == "busy")
28 | {
29 | throw new TelephoneException.Busy("I'm busy");
30 | }
31 | else
32 | {
33 | throw new SomeOtherError();
34 | }
35 | }
36 | }
37 |
38 | class OurSim : SimCard {
39 | public String name;
40 |
41 | public OurSim(String name) {
42 | this.name = name;
43 | }
44 |
45 | public String Name() {
46 | return name;
47 | }
48 | }
49 |
50 | public class TestCallbacks
51 | {
52 | [Fact]
53 | public void CallbackWorks()
54 | {
55 | var rust_sim = CallbacksMethods.GetSimCards()[0];
56 | var our_sim = new OurSim("C#");
57 | var telephone = new Telephone();
58 |
59 | Assert.Equal("Bonjour", telephone.Call(rust_sim, new CallAnswererImpl("normal")));
60 | Assert.Equal("C# est bon marché", telephone.Call(our_sim, new CallAnswererImpl("normal")));
61 |
62 | Assert.Throws(() => telephone.Call(rust_sim, new CallAnswererImpl("busy")));
63 |
64 | Assert.Throws(
65 | () => telephone.Call(rust_sim, new CallAnswererImpl("something-else"))
66 | );
67 | }
68 |
69 | [Fact]
70 | public void CallbackRegistrationIsNotAffectedByGC()
71 | {
72 | // See `static ForeignCallback INSTANCE` at `templates/CallbackInterfaceTemplate.cs`
73 | var sims = CallbacksMethods.GetSimCards();
74 |
75 | var callback = new CallAnswererImpl("normal");
76 | var telephone = new Telephone();
77 |
78 | // At this point, lib is holding references to managed delegates, so bindings have to
79 | // make sure that the delegate is not garbage collected.
80 | System.GC.Collect();
81 |
82 | telephone.Call(sims[0], callback);
83 | }
84 |
85 | [Fact]
86 | public void CallbackReferenceIsDropped()
87 | {
88 | var telephone = new Telephone();
89 | var sims = CallbacksMethods.GetSimCards();
90 |
91 | var weak_callback = CallInItsOwnScope(() =>
92 | {
93 | var callback = new CallAnswererImpl("normal");
94 | telephone.Call(sims[0], callback);
95 | return new WeakReference(callback);
96 | });
97 |
98 | System.GC.Collect();
99 | Assert.False(weak_callback.IsAlive);
100 | }
101 |
102 | // https://stackoverflow.com/questions/15205891/garbage-collection-should-have-removed-object-but-weakreference-isalive-still-re
103 | private T CallInItsOwnScope(Func getter)
104 | {
105 | return getter();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestCallbacksFixture.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using uniffi.fixture_callbacks;
9 |
10 | namespace UniffiCS.BindingTests;
11 |
12 | class CsharpGetters : ForeignGetters
13 | {
14 | public Boolean GetBool(Boolean v, Boolean argumentTwo)
15 | {
16 | return v ^ argumentTwo;
17 | }
18 |
19 | public String GetString(String v, Boolean arg2)
20 | {
21 | if (v == "bad-argument")
22 | {
23 | throw new SimpleException.BadArgument("bad argument");
24 | }
25 | if (v == "unexpected-error")
26 | {
27 | throw new Exception("something failed");
28 | }
29 | return arg2 ? "1234567890123" : v;
30 | }
31 |
32 | public String? GetOption(String? v, Boolean arg2)
33 | {
34 | if (v == "bad-argument")
35 | {
36 | throw new ComplexException.ReallyBadArgument(20);
37 | }
38 | if (v == "unexpected-error")
39 | {
40 | throw new ComplexException.UnexpectedErrorWithReason("something failed");
41 | }
42 | return arg2 && v != null ? v.ToUpper() : v;
43 | }
44 |
45 | public List GetList(List v, Boolean arg2)
46 | {
47 | return arg2 ? v : new List();
48 | }
49 |
50 | public void GetNothing(String v)
51 | {
52 | if (v == "bad-argument")
53 | {
54 | throw new SimpleException.BadArgument("bad argument");
55 | }
56 | if (v == "unexpected-error")
57 | {
58 | throw new Exception("something failed");
59 | }
60 | }
61 | }
62 |
63 | class CsharpStringifier : StoredForeignStringifier
64 | {
65 | public String FromSimpleType(Int32 value)
66 | {
67 | return "C#: " + value.ToString();
68 | }
69 |
70 | public String FromComplexType(List? values)
71 | {
72 | if (values == null)
73 | {
74 | return "C#: null";
75 | }
76 | else
77 | {
78 | var stringValues = values.Select(number =>
79 | {
80 | return number == null ? "null" : number.ToString();
81 | });
82 | return "C#: " + string.Join(" ", stringValues);
83 | }
84 | }
85 | }
86 |
87 | public class TestCallbacksFixture
88 | {
89 | [Fact]
90 | public void CallbackRoundTripValues()
91 | {
92 | var callback = new CsharpGetters();
93 | using (var rustGetters = new RustGetters())
94 | {
95 | foreach (var v in new List { true, false })
96 | {
97 | var flag = true;
98 | Assert.Equal(callback.GetBool(v, flag), rustGetters.GetBool(callback, v, flag));
99 | }
100 |
101 | foreach (
102 | var v in new List>
103 | {
104 | new List { 1, 2 },
105 | new List { 0, 1 }
106 | }
107 | )
108 | {
109 | var flag = true;
110 | Assert.Equal(callback.GetList(v, flag), rustGetters.GetList(callback, v, flag));
111 | }
112 |
113 | foreach (var v in new List { "Hello", "world" })
114 | {
115 | var flag = true;
116 | Assert.Equal(callback.GetString(v, flag), rustGetters.GetString(callback, v, flag));
117 | }
118 |
119 | foreach (var v in new List { "Some", null })
120 | {
121 | var flag = true;
122 | Assert.Equal(callback.GetOption(v, flag), rustGetters.GetOption(callback, v, flag));
123 | }
124 |
125 | Assert.Equal("TestString", rustGetters.GetStringOptionalCallback(callback, "TestString", false));
126 | Assert.Null(rustGetters.GetStringOptionalCallback(null, "TestString", false));
127 | }
128 | }
129 |
130 | [Fact]
131 | public void CallbackRoundTripErrors()
132 | {
133 | var callback = new CsharpGetters();
134 | using (var rustGetters = new RustGetters())
135 | {
136 | Assert.Throws(() => rustGetters.GetString(callback, "bad-argument", true));
137 | Assert.Throws(
138 | () => rustGetters.GetString(callback, "unexpected-error", true)
139 | );
140 |
141 | var reallyBadArgument = Assert.Throws(
142 | () => rustGetters.GetOption(callback, "bad-argument", true)
143 | );
144 | Assert.Equal(20, reallyBadArgument.code);
145 |
146 | var unexpectedException = Assert.Throws(
147 | () => rustGetters.GetOption(callback, "unexpected-error", true)
148 | );
149 | Assert.Equal(new Exception("something failed").Message, unexpectedException.@reason);
150 | }
151 | }
152 |
153 | [Fact]
154 | public void CallbackMayBeStoredInObject()
155 | {
156 | var stringifier = new CsharpStringifier();
157 | using (var rustStringifier = new RustStringifier(stringifier))
158 | {
159 | foreach (var v in new List { 1, 2 })
160 | {
161 | Assert.Equal(stringifier.FromSimpleType(v), rustStringifier.FromSimpleType(v));
162 | }
163 | }
164 | }
165 |
166 | [Fact]
167 | public void VoidCallbackExceptions()
168 | {
169 | var callback = new CsharpGetters();
170 | using (var rustGetters = new RustGetters())
171 | {
172 | // no exception
173 | rustGetters.GetNothing(callback, "foo");
174 | Assert.Throws(() => rustGetters.GetNothing(callback, "bad-argument"));
175 | Assert.Throws(
176 | () => rustGetters.GetNothing(callback, "unexpected-error")
177 | );
178 | }
179 | }
180 |
181 | [Fact]
182 | public void ShortLivedCallbackDoesNotInvalidateLongerLivedCallback()
183 | {
184 | var stringifier = new CsharpStringifier();
185 | using (var rustStringifier1 = new RustStringifier(stringifier))
186 | {
187 | using (var rustStringifier2 = new RustStringifier(stringifier))
188 | {
189 | Assert.Equal("C#: 123", rustStringifier2.FromSimpleType(123));
190 | }
191 | // `stringifier` must remain valid after `rustStringifier2` drops the reference
192 |
193 | Assert.Equal("C#: 123", rustStringifier1.FromSimpleType(123));
194 | }
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestChronological.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Threading;
7 | using uniffi.chronological;
8 |
9 | namespace UniffiCS.BindingTests;
10 |
11 | public class TestChronological
12 | {
13 | static DateTime EpochSecond(int seconds, int nanoseconds)
14 | {
15 | return DateTime.UnixEpoch.AddSeconds(seconds).AddTicks(nanoseconds / 100);
16 | }
17 |
18 | static TimeSpan TimeSpanSecond(int seconds, int nanoseconds)
19 | {
20 | return new TimeSpan(seconds * TimeSpan.TicksPerSecond + nanoseconds / 100);
21 | }
22 |
23 | [Fact]
24 | public void ChronologicalWorks()
25 | {
26 | // Test passing timestamp and duration while returning timestamp
27 | Assert.Equal(EpochSecond(101, 200), ChronologicalMethods.Add(EpochSecond(100, 100), TimeSpanSecond(1, 100)));
28 |
29 | // Test passing timestamp while returning duration
30 | Assert.Equal(TimeSpanSecond(1, 100), ChronologicalMethods.Diff(EpochSecond(101, 200), EpochSecond(100, 100)));
31 |
32 | Assert.Throws(() =>
33 | {
34 | ChronologicalMethods.Diff(EpochSecond(100, 0), EpochSecond(101, 0));
35 | });
36 |
37 | // TODO: Throws ChronologicalException.TimeOverflow on Windows, but System.ArgumentOutOfRangeException on Mac and Linux
38 | // Assert.Throws(() =>
39 | // {
40 | // ChronologicalMethods.Add(DateTime.MaxValue, TimeSpan.MaxValue);
41 | // });
42 | }
43 |
44 | [Fact]
45 | public void DateTimeMinMax()
46 | {
47 | Assert.Equal(DateTime.MinValue, ChronologicalMethods.ReturnTimestamp(DateTime.MinValue));
48 |
49 | Assert.Equal(DateTime.MaxValue, ChronologicalMethods.ReturnTimestamp(DateTime.MaxValue));
50 | }
51 |
52 | [Fact]
53 | public void TimeSpanMax()
54 | {
55 | // Rust does not allow negative timespan, so only maximum value is tested.
56 |
57 | Assert.Equal(TimeSpan.MaxValue, ChronologicalMethods.ReturnDuration(TimeSpan.MaxValue));
58 | }
59 |
60 | [Fact]
61 | public void PreEpochTimestampsSerializeCorrectly()
62 | {
63 | Assert.Equal(
64 | "1969-12-12T00:00:00.000000000Z",
65 | ChronologicalMethods.ToStringTimestamp(DateTime.Parse("1969-12-12T00:00:00.000000000Z").ToUniversalTime())
66 | );
67 |
68 | // [-999_999_999; 0) is unrepresentable
69 | // https://github.com/mozilla/uniffi-rs/issues/1433
70 | // Assert.Equal(
71 | // "1969-12-31T23:59:59.999999900Z",
72 | // ChronologicalMethods.ToStringTimestamp(
73 | // DateTime.Parse("1969-12-31T23:59:59.999999900Z")));
74 |
75 | Assert.Equal(
76 | "1969-12-31T23:59:58.999999900Z",
77 | ChronologicalMethods.ToStringTimestamp(DateTime.Parse("1969-12-31T23:59:58.999999900Z").ToUniversalTime())
78 | );
79 |
80 | Assert.Equal(
81 | DateTime.Parse("1955-11-05T00:06:01.283000200Z"),
82 | ChronologicalMethods.Add(DateTime.Parse("1955-11-05T00:06:00.283000100Z"), TimeSpanSecond(1, 100))
83 | );
84 | }
85 |
86 | [Fact]
87 | public void TestDateTimeWorksLikeRustSystemTime()
88 | {
89 | // Sleep inbetween to make sure that the clock has enough resolution
90 | var before = DateTime.UtcNow;
91 | Thread.Sleep(1);
92 | var now = ChronologicalMethods.Now();
93 | Thread.Sleep(1);
94 | var after = DateTime.UtcNow;
95 | Assert.Equal(-1, before.CompareTo(now));
96 | Assert.Equal(1, after.CompareTo(now));
97 | }
98 |
99 | [Fact]
100 | public void DateTimeAndTimeSpanOptionals()
101 | {
102 | Assert.True(ChronologicalMethods.Optional(DateTime.MaxValue, TimeSpanSecond(0, 0)));
103 | Assert.False(ChronologicalMethods.Optional(null, TimeSpanSecond(0, 0)));
104 | Assert.False(ChronologicalMethods.Optional(DateTime.MaxValue, null));
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestCustomTypes.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using uniffi.custom_types;
7 |
8 | namespace UniffiCS.BindingTests;
9 |
10 | public class TestCustomTypes
11 | {
12 | [Fact]
13 | public void CustomTypesWork()
14 | {
15 | var demo = CustomTypesMethods.GetCustomTypesDemo(null);
16 |
17 | Assert.Equal("http://example.com/", demo.url);
18 | Assert.Equal(123, demo.handle);
19 |
20 | // Change some data and ensure that the round-trip works
21 | demo = demo with
22 | {
23 | url = "http://new.example.com/"
24 | };
25 | demo = demo with { handle = 456 };
26 | Assert.Equal(demo, CustomTypesMethods.GetCustomTypesDemo(demo));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestCustomTypesBuiltin.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using uniffi.custom_types_builtin;
8 |
9 | namespace UniffiCS.BindingTests;
10 |
11 | public class TestCustomTypesBuiltin
12 | {
13 | [Fact]
14 | public void CustomTypesWork()
15 | {
16 | var demo = CustomTypesBuiltinMethods.GetCustomTypesBuiltin();
17 | AssertDemo(demo);
18 |
19 | demo = CustomTypesBuiltinMethods.ReturnCustomTypesBuiltin(demo);
20 | AssertDemo(demo);
21 | }
22 |
23 | void AssertDemo(CustomTypesBuiltin demo)
24 | {
25 | Assert.Equal("Hello, world!", demo.@string);
26 | Assert.Equal(new List { "Hello, world!" }, demo.array);
27 | Assert.Equal(new Dictionary { { "hello", "world" } }, demo.table);
28 | Assert.True(demo.boolean);
29 | Assert.Equal(SByte.MaxValue, demo.int8);
30 | Assert.Equal(Int16.MaxValue, demo.int16);
31 | Assert.Equal(Int32.MaxValue, demo.int32);
32 | Assert.Equal(Int64.MaxValue, demo.int64);
33 | Assert.Equal(Byte.MaxValue, demo.uint8);
34 | Assert.Equal(UInt16.MaxValue, demo.uint16);
35 | Assert.Equal(UInt32.MaxValue, demo.uint32);
36 | Assert.Equal(UInt64.MaxValue, demo.uint64);
37 | Assert.Equal(Single.MaxValue, demo.@float);
38 | Assert.Equal(Double.MaxValue, demo.@double);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestDisposable.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using uniffi.disposable;
6 |
7 | namespace UniffiCS.BindingTests;
8 |
9 | public class TestDisposable
10 | {
11 | [Fact]
12 | public void ObjectDecrementsLiveCount()
13 | {
14 | using (var resource = DisposableMethods.GetResource())
15 | {
16 | Assert.Equal(1, DisposableMethods.GetLiveCount());
17 | }
18 | Assert.Equal(0, DisposableMethods.GetLiveCount());
19 | }
20 |
21 | [Fact]
22 | public void MapDecrementsLiveCount()
23 | {
24 | using (var journal = DisposableMethods.GetResourceJournalMap())
25 | {
26 | Assert.Equal(2, DisposableMethods.GetLiveCount());
27 | }
28 | Assert.Equal(0, DisposableMethods.GetLiveCount());
29 | }
30 |
31 | [Fact]
32 | public void ListDecrementsLiveCount()
33 | {
34 | using (var journal = DisposableMethods.GetResourceJournalList())
35 | {
36 | Assert.Equal(2, DisposableMethods.GetLiveCount());
37 | }
38 | Assert.Equal(0, DisposableMethods.GetLiveCount());
39 | }
40 |
41 | [Fact]
42 | public void MapListDecrementsLiveCount()
43 | {
44 | using (var journal = DisposableMethods.GetResourceJournalMapList())
45 | {
46 | Assert.Equal(2, DisposableMethods.GetLiveCount());
47 | }
48 | Assert.Equal(0, DisposableMethods.GetLiveCount());
49 | }
50 |
51 | [Fact]
52 | public void EnumDecrementsLiveCount()
53 | {
54 | using (var maybe_journal = DisposableMethods.GetMaybeResourceJournal())
55 | {
56 | Assert.Equal(2, DisposableMethods.GetLiveCount());
57 | }
58 | Assert.Equal(0, DisposableMethods.GetLiveCount());
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestDocstring.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Text.RegularExpressions;
10 | using uniffi.uniffi_docstring;
11 |
12 | namespace UniffiCS.BindingTests;
13 |
14 | public class TestDocstring
15 | {
16 | class CallbackImpls : CallbackTest
17 | {
18 | public void Test() { }
19 | }
20 |
21 | [Fact]
22 | public void DocstringWorks()
23 | {
24 | // Checking to make sure the symbols are reachable,
25 | // not accidentally commented out by docstrings
26 |
27 | UniffiDocstringMethods.Test();
28 |
29 | _ = EnumTest.One;
30 | _ = EnumTest.Two;
31 |
32 | _ = new AssociatedEnumTest.Test(0);
33 | _ = new AssociatedEnumTest.Test2(0);
34 |
35 | _ = new ErrorTest.One("hello");
36 | _ = new ErrorTest.Two("hello");
37 |
38 | _ = new AssociatedErrorTest.Test(0);
39 | _ = new AssociatedErrorTest.Test2(0);
40 |
41 | var obj1 = new ObjectTest();
42 | var obj2 = ObjectTest.NewAlternate();
43 | obj2.Test();
44 |
45 | var record = new RecordTest(123);
46 | _ = record.test;
47 |
48 | CallbackTest callback = new CallbackImpls();
49 | callback.Test();
50 | }
51 |
52 | [Fact]
53 | public void DocstringsAppearInBindings()
54 | {
55 | // Hacky way to find project directory based on working directory..
56 | string rootDirectory = Directory.GetCurrentDirectory() + "../../../../../../";
57 | string bindingsSource = File.ReadAllText(rootDirectory + "dotnet-tests/UniffiCS/gen/uniffi_docstring.cs");
58 |
59 | List expected = new List {
60 | "// ",
61 | "// ",
62 | "// ",
63 | "// ",
64 | "// ",
65 | "// ",
66 | "// ",
67 | "// ",
68 | "// ",
69 | "// ",
70 | "// ",
71 | "// ",
72 | "// ",
73 | "// ",
74 | "// ",
75 | "// ",
76 | "// ",
77 | "// ",
78 | "// ",
79 | "// ",
80 | "// ",
81 | "// ",
82 | "// ",
83 | "// "
84 | };
85 |
86 | List excludedPrefixes = new List {
87 | "// ",
88 | "// ",
89 | "// ",
90 | "// ",
91 | "// ",
93 | "// missingDocstrings = expected.Where(e => !bindingsSource.Contains(e)).ToList();
97 | Assert.Empty(missingDocstrings);
98 |
99 | string pattern = @"// (<[^>]*>)";
100 | List unexpectedDocstrings = Regex.Matches(bindingsSource, pattern)
101 | .Select(match => match.Value)
102 | .Where(match =>
103 | !expected.Contains(match) &&
104 | !excludedPrefixes.Any(prefix => match.StartsWith(prefix))
105 | )
106 | .ToList();
107 | Assert.Empty(unexpectedDocstrings);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestErrorTypes.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using uniffi.error_types;
5 |
6 | namespace UniffiCS.BindingTests;
7 |
8 |
9 | public class TestErrorTypes
10 | {
11 | [Fact]
12 | public void SimpleObjectErrorThrow()
13 | {
14 | var e = Assert.Throws(() => ErrorTypesMethods.Oops());
15 | Assert.Equal("because uniffi told me so\n\nCaused by:\n oops", e.ToString());
16 | Assert.Equal(2, e.Chain().Count);
17 | Assert.Equal("because uniffi told me so", e.Link(0));
18 | }
19 |
20 | [Fact]
21 | public void NoWrapObjectErrorThrow()
22 | {
23 | var e = Assert.Throws(() => ErrorTypesMethods.OopsNowrap());
24 | Assert.Equal("because uniffi told me so\n\nCaused by:\n oops", e.ToString());
25 | Assert.Equal(2, e.Chain().Count);
26 | Assert.Equal("because uniffi told me so", e.Link(0));
27 | }
28 |
29 | [Fact]
30 | public void SimpleObjectErrorReturn()
31 | {
32 | ErrorInterface e = ErrorTypesMethods.GetError("the error");
33 | Assert.Equal("the error", e.ToString());
34 | Assert.Equal("the error", e.Link(0));
35 | }
36 |
37 | [Fact]
38 | public void TraitObjectErrorThrow()
39 | {
40 | var e = Assert.Throws(() => ErrorTypesMethods.Toops());
41 | Assert.Equal("trait-oops", e.Msg());
42 | }
43 |
44 | [Fact]
45 | public void RichErrorThrow()
46 | {
47 | var e = Assert.Throws(() => ErrorTypesMethods.ThrowRich("oh no"));
48 | Assert.Equal("RichError: \"oh no\"", e.ToString());
49 | }
50 |
51 | [Fact]
52 | public void EnumThrow()
53 | {
54 | var e1 = Assert.Throws(() => ErrorTypesMethods.OopsEnum(0));
55 | Assert.Contains("Oops", e1.ToString());
56 |
57 | var e2 = Assert.Throws(() => ErrorTypesMethods.OopsEnum(1));
58 | Assert.Contains("Value", e2.ToString());
59 | Assert.Contains("value=value", e2.ToString());
60 |
61 | var e3 = Assert.Throws(() => ErrorTypesMethods.OopsEnum(2));
62 | Assert.Contains("IntValue", e3.ToString());
63 | Assert.Contains("value=2", e3.ToString());
64 |
65 | var e4 = Assert.Throws(() => ErrorTypesMethods.OopsEnum(3));
66 | Assert.Contains("FlatInnerException", e4.ToString());
67 | Assert.Contains("CaseA: inner", e4.ToString());
68 |
69 | var e5 = Assert.Throws(() => ErrorTypesMethods.OopsEnum(4));
70 | Assert.Contains("FlatInnerException", e5.ToString());
71 | Assert.Contains("CaseB: NonUniffiTypeValue: value", e5.ToString());
72 |
73 | var e6 = Assert.Throws(() => ErrorTypesMethods.OopsEnum(5));
74 | Assert.Contains("InnerException", e6.ToString());
75 | Assert.Contains("CaseA: @v1=inner", e6.ToString());
76 | }
77 |
78 | [Fact]
79 | public void TupleThrow()
80 | {
81 | var t1 = Assert.Throws(() => ErrorTypesMethods.OopsTuple(0));
82 | Assert.Contains("Oops: @v1=oops", t1.ToString());
83 |
84 | var t2 = Assert.Throws(() => ErrorTypesMethods.OopsTuple(1));
85 | Assert.Contains("Value: @v1=1", t2.ToString());
86 | }
87 |
88 | [Fact]
89 | public async Task AsyncThrow()
90 | {
91 | var e = await Assert.ThrowsAsync(async () => await ErrorTypesMethods.Aoops());
92 | Assert.Equal("async-oops", e.ToString());
93 | }
94 | }
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestGeometry.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using uniffi.geometry;
6 |
7 | namespace UniffiCS.BindingTests;
8 |
9 | public class TestGeometry
10 | {
11 | [Fact]
12 | public void GeometryWorks()
13 | {
14 | var ln1 = new Line(new Point(0, 0), new Point(1, 2));
15 | var ln2 = new Line(new Point(1, 1), new Point(2, 2));
16 |
17 | Assert.Equal(2, GeometryMethods.Gradient(ln1));
18 | Assert.Equal(1, GeometryMethods.Gradient(ln2));
19 |
20 | Assert.Equal(new Point(0, 0), GeometryMethods.Intersection(ln1, ln2));
21 | Assert.Null(GeometryMethods.Intersection(ln1, ln1));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestGlobalMethodsClassName.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using uniffi.global_methods_class_name;
6 |
7 | namespace UniffiCS.BindingTests;
8 |
9 | public class TestGlobalMethodsClassName
10 | {
11 | [Fact]
12 | public void GlobalMethodsClassNameWorks()
13 | {
14 | Assert.Equal("Hello, world!", LibGreeter.HelloWorld());
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestIssue110.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using uniffi.issue_110;
6 |
7 | namespace UniffiCS.BindingTests;
8 |
9 | public class ClassIssue110
10 | {
11 | [Fact]
12 | public void TestIssue110()
13 | {
14 | var @string = new Value.String("test");
15 | Assert.Equal("test", @string.value);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestIssue28.cs:
--------------------------------------------------------------------------------
1 | using uniffi.issue_28;
2 |
3 | namespace UniffiCS.BindingTests;
4 |
5 | public class ClassIssue28
6 | {
7 | [Fact]
8 | public void TestIssue28()
9 | {
10 | new AsdfObject();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestIssue60.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using uniffi.issue_60;
6 |
7 | namespace UniffiCS.BindingTests;
8 |
9 | public class ClassIssue60
10 | {
11 | [Fact]
12 | public void TestIssue60()
13 | {
14 | new Shape.Rectangle(new Rectangle(1f, 2f));
15 | new ShapeException.Rectangle(new Rectangle(1f, 2f));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestIssue75.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using uniffi.issue_75;
6 |
7 | namespace UniffiCS.BindingTests;
8 |
9 | public class ClassIssue75
10 | {
11 | [Fact]
12 | public void TestIssue75()
13 | {
14 | new EmptyDictionary();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestIssue76.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using uniffi.issue_76;
6 |
7 | namespace UniffiCS.BindingTests;
8 |
9 | public class ClassIssue76
10 | {
11 | [Fact]
12 | public void TestIssue76()
13 | {
14 | Assert.Throws(() => Issue76Methods.AlwaysError());
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestNullToEmptyString.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using uniffi.null_to_empty_string;
6 |
7 | namespace UniffiCS.BindingTests;
8 |
9 | public class TestNullToEmptyString
10 | {
11 | [Fact]
12 | public void NullToEmptyStringWorks()
13 | {
14 | Assert.Equal("hello", LibGreeter.HelloWorld("hello"));
15 | #pragma warning disable 8625 // Cannot convert null literal to non-nullable reference type
16 | Assert.Equal("", LibGreeter.HelloWorld(null));
17 | #pragma warning restore 8625
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestNumericLimits.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Runtime.InteropServices;
4 | using uniffi.stringify;
5 |
6 | namespace UniffiCS.BindingTests;
7 |
8 | public class TestNumericLimits
9 | {
10 | [Fact]
11 | public void NumericLimitsAreTheSame()
12 | {
13 | // At first, I tried to write this test by stringifying values in C# and Rust, then
14 | // comparing the stringified result. Turns out C# and Rust format floating point values
15 | // differently, so comparing the result is useless. I tried to change the formatting
16 | // settings in few ways, but I couldn't find formatting settings that would produce
17 | // exact results.
18 |
19 | var meanValue = 0x1234_5678_9123_4567;
20 |
21 | ParseTest(
22 | StringifyMethods.ParseI8,
23 | SByte.MinValue,
24 | SByte.MaxValue,
25 | (sbyte)meanValue);
26 |
27 | ParseTest(
28 | StringifyMethods.ParseI16,
29 | Int16.MinValue,
30 | Int16.MaxValue,
31 | (short)meanValue);
32 |
33 | ParseTest(
34 | StringifyMethods.ParseI32,
35 | Int32.MinValue,
36 | Int32.MaxValue,
37 | (int)meanValue);
38 |
39 | ParseTest(
40 | StringifyMethods.ParseI64,
41 | Int64.MinValue,
42 | Int64.MaxValue,
43 | (long)meanValue);
44 |
45 | ParseTest(
46 | StringifyMethods.ParseU8,
47 | Byte.MinValue,
48 | Byte.MaxValue,
49 | (byte)meanValue);
50 |
51 | ParseTest(
52 | StringifyMethods.ParseU16,
53 | UInt16.MinValue,
54 | UInt16.MaxValue,
55 | (ushort)meanValue);
56 |
57 | ParseTest(
58 | StringifyMethods.ParseU32,
59 | UInt32.MinValue,
60 | UInt32.MaxValue,
61 | (uint)meanValue);
62 |
63 | ParseTest(
64 | StringifyMethods.ParseU64,
65 | UInt64.MinValue,
66 | UInt64.MaxValue,
67 | (ulong)meanValue);
68 |
69 | ParseTest(
70 | StringifyMethods.ParseF32,
71 | Single.MinValue,
72 | Single.MaxValue,
73 | Single.Epsilon);
74 |
75 | ParseTest(
76 | StringifyMethods.ParseF64,
77 | Double.MinValue,
78 | Double.MaxValue,
79 | Double.Epsilon);
80 | }
81 |
82 | static void ParseTest(
83 | Func parseMethod,
84 | T minValue,
85 | T maxValue,
86 | T meanValue
87 | )
88 | {
89 | // Possible null reference assignment
90 | #pragma warning disable 8602
91 | #pragma warning disable 8604
92 | Assert.Equal(minValue, parseMethod(minValue.ToString()));
93 | Assert.Equal(maxValue, parseMethod(maxValue.ToString()));
94 | Assert.Equal(meanValue, parseMethod(meanValue.ToString()));
95 | #pragma warning restore 8602
96 | #pragma warning restore 8604
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestPositionalEnums.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using uniffi.uniffi_cs_positional_enums;
5 |
6 | namespace UniffiCS.BindingTests;
7 |
8 |
9 | public class TestPositionalEnums
10 | {
11 | [Fact]
12 | public void PositionalEnumsWork()
13 | {
14 | var good = new PositionalEnum.GoodVariant("Hi good");
15 | Assert.Equal("Hi good", good.v1);
16 |
17 | var nice = new PositionalEnum.NiceVariant(10, "Hi nice");
18 | Assert.Equal(10, nice.v1);
19 | Assert.Equal("Hi nice", nice.v2);
20 | }
21 | }
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestSprites.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using uniffi.sprites;
6 |
7 | namespace UniffiCS.BindingTests;
8 |
9 | public class TestSprites
10 | {
11 | [Fact]
12 | public void SpritesWork()
13 | {
14 | using (var sempty = new Sprite((Point?)null))
15 | {
16 | Assert.Equal(new Point(0, 0), sempty.GetPosition());
17 | }
18 |
19 | var s = new Sprite(new Point(0, 1));
20 | Assert.Equal(new Point(0, 1), s.GetPosition());
21 |
22 | s.MoveTo(new Point(1, 2));
23 | Assert.Equal(new Point(1, 2), s.GetPosition());
24 |
25 | s.MoveBy(new Vector(-4, 2));
26 | Assert.Equal(new Point(-3, 4), s.GetPosition());
27 |
28 | s.Dispose();
29 | Assert.Throws(() => s.MoveBy(new Vector(0, 0)));
30 |
31 | using (var srel = Sprite.NewRelativeTo(new Point(0, 1), new Vector(1, 1.5)))
32 | {
33 | Assert.Equal(new Point(1, 2.5), srel.GetPosition());
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestTodoList.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System.Collections.Generic;
6 | using uniffi.todolist;
7 |
8 | namespace UniffiCS.BindingTests;
9 |
10 | public class TestTodoList
11 | {
12 | [Fact]
13 | public void TodoListWorks()
14 | {
15 | var todo = new TodoList();
16 |
17 | Assert.Throws(() => todo.GetLast());
18 |
19 | Assert.Throws(() => TodolistMethods.CreateEntryWith(""));
20 |
21 | todo.AddItem("Write strings support");
22 | Assert.Equal("Write strings support", todo.GetLast());
23 |
24 | todo.AddItem("Write tests for strings support");
25 | Assert.Equal("Write tests for strings support", todo.GetLast());
26 |
27 | var entry = TodolistMethods.CreateEntryWith("Write bindings for strings as record members");
28 | todo.AddEntry(entry);
29 | Assert.Equal("Write bindings for strings as record members", todo.GetLast());
30 | Assert.Equal("Write bindings for strings as record members", todo.GetLastEntry().text);
31 |
32 | todo.AddItem("Test Ünicode hàndling without an entry 🤣");
33 | Assert.Equal("Test Ünicode hàndling without an entry 🤣", todo.GetLast());
34 |
35 | var entry2 = new TodoEntry("Test Ünicode hàndling in an entry 🤣");
36 | todo.AddEntry(entry2);
37 | Assert.Equal("Test Ünicode hàndling in an entry 🤣", todo.GetLastEntry().text);
38 |
39 | Assert.Equal(5, todo.GetEntries().Count);
40 |
41 | todo.AddEntries(new List() { new TodoEntry("foo"), new TodoEntry("bar") });
42 | Assert.Equal(7, todo.GetEntries().Count);
43 | Assert.Equal("bar", todo.GetLastEntry().text);
44 |
45 | todo.AddItems(new List() { "bobo", "fofo" });
46 | Assert.Equal(9, todo.GetItems().Count);
47 | Assert.Equal("bobo", todo.GetItems()[7]);
48 |
49 | Assert.Null(TodolistMethods.GetDefaultList());
50 |
51 | // https://github.com/xunit/xunit/issues/2027
52 | #pragma warning disable CS8602
53 |
54 | // Note that each individual object instance needs to be explicitly destroyed,
55 | // either by using the `.use` helper or explicitly calling its `.destroy` method.
56 | // Failure to do so will leak the underlying Rust object.
57 | using (var todo2 = new TodoList())
58 | {
59 | TodolistMethods.SetDefaultList(todo);
60 | using (var defaultList = TodolistMethods.GetDefaultList())
61 | {
62 | Assert.NotNull(defaultList);
63 | Assert.Equal(todo.GetEntries(), defaultList.GetEntries());
64 | Assert.NotEqual(todo2.GetEntries(), defaultList.GetEntries());
65 | }
66 |
67 | todo2.MakeDefault();
68 | using (var defaultList = TodolistMethods.GetDefaultList())
69 | {
70 | Assert.NotEqual(todo.GetEntries(), defaultList.GetEntries());
71 | Assert.Equal(todo2.GetEntries(), defaultList.GetEntries());
72 | }
73 |
74 | todo.AddItem("Test liveness after being demoted from default");
75 | Assert.Equal("Test liveness after being demoted from default", todo.GetLast());
76 |
77 | todo2.AddItem("Test shared state through local vs default reference");
78 | using (var defaultList = TodolistMethods.GetDefaultList())
79 | {
80 | Assert.Equal("Test shared state through local vs default reference", defaultList.GetLast());
81 | }
82 | }
83 |
84 | #pragma warning restore CS8602
85 |
86 | // Ensure the kotlin version of deinit doesn't crash, and is idempotent.
87 | todo.Dispose();
88 | todo.Dispose();
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestTraitMethod.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using uniffi.trait_methods;
8 |
9 | namespace UniffiCS.BindingTests;
10 |
11 | public class TestTraitMethods
12 | {
13 | [Fact]
14 | public void TestDisplay()
15 | {
16 | using (var methods = new TraitMethods("yo"))
17 | {
18 | Assert.Equal("TraitMethods(yo)", methods.ToString());
19 | }
20 | }
21 |
22 | [Fact]
23 | public void TestEq()
24 | {
25 | using (var methods = new TraitMethods("yo"))
26 | {
27 | // Values are equal if input is the same
28 | Assert.Equal(methods, new TraitMethods("yo"));
29 | Assert.NotEqual(methods, new TraitMethods("yoyo"));
30 |
31 | // Values are not referentially equal
32 | Assert.False(methods == new TraitMethods("yo"));
33 | }
34 | }
35 |
36 | [Fact]
37 | public void TestEqNull()
38 | {
39 | TraitMethods? t1 = null;
40 | TraitMethods? t2 = null;
41 | Assert.True(Object.Equals(t1, t2));
42 |
43 | t1 = new TraitMethods("yo");
44 | Assert.False(Object.Equals(t1, t2));
45 |
46 | Assert.False(new TraitMethods("yo") == null);
47 | Assert.True(new TraitMethods("yo") != null);
48 | }
49 |
50 | [Fact]
51 | public void TestEqContains()
52 | {
53 | var tm = new TraitMethods("yo");
54 | var list = new List
55 | {
56 | tm
57 | };
58 |
59 | Assert.Contains(tm, list);
60 | Assert.Contains(new TraitMethods("yo"), list);
61 | Assert.DoesNotContain(null, list);
62 | Assert.DoesNotContain(new TraitMethods("yoyo"), list);
63 | }
64 |
65 | [Fact]
66 | public void TestHash()
67 | {
68 | using (var methods = new TraitMethods("yo"))
69 | {
70 | Assert.Equal(methods.GetHashCode(), new TraitMethods("yo").GetHashCode());
71 | Assert.NotEqual(methods.GetHashCode(), new TraitMethods("yoyo").GetHashCode());
72 | }
73 | }
74 | }
75 |
76 | public class TestProcMacroTraitMethods
77 | {
78 | [Fact]
79 | public void TestDisplay()
80 | {
81 | using (var methods = new ProcTraitMethods("yo"))
82 | {
83 | Assert.Equal("ProcTraitMethods(yo)", methods.ToString());
84 | }
85 | }
86 |
87 | [Fact]
88 | public void TestEq()
89 | {
90 | using (var methods = new ProcTraitMethods("yo"))
91 | {
92 | // Values are equal if input is the same
93 | Assert.Equal(methods, new ProcTraitMethods("yo"));
94 | Assert.NotEqual(methods, new ProcTraitMethods("yoyo"));
95 |
96 | // Values are not referentially equal
97 | Assert.False(methods == new ProcTraitMethods("yo"));
98 | }
99 | }
100 |
101 | [Fact]
102 | public void TestEqNull()
103 | {
104 | ProcTraitMethods? t1 = null;
105 | ProcTraitMethods? t2 = null;
106 | Assert.True(Object.Equals(t1, t2));
107 |
108 | Assert.False(new ProcTraitMethods("yo") == null);
109 | }
110 |
111 | [Fact]
112 | public void TestEqContains()
113 | {
114 | var tm = new ProcTraitMethods("yo");
115 | var list = new List
116 | {
117 | tm
118 | };
119 |
120 | Assert.Contains(tm, list);
121 | Assert.Contains(new ProcTraitMethods("yo"), list);
122 | Assert.DoesNotContain(null, list);
123 | Assert.DoesNotContain(new ProcTraitMethods("yoyo"), list);
124 | }
125 |
126 | [Fact]
127 | public void TestHash()
128 | {
129 | using (var methods = new ProcTraitMethods("yo"))
130 | {
131 | Assert.Equal(methods.GetHashCode(), new ProcTraitMethods("yo").GetHashCode());
132 | Assert.NotEqual(methods.GetHashCode(), new ProcTraitMethods("yoyo").GetHashCode());
133 | }
134 | }
135 | }
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/TestTraits.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using uniffi.traits;
4 |
5 | namespace UniffiCS.BindingTests;
6 |
7 | class OurButton : Button {
8 | public String Name() {
9 | return "c#";
10 | }
11 | }
12 |
13 | public class TestTraits
14 | {
15 | [Fact]
16 | public void TraitsWorking()
17 | {
18 | foreach (var button in TraitsMethods.GetButtons())
19 | {
20 | var name = button.Name();
21 | Assert.Contains(name, new string[] {"go", "stop"});
22 | Assert.Equal(TraitsMethods.Press(button).Name(), name);
23 | }
24 | }
25 |
26 | [Fact]
27 | public void TraitsWorkingWithForeign()
28 | {
29 | var button = new OurButton();
30 | Assert.Equal("c#", button.Name());
31 | Assert.Equal("c#", TraitsMethods.Press(button).Name());
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/UniffiCS.BindingTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 |
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 | runtime; build; native; contentfiles; analyzers; buildtransitive
15 | all
16 |
17 |
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 | all
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | PreserveNewest
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/Usings.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | global using Xunit;
6 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.BindingTests/xunit.runner.json:
--------------------------------------------------------------------------------
1 | {
2 | "diagnosticMessages": false,
3 | "parallelizeAssembly": false,
4 | "parallelizeTestCollections": false
5 | }
6 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniffiCS.BindingTests", "UniffiCS.BindingTests\UniffiCS.BindingTests.csproj", "{405D55E2-67ED-4842-8D44-8270641C1BF8}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniffiCS", "UniffiCS\UniffiCS.csproj", "{84FB93AF-E788-4D31-9FC7-0FFCD3864054}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(SolutionProperties) = preSolution
16 | HideSolutionNode = FALSE
17 | EndGlobalSection
18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
19 | {405D55E2-67ED-4842-8D44-8270641C1BF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {405D55E2-67ED-4842-8D44-8270641C1BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {405D55E2-67ED-4842-8D44-8270641C1BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {405D55E2-67ED-4842-8D44-8270641C1BF8}.Release|Any CPU.Build.0 = Release|Any CPU
23 | {84FB93AF-E788-4D31-9FC7-0FFCD3864054}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {84FB93AF-E788-4D31-9FC7-0FFCD3864054}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {84FB93AF-E788-4D31-9FC7-0FFCD3864054}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {84FB93AF-E788-4D31-9FC7-0FFCD3864054}.Release|Any CPU.Build.0 = Release|Any CPU
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/dotnet-tests/UniffiCS/UniffiCS.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net461;net6.0
4 | 10.0
5 | true
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | PreserveNewest
16 |
17 |
18 |
19 |
20 | PreserveNewest
21 |
22 |
23 |
24 |
25 | PreserveNewest
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/fixtures/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "uniffi-bindgen-cs-fixtures"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | name = "uniffi_fixtures"
8 | path = "src/lib.rs"
9 | crate-type = ["cdylib", "lib"]
10 |
11 | [dependencies]
12 | global-methods-class-name = { path = "global-methods-class-name" }
13 | issue-28 = { path = "regressions/issue-28" }
14 | issue-60 = { path = "regressions/issue-60" }
15 | issue-75 = { path = "regressions/issue-75" }
16 | issue-76 = { path = "regressions/issue-76" }
17 | issue-110 = { path = "regressions/issue-110" }
18 | null-to-empty-string = { path = "null-to-empty-string" }
19 | uniffi-cs-custom-types-builtin = { path = "custom-types-builtin" }
20 | uniffi-cs-disposable-fixture = { path = "disposable" }
21 | uniffi-cs-optional-parameters-fixture = { path = "optional-parameters" }
22 | uniffi-cs-positional-enums = { path = "positional-enums" }
23 | uniffi-cs-stringify = { path = "stringify" }
24 |
25 | # Examples
26 | uniffi-example-arithmetic = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
27 | uniffi-example-callbacks = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
28 | uniffi-example-custom-types = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
29 | uniffi-example-geometry = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
30 | uniffi-example-rondpoint = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
31 | uniffi-example-sprites = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
32 | uniffi-example-todolist = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
33 | uniffi-example-traits = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
34 |
35 | # Fixtures
36 | uniffi-fixture-callbacks = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
37 | uniffi-fixture-coverall = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
38 | uniffi-fixture-docstring = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
39 | uniffi-fixture-error-types = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
40 | uniffi-fixture-futures = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
41 | uniffi-fixture-time = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
42 | uniffi-fixture-trait-methods = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.28.3"}
--------------------------------------------------------------------------------
/fixtures/custom-types-builtin/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "uniffi-cs-custom-types-builtin"
3 | version = "1.0.0"
4 | edition = "2021"
5 | publish = false
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 | name = "uniffi_cs_custom_types_builtin"
10 |
11 | [dependencies]
12 | once_cell = "1.12"
13 | paste = "1.0"
14 | thiserror = "1.0"
15 | uniffi = { workspace = true, features = ["build"] }
16 | uniffi_macros.workspace = true
17 |
18 | [build-dependencies]
19 | uniffi = { workspace = true, features = ["bindgen-tests"] }
20 |
--------------------------------------------------------------------------------
/fixtures/custom-types-builtin/build.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | fn main() {
6 | uniffi::generate_scaffolding("./src/custom_types_builtin.udl").unwrap();
7 | }
8 |
--------------------------------------------------------------------------------
/fixtures/custom-types-builtin/src/custom_types_builtin.udl:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | // Ensure that builtin types can be used as the underlying type in custom types.
6 |
7 | [Custom]
8 | typedef string MyString;
9 |
10 | // using sequence or record as the underlying type produces broken code
11 |
12 | [Custom]
13 | typedef sequence Array;
14 |
15 | [Custom]
16 | typedef record Table;
17 |
18 | [Custom]
19 | typedef boolean Boolean;
20 |
21 | [Custom]
22 | typedef i8 Int8;
23 |
24 | [Custom]
25 | typedef i16 Int16;
26 |
27 | [Custom]
28 | typedef i32 Int32;
29 |
30 | [Custom]
31 | typedef i64 Int64;
32 |
33 | [Custom]
34 | typedef u8 UInt8;
35 |
36 | [Custom]
37 | typedef u16 UInt16;
38 |
39 | [Custom]
40 | typedef u32 UInt32;
41 |
42 | [Custom]
43 | typedef u64 UInt64;
44 |
45 | [Custom]
46 | typedef float Float;
47 |
48 | [Custom]
49 | typedef double Double;
50 |
51 | dictionary CustomTypesBuiltin {
52 | MyString string;
53 | Array array;
54 | Table table;
55 | Boolean boolean;
56 | Int8 int8;
57 | Int16 int16;
58 | Int32 int32;
59 | Int64 int64;
60 | UInt8 uint8;
61 | UInt16 uint16;
62 | UInt32 uint32;
63 | UInt64 uint64;
64 | Float float;
65 | Double double;
66 | };
67 |
68 | namespace custom_types_builtin {
69 | CustomTypesBuiltin get_custom_types_builtin();
70 | CustomTypesBuiltin return_custom_types_builtin(CustomTypesBuiltin custom_types);
71 | };
72 |
--------------------------------------------------------------------------------
/fixtures/custom-types-builtin/src/lib.rs:
--------------------------------------------------------------------------------
1 | use paste::paste;
2 | use std::collections::HashMap;
3 |
4 | macro_rules! define_custom_builtin_type {
5 | ($custom:ty, $underlying:ty) => {
6 | paste! {
7 | pub struct $custom(pub $underlying);
8 |
9 | impl UniffiCustomTypeConverter for $custom {
10 | type Builtin = $underlying;
11 |
12 | fn into_custom(val: Self::Builtin) -> uniffi::Result {
13 | Ok($custom(val))
14 | }
15 |
16 | fn from_custom(obj: Self) -> Self::Builtin {
17 | obj.0
18 | }
19 | }
20 | }
21 | };
22 | }
23 |
24 | define_custom_builtin_type!(MyString, String);
25 | define_custom_builtin_type!(Array, Vec);
26 | define_custom_builtin_type!(Table, HashMap);
27 | define_custom_builtin_type!(Boolean, bool);
28 | define_custom_builtin_type!(Int8, i8);
29 | define_custom_builtin_type!(Int16, i16);
30 | define_custom_builtin_type!(Int32, i32);
31 | define_custom_builtin_type!(Int64, i64);
32 | define_custom_builtin_type!(UInt8, u8);
33 | define_custom_builtin_type!(UInt16, u16);
34 | define_custom_builtin_type!(UInt32, u32);
35 | define_custom_builtin_type!(UInt64, u64);
36 | define_custom_builtin_type!(Float, f32);
37 | define_custom_builtin_type!(Double, f64);
38 |
39 | pub struct CustomTypesBuiltin {
40 | string: MyString,
41 | array: Array,
42 | table: Table,
43 | boolean: Boolean,
44 | int8: Int8,
45 | int16: Int16,
46 | int32: Int32,
47 | int64: Int64,
48 | uint8: UInt8,
49 | uint16: UInt16,
50 | uint32: UInt32,
51 | uint64: UInt64,
52 | float: Float,
53 | double: Double,
54 | }
55 |
56 | pub fn get_custom_types_builtin() -> CustomTypesBuiltin {
57 | CustomTypesBuiltin {
58 | string: MyString("Hello, world!".to_string()),
59 | array: Array(vec!["Hello, world!".to_string()]),
60 | table: Table(HashMap::from([("hello".to_string(), "world".to_string())])),
61 | boolean: Boolean(true),
62 | int8: Int8(i8::MAX),
63 | int16: Int16(i16::MAX),
64 | int32: Int32(i32::MAX),
65 | int64: Int64(i64::MAX),
66 | uint8: UInt8(u8::MAX),
67 | uint16: UInt16(u16::MAX),
68 | uint32: UInt32(u32::MAX),
69 | uint64: UInt64(u64::MAX),
70 | float: Float(f32::MAX),
71 | double: Double(f64::MAX),
72 | }
73 | }
74 |
75 | pub fn return_custom_types_builtin(custom_types: CustomTypesBuiltin) -> CustomTypesBuiltin {
76 | custom_types
77 | }
78 |
79 | uniffi::include_scaffolding!("custom_types_builtin");
80 |
--------------------------------------------------------------------------------
/fixtures/disposable/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "uniffi-cs-disposable-fixture"
3 | version = "1.0.0"
4 | edition = "2021"
5 | publish = false
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 | name = "uniffi_cs_disposable"
10 |
11 | [dependencies]
12 | once_cell = "1.12"
13 | thiserror = "1.0"
14 | uniffi = { workspace = true, features = ["build"] }
15 | uniffi_macros.workspace = true
16 |
17 | [build-dependencies]
18 | uniffi = { workspace = true, features = ["bindgen-tests"] }
19 |
--------------------------------------------------------------------------------
/fixtures/disposable/build.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | fn main() {
6 | uniffi::generate_scaffolding("./src/disposable.udl").unwrap();
7 | }
8 |
--------------------------------------------------------------------------------
/fixtures/disposable/src/disposable.udl:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | namespace disposable {
6 | i32 get_live_count();
7 | Resource get_resource();
8 | ResourceJournalList get_resource_journal_list();
9 | ResourceJournalMap get_resource_journal_map();
10 | ResourceJournalMapList get_resource_journal_map_list();
11 | MaybeResourceJournal get_maybe_resource_journal();
12 | };
13 |
14 | interface Resource {
15 | constructor();
16 | };
17 |
18 | // interfaces are not allowed in enums
19 | // https://github.com/mozilla/uniffi-rs/blob/6920e5592f9267ace433a5deb05017dca79ee0d6/uniffi_bindgen/src/interface/enum_.rs#L271
20 | // [Enum]
21 | // interface MaybeResource {
22 | // Some(Resource resource);
23 | // None();
24 | // };
25 |
26 | dictionary ResourceJournalList {
27 | sequence resources;
28 | };
29 |
30 | dictionary ResourceJournalMap {
31 | record resources;
32 | };
33 |
34 | dictionary ResourceJournalMapList {
35 | // a complex type, map-optional-list
36 | record?> resources;
37 | };
38 |
39 | [Enum]
40 | interface MaybeResourceJournal {
41 | Some(ResourceJournalList resource);
42 | None();
43 | };
44 |
--------------------------------------------------------------------------------
/fixtures/disposable/src/lib.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | use once_cell::sync::Lazy;
6 | use std::collections::HashMap;
7 | use std::sync::{Arc, RwLock};
8 |
9 | static LIVE_COUNT: Lazy> = Lazy::new(|| RwLock::new(0));
10 |
11 | #[derive(Debug, Clone)]
12 | pub struct Resource {}
13 |
14 | impl Resource {
15 | pub fn new() -> Self {
16 | *LIVE_COUNT.write().unwrap() += 1;
17 | Resource {}
18 | }
19 | }
20 |
21 | impl Drop for Resource {
22 | fn drop(&mut self) {
23 | *LIVE_COUNT.write().unwrap() -= 1;
24 | }
25 | }
26 |
27 | #[derive(Debug, Clone)]
28 | pub struct ResourceJournalList {
29 | resources: Vec>,
30 | }
31 |
32 | #[derive(Debug, Clone)]
33 | pub struct ResourceJournalMap {
34 | resources: HashMap>,
35 | }
36 |
37 | #[derive(Debug, Clone)]
38 | pub struct ResourceJournalMapList {
39 | resources: HashMap>>>,
40 | }
41 |
42 | pub enum MaybeResourceJournal {
43 | Some { resource: ResourceJournalList },
44 | None,
45 | }
46 |
47 | fn get_live_count() -> i32 {
48 | *LIVE_COUNT.read().unwrap()
49 | }
50 |
51 | fn get_resource() -> Arc {
52 | Arc::new(Resource::new())
53 | }
54 |
55 | fn get_resource_journal_list() -> ResourceJournalList {
56 | ResourceJournalList {
57 | resources: vec![get_resource(), get_resource()],
58 | }
59 | }
60 |
61 | fn get_resource_journal_map() -> ResourceJournalMap {
62 | ResourceJournalMap {
63 | resources: HashMap::from([(1, get_resource()), (2, get_resource())]),
64 | }
65 | }
66 |
67 | fn get_resource_journal_map_list() -> ResourceJournalMapList {
68 | ResourceJournalMapList {
69 | resources: HashMap::from([(1, Some(vec![get_resource(), get_resource()])), (2, None)]),
70 | }
71 | }
72 |
73 | fn get_maybe_resource_journal() -> MaybeResourceJournal {
74 | MaybeResourceJournal::Some {
75 | resource: get_resource_journal_list(),
76 | }
77 | }
78 |
79 | uniffi::include_scaffolding!("disposable");
80 |
--------------------------------------------------------------------------------
/fixtures/global-methods-class-name/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "global-methods-class-name"
3 | version = "1.0.0"
4 | edition = "2021"
5 | publish = false
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 | name = "global_methods_class_name"
10 |
11 | [dependencies]
12 | once_cell = "1.12"
13 | thiserror = "1.0"
14 | uniffi = { workspace = true, features = ["build"] }
15 | uniffi_macros.workspace = true
16 |
17 | [build-dependencies]
18 | uniffi = { workspace = true, features = ["bindgen-tests"] }
19 |
--------------------------------------------------------------------------------
/fixtures/global-methods-class-name/build.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | fn main() {
6 | uniffi::generate_scaffolding("./src/global_methods_class_name.udl").unwrap();
7 | }
8 |
--------------------------------------------------------------------------------
/fixtures/global-methods-class-name/src/global_methods_class_name.udl:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | namespace global_methods_class_name {
6 | string hello_world();
7 | };
8 |
--------------------------------------------------------------------------------
/fixtures/global-methods-class-name/src/lib.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | pub fn hello_world() -> String {
6 | "Hello, world!".to_string()
7 | }
8 |
9 | uniffi::include_scaffolding!("global_methods_class_name");
10 |
--------------------------------------------------------------------------------
/fixtures/global-methods-class-name/uniffi.toml:
--------------------------------------------------------------------------------
1 | [bindings.csharp]
2 | cdylib_name = "uniffi_fixtures"
3 | global_methods_class_name = "LibGreeter"
4 |
--------------------------------------------------------------------------------
/fixtures/null-to-empty-string/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "null-to-empty-string"
3 | version = "1.0.0"
4 | edition = "2021"
5 | publish = false
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 | name = "null_to_empty_string"
10 |
11 | [dependencies]
12 | once_cell = "1.12"
13 | thiserror = "1.0"
14 | uniffi = { workspace = true, features = ["build"] }
15 | uniffi_macros.workspace = true
16 |
17 | [build-dependencies]
18 | uniffi = { workspace = true, features = ["bindgen-tests"] }
--------------------------------------------------------------------------------
/fixtures/null-to-empty-string/build.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | fn main() {
6 | uniffi::generate_scaffolding("./src/null_to_empty_string.udl").unwrap();
7 | }
8 |
--------------------------------------------------------------------------------
/fixtures/null-to-empty-string/src/lib.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | pub fn hello_world(greet: String) -> String {
6 | greet
7 | }
8 |
9 | uniffi::include_scaffolding!("null_to_empty_string");
10 |
--------------------------------------------------------------------------------
/fixtures/null-to-empty-string/src/null_to_empty_string.udl:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | namespace null_to_empty_string {
6 | string hello_world(string greet);
7 | };
8 |
--------------------------------------------------------------------------------
/fixtures/null-to-empty-string/uniffi.toml:
--------------------------------------------------------------------------------
1 | [bindings.csharp]
2 | cdylib_name = "uniffi_fixtures"
3 | global_methods_class_name = "LibGreeter"
4 | null_string_to_empty = true
5 |
--------------------------------------------------------------------------------
/fixtures/optional-parameters/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "uniffi-cs-optional-parameters-fixture"
3 | version = "1.0.0"
4 | edition = "2021"
5 | publish = false
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 | name = "uniffi_cs_optional_parameters"
10 |
11 | [dependencies]
12 | uniffi = { workspace = true, features = ["build"] }
13 | uniffi_macros.workspace = true
14 |
15 | [build-dependencies]
16 | uniffi = { workspace = true, features = ["bindgen-tests"] }
17 |
--------------------------------------------------------------------------------
/fixtures/optional-parameters/src/lib.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | uniffi::setup_scaffolding!();
6 |
7 | #[derive(uniffi::Record)]
8 | pub struct Person {
9 | #[uniffi(default = None)]
10 | pub name: Option,
11 | pub is_special: bool,
12 | }
13 |
14 | #[uniffi::export]
15 | fn hello(person: Person) -> String {
16 | let name = person.name.unwrap_or("stranger".to_string());
17 | if person.is_special {
18 | format!("Hello {}! You are special!", name)
19 | } else {
20 | format!("Hello {}!", name)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/fixtures/positional-enums/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "uniffi-cs-positional-enums"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [lib]
9 | crate-type = ["lib", "cdylib"]
10 | name = "uniffi_cs_positional_enums"
11 |
12 | [dependencies]
13 | uniffi = { workspace = true, features = ["build"] }
14 | uniffi_macros.workspace = true
15 |
16 | [build-dependencies]
17 | uniffi = { workspace = true, features = ["bindgen-tests"] }
--------------------------------------------------------------------------------
/fixtures/positional-enums/src/lib.rs:
--------------------------------------------------------------------------------
1 | #[derive(uniffi::Enum)]
2 | pub enum PositionalEnum {
3 | GoodVariant(String),
4 | NiceVariant(i32, String),
5 | }
6 | uniffi::setup_scaffolding!();
--------------------------------------------------------------------------------
/fixtures/regressions/issue-110/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "issue-110"
3 | version = "1.0.0"
4 | edition = "2021"
5 | publish = false
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 | name = "issue_110"
10 |
11 | [dependencies]
12 | uniffi = { workspace = true, features = ["build"] }
13 | uniffi_macros.workspace = true
14 |
15 | [build-dependencies]
16 | uniffi = { workspace = true, features = ["bindgen-tests"] }
--------------------------------------------------------------------------------
/fixtures/regressions/issue-110/README.md:
--------------------------------------------------------------------------------
1 | # https://github.com/NordSecurity/uniffi-bindgen-cs/issues/110
2 |
3 | Builtin string types are generated using the builtin c# keyword.
4 |
--------------------------------------------------------------------------------
/fixtures/regressions/issue-110/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | uniffi::generate_scaffolding("src/issue-110.udl").unwrap();
3 | }
4 |
--------------------------------------------------------------------------------
/fixtures/regressions/issue-110/src/issue-110.udl:
--------------------------------------------------------------------------------
1 | namespace issue_110 {
2 |
3 | };
4 |
5 | [Enum]
6 | interface Value{
7 | Null();
8 | Bool(boolean value);
9 | Double(f64 value);
10 | I64(i64 value);
11 | Binary(bytes value);
12 | String(string value);
13 | };
--------------------------------------------------------------------------------
/fixtures/regressions/issue-110/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub enum Value {
2 | Null,
3 | Bool { value: bool },
4 | Double { value: f64 },
5 | I64 { value: i64 },
6 | Binary { value: Vec },
7 | String { value: String }
8 | }
9 |
10 | uniffi::include_scaffolding!("issue-110");
11 |
--------------------------------------------------------------------------------
/fixtures/regressions/issue-28/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "issue-28"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [lib]
9 | crate-type = ["lib", "cdylib"]
10 | name = "issue_28"
11 |
12 | [dependencies]
13 | uniffi = { workspace = true, features = ["build"] }
14 | uniffi_macros.workspace = true
15 |
16 | [build-dependencies]
17 | uniffi = { workspace = true, features = ["bindgen-tests"] }
--------------------------------------------------------------------------------
/fixtures/regressions/issue-28/README.md:
--------------------------------------------------------------------------------
1 | https://github.com/NordSecurity/uniffi-bindgen-cs/issues/28
2 |
3 | If the interface's name is like `ASDFObject`, typenames are not generated uniformly.
4 | There will be a difference between the class name `AsdfObject` and the return type of the ffi constructor `ASDFObject`.
--------------------------------------------------------------------------------
/fixtures/regressions/issue-28/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | uniffi::generate_scaffolding("src/issue-28.udl").unwrap();
3 | }
4 |
--------------------------------------------------------------------------------
/fixtures/regressions/issue-28/src/issue-28.udl:
--------------------------------------------------------------------------------
1 | namespace issue_28 {
2 |
3 | };
4 |
5 | interface ASDFObject {
6 | constructor();
7 | };
--------------------------------------------------------------------------------
/fixtures/regressions/issue-28/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub struct ASDFObject {}
2 |
3 | impl ASDFObject {
4 | fn new() -> ASDFObject {
5 | ASDFObject {}
6 | }
7 | }
8 |
9 | uniffi::include_scaffolding!("issue-28");
10 |
--------------------------------------------------------------------------------
/fixtures/regressions/issue-60/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "issue-60"
3 | version = "1.0.0"
4 | edition = "2021"
5 | publish = false
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 | name = "issue_60"
10 |
11 | [dependencies]
12 | thiserror = "1.0"
13 | uniffi = { workspace = true, features = ["build"] }
14 | uniffi_macros.workspace = true
15 |
16 | [build-dependencies]
17 | uniffi = { workspace = true, features = ["bindgen-tests"] }
18 |
--------------------------------------------------------------------------------
/fixtures/regressions/issue-60/README.md:
--------------------------------------------------------------------------------
1 | # https://github.com/NordSecurity/uniffi-bindgen-cs/issues/60
2 |
3 | Associated enum class names conflict with top level type definitions.
4 |
5 | ```C#
6 | public record Rectangle(double @width, double @height) { }
7 | public record Shape
8 | { ____________
9 | ∨ ∧
10 | public record Rectangle(Rectangle @s) : Shape { }
11 | public record Ellipse(Ellipse @s) : Shape { }
12 | }
13 | ```
14 |
--------------------------------------------------------------------------------
/fixtures/regressions/issue-60/src/lib.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | use std::fmt;
6 |
7 | #[derive(uniffi::Enum)]
8 | pub enum Shape {
9 | Rectangle { s: Rectangle },
10 | Ellipse { s: Ellipse },
11 | }
12 |
13 | #[derive(Debug, thiserror::Error, uniffi::Error)]
14 | pub enum ShapeError {
15 | #[error("Rectangle: {s}")]
16 | Rectangle { s: Rectangle },
17 | #[error("Ellipse: {s}")]
18 | Ellipse { s: Ellipse },
19 | }
20 |
21 | #[derive(uniffi::Record, Debug)]
22 | pub struct Rectangle {
23 | width: f64,
24 | height: f64,
25 | }
26 |
27 | #[derive(uniffi::Record, Debug)]
28 | pub struct Ellipse {
29 | x_radius: f64,
30 | y_radius: f64,
31 | }
32 |
33 | impl fmt::Display for Rectangle {
34 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 | write!(f, "width: {}, height: {}", self.width, self.height)
36 | }
37 | }
38 |
39 | impl fmt::Display for Ellipse {
40 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 | write!(f, "x_radius: {}, y_radius: {}", self.x_radius, self.y_radius)
42 | }
43 | }
44 |
45 | uniffi::setup_scaffolding!();
46 |
--------------------------------------------------------------------------------
/fixtures/regressions/issue-75/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "issue-75"
3 | version = "1.0.0"
4 | edition = "2021"
5 | publish = false
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 | name = "issue_75"
10 |
11 | [dependencies]
12 | once_cell = "1.12"
13 | thiserror = "1.0"
14 | uniffi = { workspace = true, features = ["build"] }
15 | uniffi_macros.workspace = true
16 |
17 | [build-dependencies]
18 | uniffi = { workspace = true, features = ["bindgen-tests"] }
19 |
--------------------------------------------------------------------------------
/fixtures/regressions/issue-75/README.md:
--------------------------------------------------------------------------------
1 | # https://github.com/NordSecurity/uniffi-bindgen-cs/issues/75
2 |
3 | Empty struct causes invalid code to be generated in `RecordTemplate.cs::AllocationSize`
4 |
--------------------------------------------------------------------------------
/fixtures/regressions/issue-75/src/lib.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | #[derive(uniffi::Record)]
6 | pub struct EmptyDictionary {
7 | }
8 |
9 | uniffi::setup_scaffolding!();
10 |
--------------------------------------------------------------------------------
/fixtures/regressions/issue-76/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "issue-76"
3 | version = "1.0.0"
4 | edition = "2021"
5 | publish = false
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 | name = "issue_76"
10 |
11 | [dependencies]
12 | thiserror = "1.0"
13 | uniffi = { workspace = true, features = ["build"] }
14 | uniffi_macros.workspace = true
15 |
16 | [build-dependencies]
17 | uniffi = { workspace = true, features = ["bindgen-tests"] }
--------------------------------------------------------------------------------
/fixtures/regressions/issue-76/README.md:
--------------------------------------------------------------------------------
1 | # https://github.com/NordSecurity/uniffi-bindgen-cs/issues/76
2 |
3 | `Error` is translated into `Exception`, and causes ambiguous type references between `System.Exception` and `Exception`.
4 |
--------------------------------------------------------------------------------
/fixtures/regressions/issue-76/src/lib.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | #[derive(Debug, thiserror::Error, uniffi::Error)]
6 | pub enum Error {
7 | #[error("Example")]
8 | Example,
9 | }
10 |
11 | #[uniffi::export]
12 | pub fn always_error() -> Result<(), Error> {
13 | Err(Error::Example)
14 | }
15 |
16 | uniffi::setup_scaffolding!();
17 |
--------------------------------------------------------------------------------
/fixtures/src/lib.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | mod uniffi_fixtures {
6 | arithmetical::uniffi_reexport_scaffolding!();
7 | custom_types::uniffi_reexport_scaffolding!();
8 | uniffi_callbacks::uniffi_reexport_scaffolding!();
9 | uniffi_geometry::uniffi_reexport_scaffolding!();
10 | uniffi_rondpoint::uniffi_reexport_scaffolding!();
11 | uniffi_sprites::uniffi_reexport_scaffolding!();
12 | uniffi_todolist::uniffi_reexport_scaffolding!();
13 | uniffi_traits::uniffi_reexport_scaffolding!();
14 |
15 | uniffi_chronological::uniffi_reexport_scaffolding!();
16 | uniffi_coverall::uniffi_reexport_scaffolding!();
17 | uniffi_fixture_callbacks::uniffi_reexport_scaffolding!();
18 | uniffi_fixture_docstring::uniffi_reexport_scaffolding!();
19 | uniffi_error_types::uniffi_reexport_scaffolding!();
20 | uniffi_futures::uniffi_reexport_scaffolding!();
21 | uniffi_trait_methods::uniffi_reexport_scaffolding!();
22 |
23 | global_methods_class_name::uniffi_reexport_scaffolding!();
24 | null_to_empty_string::uniffi_reexport_scaffolding!();
25 | uniffi_cs_custom_types_builtin::uniffi_reexport_scaffolding!();
26 | uniffi_cs_disposable::uniffi_reexport_scaffolding!();
27 | uniffi_cs_optional_parameters::uniffi_reexport_scaffolding!();
28 | uniffi_cs_positional_enums::uniffi_reexport_scaffolding!();
29 | stringify::uniffi_reexport_scaffolding!();
30 | issue_28::uniffi_reexport_scaffolding!();
31 | issue_60::uniffi_reexport_scaffolding!();
32 | issue_75::uniffi_reexport_scaffolding!();
33 | issue_76::uniffi_reexport_scaffolding!();
34 | issue_110::uniffi_reexport_scaffolding!();
35 | }
36 |
--------------------------------------------------------------------------------
/fixtures/stringify/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "uniffi-cs-stringify"
3 | version = "1.0.0"
4 | edition = "2021"
5 | publish = false
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 | name = "stringify"
10 |
11 | [dependencies]
12 | paste = "1.0"
13 | uniffi = { workspace = true, features = ["build"] }
14 | uniffi_macros.workspace = true
15 |
16 | [build-dependencies]
17 | uniffi = { workspace = true, features = ["bindgen-tests"] }
18 |
--------------------------------------------------------------------------------
/fixtures/stringify/src/lib.rs:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | use paste::paste;
6 |
7 | uniffi::setup_scaffolding!();
8 |
9 | macro_rules! define_parse_function {
10 | ($type:ty) => {
11 | paste! {
12 | #[uniffi::export]
13 | fn [](value: $type) -> String {
14 | value.to_string()
15 | }
16 |
17 | #[uniffi::export]
18 | fn [](value: String) -> $type {
19 | value.parse::<$type>().unwrap()
20 | }
21 | }
22 | };
23 | }
24 |
25 | define_parse_function!(i8);
26 | define_parse_function!(i16);
27 | define_parse_function!(i32);
28 | define_parse_function!(i64);
29 | define_parse_function!(u8);
30 | define_parse_function!(u16);
31 | define_parse_function!(u32);
32 | define_parse_function!(u64);
33 | define_parse_function!(f32);
34 | define_parse_function!(f64);
35 |
--------------------------------------------------------------------------------
/generate_bindings.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euxo pipefail
3 |
4 | GEN_DIR="dotnet-tests/UniffiCS/gen"
5 |
6 | case $(uname | tr '[:upper:]' '[:lower:]') in
7 | linux*)
8 | LIB=libuniffi_fixtures.so
9 | ;;
10 | darwin*)
11 | LIB=libuniffi_fixtures.dylib
12 | ;;
13 | msys* | mingw64*)
14 | LIB=uniffi_fixtures.dll
15 | ;;
16 | *)
17 | echo "Cannot find binary - unrecognized uname" >&2
18 | exit 1
19 | ;;
20 | esac
21 |
22 | rm -rf "$GEN_DIR"
23 | mkdir -p "$GEN_DIR"
24 |
25 | target/debug/uniffi-bindgen-cs target/debug/$LIB --library --out-dir="$GEN_DIR" --no-format
--------------------------------------------------------------------------------
/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "1.81"
--------------------------------------------------------------------------------
/test_bindings.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euxo pipefail
3 |
4 | SOLUTION_DIR="dotnet-tests"
5 | dotnet test -l "console;verbosity=normal" $SOLUTION_DIR
6 |
--------------------------------------------------------------------------------
/uniffi-test-fixtures.toml:
--------------------------------------------------------------------------------
1 | [bindings.csharp]
2 | cdylib_name = "uniffi_fixtures"
3 |
4 | [bindings.csharp.custom_types.Url]
5 | imports = ["System"]
6 | type_name = "Uri"
7 | into_custom = "new Uri({})"
8 | from_custom = "{}.AbsoluteUri"
9 |
--------------------------------------------------------------------------------