├── .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 | --------------------------------------------------------------------------------