diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 7a5c3e1c..e1a5213b 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -36,3 +36,11 @@ updates:
target-branch: "main"
labels:
- "kind/dependencies"
+ - package-ecosystem: "nuget"
+ directory: "/dotnet"
+ schedule:
+ interval: "daily"
+ time: "03:00"
+ target-branch: "main"
+ labels:
+ - "kind/dependencies"
diff --git a/.gitignore b/.gitignore
index d81ee86a..33a8408d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -457,6 +457,7 @@ $RECYCLE.BIN/
## Hyperlight
**/include/hyperlight*.h
+!src/hyperlight_wasm_capi/include/hyperlight_wasm.h
**/include/printf.h
**/native/HyperlightGuest.lib
**/native/HyperlightGuest.pdb
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15d3fe67..92fb8d3e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Prerelease] - Unreleased
+### Added
+
+- Added the `hyperlight-wasm-capi` crate, a C API that calls component functions with arguments and results encoded in hyperlight's component wire format.
+- Added the `Hyperlight.Wasm` and `Hyperlight.Wasm.Build` .NET packages. They run components from .NET with typed C# bindings generated from WIT at build time. See `dotnet/README.md`.
+- Added `SandboxBuilder::with_runtime_file` to load the guest runtime from a file, and `SandboxBuilder::with_interrupt_vcpu_sigrtmin_offset` to change the interrupt signal on Linux.
+
+### Changed
+
+- **BREAKING CHANGE:** Component functions use hyperlight's wire names and little-endian encoding, so hosts and guests built with earlier versions cannot call each other.
+
+### Fixed
+
+- Wasm traps report an error instead of aborting the guest with a general protection fault.
+
## [v0.15.0] - 2026-08
### Changed
diff --git a/Cargo.lock b/Cargo.lock
index b19931cc..0fc0f392 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -44,6 +44,12 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+[[package]]
+name = "allocator-api2"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880"
+
[[package]]
name = "android_system_properties"
version = "0.1.6"
@@ -187,21 +193,20 @@ checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
[[package]]
name = "bindgen"
-version = "0.72.1"
+version = "0.73.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
+checksum = "787ef8ef523575546b106a58213d6e6b06198a05c2f757258c68a74273670cfa"
dependencies = [
"bitflags 2.13.2",
"cexpr",
"clang-sys",
- "itertools 0.13.0",
"log",
"prettyplease 0.2.37",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
- "shlex 1.3.0",
+ "shlex",
"syn 2.0.119",
]
@@ -258,15 +263,6 @@ dependencies = [
"serde_core",
]
-[[package]]
-name = "buddy_system_allocator"
-version = "0.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1af5a01adeeade54c9f5060300227a8ee64c05b6376f28e9b8ee3b72dd2056f"
-dependencies = [
- "spin 0.10.1",
-]
-
[[package]]
name = "built"
version = "0.8.1"
@@ -283,7 +279,7 @@ version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
dependencies = [
- "allocator-api2",
+ "allocator-api2 0.2.21",
]
[[package]]
@@ -402,7 +398,7 @@ dependencies = [
"find-msvc-tools",
"jobserver",
"libc",
- "shlex 2.0.1",
+ "shlex",
]
[[package]]
@@ -1858,8 +1854,7 @@ dependencies = [
[[package]]
name = "hyperlight-common"
version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "360d5876788b26af66bf055bca6b4f2c9c930a0fdcaebda4bd7f808c6bd93068"
+source = "git+https://github.com/hyperlight-dev/hyperlight?branch=component-wire-format#ca2b8f78bd53a5fe168b208c92c3888bcc2e8535"
dependencies = [
"anyhow",
"bitflags 2.13.2",
@@ -1869,17 +1864,24 @@ dependencies = [
"flatbuffers",
"log",
"smallvec",
- "spin 0.12.3",
+ "spin",
"thiserror",
"tracing",
"tracing-core",
]
+[[package]]
+name = "hyperlight-component-csharp"
+version = "0.18.0"
+source = "git+https://github.com/hyperlight-dev/hyperlight?branch=component-wire-format#ca2b8f78bd53a5fe168b208c92c3888bcc2e8535"
+dependencies = [
+ "hyperlight-component-util",
+]
+
[[package]]
name = "hyperlight-component-macro"
version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec066e71934ebaddf70345ead15c4d01df880a9cd52fafb2934a559c4fa8a991"
+source = "git+https://github.com/hyperlight-dev/hyperlight?branch=component-wire-format#ca2b8f78bd53a5fe168b208c92c3888bcc2e8535"
dependencies = [
"env_logger",
"hyperlight-component-util",
@@ -1893,8 +1895,7 @@ dependencies = [
[[package]]
name = "hyperlight-component-util"
version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "271a2a808bc2e6b0058ca8dd6aea81c0dca91ec7919d167315289190bb64e767"
+source = "git+https://github.com/hyperlight-dev/hyperlight?branch=component-wire-format#ca2b8f78bd53a5fe168b208c92c3888bcc2e8535"
dependencies = [
"itertools 0.15.0",
"prettyplease 0.3.0",
@@ -1911,8 +1912,7 @@ dependencies = [
[[package]]
name = "hyperlight-guest"
version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7faceeab52cbc0f128e75fa4b0ee9fce17c3c462d7776feca66f41e98bf2a4bb"
+source = "git+https://github.com/hyperlight-dev/hyperlight?branch=component-wire-format#ca2b8f78bd53a5fe168b208c92c3888bcc2e8535"
dependencies = [
"anyhow",
"flatbuffers",
@@ -1926,10 +1926,8 @@ dependencies = [
[[package]]
name = "hyperlight-guest-bin"
version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7eff3c3b94ae4da51f03d535e9a13a4132f5f87694a19514c038a9517cfd750"
+source = "git+https://github.com/hyperlight-dev/hyperlight?branch=component-wire-format#ca2b8f78bd53a5fe168b208c92c3888bcc2e8535"
dependencies = [
- "buddy_system_allocator",
"flatbuffers",
"hyperlight-common",
"hyperlight-guest",
@@ -1938,15 +1936,15 @@ dependencies = [
"hyperlight-libc",
"linkme",
"log",
- "spin 0.12.3",
+ "spin",
+ "talc",
"tracing",
]
[[package]]
name = "hyperlight-guest-macro"
version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f957baa8d97e2787589bf084a18096c08ab4dd22d4f5a040f153f9f719330ec3"
+source = "git+https://github.com/hyperlight-dev/hyperlight?branch=component-wire-format#ca2b8f78bd53a5fe168b208c92c3888bcc2e8535"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@@ -1957,11 +1955,10 @@ dependencies = [
[[package]]
name = "hyperlight-guest-tracing"
version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a12c240f87acd92e80ea08e9a405e781e4a15b9f38fee5fe261e9ce69b816d3"
+source = "git+https://github.com/hyperlight-dev/hyperlight?branch=component-wire-format#ca2b8f78bd53a5fe168b208c92c3888bcc2e8535"
dependencies = [
"hyperlight-common",
- "spin 0.12.3",
+ "spin",
"tracing",
"tracing-core",
]
@@ -1969,8 +1966,7 @@ dependencies = [
[[package]]
name = "hyperlight-host"
version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40f8b85ddd688890477158b9921b228973d1b70c84b866698838df0f32d2aa44"
+source = "git+https://github.com/hyperlight-dev/hyperlight?branch=component-wire-format#ca2b8f78bd53a5fe168b208c92c3888bcc2e8535"
dependencies = [
"anyhow",
"bindgen",
@@ -1999,7 +1995,7 @@ dependencies = [
"mshv-bindings",
"mshv-ioctls",
"oci-spec",
- "opentelemetry",
+ "opentelemetry 0.33.1",
"page_size",
"parking_lot",
"rand 0.10.3",
@@ -2012,7 +2008,7 @@ dependencies = [
"thiserror",
"tracing",
"tracing-core",
- "tracing-opentelemetry",
+ "tracing-opentelemetry 0.34.0",
"uuid",
"vmm-sys-util",
"windows",
@@ -2024,8 +2020,7 @@ dependencies = [
[[package]]
name = "hyperlight-libc"
version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a74af1c1cc60140c000a6ebd4eba3121bc2b8f56f134163c25eadca1146b73e0"
+source = "git+https://github.com/hyperlight-dev/hyperlight?branch=component-wire-format#ca2b8f78bd53a5fe168b208c92c3888bcc2e8535"
dependencies = [
"anyhow",
"bindgen",
@@ -2061,7 +2056,7 @@ dependencies = [
"metrics-exporter-prometheus",
"metrics-util",
"once_cell",
- "opentelemetry",
+ "opentelemetry 0.32.0",
"opentelemetry-otlp",
"opentelemetry-semantic-conventions",
"opentelemetry_sdk",
@@ -2074,7 +2069,7 @@ dependencies = [
"toml",
"tracing",
"tracing-forest",
- "tracing-opentelemetry",
+ "tracing-opentelemetry 0.33.0",
"tracing-subscriber",
"uuid",
"windows",
@@ -2092,6 +2087,21 @@ dependencies = [
"wasmtime 47.0.4",
]
+[[package]]
+name = "hyperlight-wasm-bindgen"
+version = "0.15.0"
+dependencies = [
+ "hyperlight-component-csharp",
+]
+
+[[package]]
+name = "hyperlight-wasm-capi"
+version = "0.15.0"
+dependencies = [
+ "examples_common",
+ "hyperlight-wasm",
+]
+
[[package]]
name = "hyperlight-wasm-macro"
version = "0.15.0"
@@ -2116,7 +2126,7 @@ dependencies = [
"hyperlight-guest",
"hyperlight-guest-bin",
"hyperlight-wasm-macro",
- "spin 0.12.3",
+ "spin",
"tracing",
"wasmtime 36.0.18",
"wasmtime 47.0.4",
@@ -2872,6 +2882,20 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "opentelemetry"
+version = "0.33.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c1f1a6ac9c875a4941e4c7c085b61e8f4daa326de03b4701768c3491a6ad9ee"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "js-sys",
+ "pin-project-lite",
+ "thiserror",
+ "tracing",
+]
+
[[package]]
name = "opentelemetry-http"
version = "0.32.0"
@@ -2881,7 +2905,7 @@ dependencies = [
"async-trait",
"bytes",
"http",
- "opentelemetry",
+ "opentelemetry 0.32.0",
"reqwest",
]
@@ -2892,7 +2916,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35"
dependencies = [
"http",
- "opentelemetry",
+ "opentelemetry 0.32.0",
"opentelemetry-http",
"opentelemetry-proto",
"opentelemetry_sdk",
@@ -2910,7 +2934,7 @@ version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638"
dependencies = [
- "opentelemetry",
+ "opentelemetry 0.32.0",
"opentelemetry_sdk",
"prost",
"tonic",
@@ -2932,7 +2956,7 @@ dependencies = [
"futures-channel",
"futures-executor",
"futures-util",
- "opentelemetry",
+ "opentelemetry 0.32.0",
"percent-encoding",
"portable-atomic",
"rand 0.9.5",
@@ -3410,7 +3434,7 @@ version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5216b1837de2149f8bc8e6d5f88a9326b63b8c836ed58ce4a0a29ec736a59734"
dependencies = [
- "allocator-api2",
+ "allocator-api2 0.2.21",
"bumpalo",
"hashbrown 0.15.5",
"log",
@@ -3424,7 +3448,7 @@ version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "757712e8e61590d6d4f5d563483755538b5aa13467837a3b41cd9832509a7f85"
dependencies = [
- "allocator-api2",
+ "allocator-api2 0.2.21",
"bumpalo",
"hashbrown 0.17.1",
"log",
@@ -3828,12 +3852,6 @@ dependencies = [
"dirs",
]
-[[package]]
-name = "shlex"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
-
[[package]]
name = "shlex"
version = "2.0.1"
@@ -3887,12 +3905,6 @@ dependencies = [
"windows-sys 0.61.2",
]
-[[package]]
-name = "spin"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3"
-
[[package]]
name = "spin"
version = "0.12.3"
@@ -3980,6 +3992,16 @@ dependencies = [
"syn 3.0.9",
]
+[[package]]
+name = "talc"
+version = "5.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53f8ad14fa1c1b8635114abf9721076fe1e869572a5f26a52690f407292c80dc"
+dependencies = [
+ "allocator-api2 0.4.0",
+ "lock_api",
+]
+
[[package]]
name = "tar"
version = "0.4.46"
@@ -4345,7 +4367,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adbc64cba7137545b8044cb1fe9814f7aacf3c6b5f9b45be8bb5db538befdb26"
dependencies = [
"js-sys",
- "opentelemetry",
+ "opentelemetry 0.32.0",
+ "smallvec",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+ "tracing-subscriber",
+ "web-time",
+]
+
+[[package]]
+name = "tracing-opentelemetry"
+version = "0.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a904802a1b902f43638b677ff2a650847e3b4404101b6c586d648e8c1e3e8fe"
+dependencies = [
+ "js-sys",
+ "opentelemetry 0.33.1",
"smallvec",
"tracing",
"tracing-core",
diff --git a/Cargo.toml b/Cargo.toml
index 72a6df1f..2c987333 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
[workspace]
-members = [ "src/hyperlight_wasm", "src/examples_common", "src/hyperlight_wasm_aot", "src/hyperlight_wasm_runtime", "src/hyperlight_wasm_macro" ]
+members = [ "src/hyperlight_wasm", "src/hyperlight_wasm_capi", "src/hyperlight_wasm_bindgen", "src/examples_common", "src/hyperlight_wasm_aot", "src/hyperlight_wasm_runtime", "src/hyperlight_wasm_macro" ]
exclude = [ "src/tests/rust_guests/rust_wasm_samples", "src/tests/rust_guests/component_sample", "src/tests/rust_guests/greeter_sample" ]
resolver = "2"
@@ -15,9 +15,24 @@ readme = "README.md"
[workspace.dependencies]
hyperlight-common = { version = "0.18.0", default-features = false }
hyperlight-component-macro = "0.18.0"
+hyperlight-component-csharp = "0.18.0"
hyperlight-component-util = "0.18.0"
hyperlight-guest = "0.18.0"
hyperlight-guest-bin = "0.18.0"
hyperlight-host = { version = "0.18.0", default-features = false }
hyperlight-wasm-macro = { version = "0.15.0", path = "src/hyperlight_wasm_macro" }
hyperlight-wasm-runtime = { version = "0.15.0", path = "src/hyperlight_wasm_runtime" }
+
+# Unreleased hyperlight with the component wire format and C# bindings.
+# Remove once hyperlight releases these changes.
+[patch.crates-io]
+hyperlight-host = { git = "https://github.com/hyperlight-dev/hyperlight", branch = "component-wire-format" }
+hyperlight-common = { git = "https://github.com/hyperlight-dev/hyperlight", branch = "component-wire-format" }
+hyperlight-guest = { git = "https://github.com/hyperlight-dev/hyperlight", branch = "component-wire-format" }
+hyperlight-guest-bin = { git = "https://github.com/hyperlight-dev/hyperlight", branch = "component-wire-format" }
+hyperlight-guest-macro = { git = "https://github.com/hyperlight-dev/hyperlight", branch = "component-wire-format" }
+hyperlight-guest-tracing = { git = "https://github.com/hyperlight-dev/hyperlight", branch = "component-wire-format" }
+hyperlight-libc = { git = "https://github.com/hyperlight-dev/hyperlight", branch = "component-wire-format" }
+hyperlight-component-macro = { git = "https://github.com/hyperlight-dev/hyperlight", branch = "component-wire-format" }
+hyperlight-component-util = { git = "https://github.com/hyperlight-dev/hyperlight", branch = "component-wire-format" }
+hyperlight-component-csharp = { git = "https://github.com/hyperlight-dev/hyperlight", branch = "component-wire-format" }
diff --git a/Justfile b/Justfile
index 360bf132..05212929 100644
--- a/Justfile
+++ b/Justfile
@@ -124,6 +124,22 @@ examples-components target=default-target features="": (build-rust-component-exa
{{ wit-world }} cargo run {{ if features =="" {''} else {"--no-default-features -F kvm -F " + features } }} --profile={{ if target == "debug" {"dev"} else { target } }} --example component_example
{{ wit-world-c }} cargo run {{ if features =="" {''} else {"--no-default-features -F kvm -F " + features } }} --profile={{ if target == "debug" {"dev"} else { target } }} --example c-component
{{ wit-world }} {{ wit-world-name-greeter }} cargo run {{ if features =="" {''} else {"--no-default-features -F kvm -F " + features } }} --profile={{ if target == "debug" {"dev"} else { target } }} --example component_greeter_example
+ {{ wit-world }} cargo test -p hyperlight-wasm-capi --profile={{ if target == "debug" {"dev"} else { target } }} -- --include-ignored
+
+# Builds the native libraries, and a guest runtime and component for the component_sample example world, for the .NET tests
+dotnet-test-assets target=default-target: (build-rust-component-examples target)
+ cargo build -p hyperlight-wasm-capi -p hyperlight-wasm-bindgen --profile={{ if target == "debug" {"dev"} else { target } }}
+ {{ wit-world }} cargo hyperlight build --profile={{ if target == "debug" {"dev"} else { target } }} --manifest-path src/hyperlight_wasm_runtime/Cargo.toml --target-dir target/component-sample-runtime --locked
+
+# Runs the .NET tests. Needs the .NET 8 SDK or later.
+dotnet-test target=default-target: (dotnet-test-assets target)
+ dotnet test dotnet/tests/Hyperlight.Wasm.Tests -c {{ if target == "debug" {"Debug"} else {"Release"} }}
+
+# Packs the .NET packages for this machine's runtime identifier into dotnet/artifacts
+dotnet-pack target="release":
+ cargo build -p hyperlight-wasm-capi -p hyperlight-wasm-bindgen --profile={{ if target == "debug" {"dev"} else { target } }}
+ dotnet pack dotnet/src/Hyperlight.Wasm -c {{ if target == "debug" {"Debug"} else {"Release"} }} -o dotnet/artifacts
+ dotnet pack dotnet/src/Hyperlight.Wasm.Build -c {{ if target == "debug" {"Debug"} else {"Release"} }} -o dotnet/artifacts
# Test a component and a module compiled with pulley
examples-pulley target=default-target features="": (build-pulley-rust-component-examples target features) (build-pulley-rust-wasm-examples target features)
diff --git a/README.md b/README.md
index 878764a3..1a9a002f 100644
--- a/README.md
+++ b/README.md
@@ -134,6 +134,13 @@ WIT_WORLD= HYPERLIGHT_COMPONENT_MACRO_DEBUG=/tmp/guest.rs
HYPERLIGHT_COMPONENT_MACRO_DEBUG=/tmp/host.rs cargo build
```
+### Other languages
+
+* [`src/hyperlight_wasm_capi`](src/hyperlight_wasm_capi) is a C API. Its header is `include/hyperlight_wasm.h`.
+* [`dotnet`](dotnet) has .NET packages with C# bindings generated from WIT.
+
+Both load a guest runtime built for the component's world from a file.
+
## Code of Conduct
diff --git a/dotnet/Directory.Build.props b/dotnet/Directory.Build.props
new file mode 100644
index 00000000..a4b8951f
--- /dev/null
+++ b/dotnet/Directory.Build.props
@@ -0,0 +1,23 @@
+
+
+
+ enable
+ enable
+ latest
+ true
+ 0.15.0
+ The Hyperlight Authors
+ Apache-2.0
+ https://github.com/hyperlight-dev/hyperlight-wasm
+ https://github.com/hyperlight-dev/hyperlight-wasm
+
+ release
+ debug
+ $(MSBuildThisFileDirectory)../target/$(HyperlightCargoProfile)/
+ lib
+ .dll
+ .so
+ .dylib
+
+
+
diff --git a/dotnet/Directory.Build.targets b/dotnet/Directory.Build.targets
new file mode 100644
index 00000000..49cbec8a
--- /dev/null
+++ b/dotnet/Directory.Build.targets
@@ -0,0 +1,8 @@
+
+
+
+
+ $(NETCoreSdkRuntimeIdentifier)
+
+
+
diff --git a/dotnet/README.md b/dotnet/README.md
new file mode 100644
index 00000000..90013620
--- /dev/null
+++ b/dotnet/README.md
@@ -0,0 +1,72 @@
+# Hyperlight.Wasm for .NET
+
+Run WebAssembly components in Hyperlight micro VMs from .NET, with typed bindings generated from WIT.
+
+* `Hyperlight.Wasm` is the runtime library. It wraps the native `hyperlight-wasm-capi` library.
+* `Hyperlight.Wasm.Build` generates C# from WIT at build time. It needs no Rust toolchain.
+
+Both target .NET 8 or later. The packages hold native libraries for the runtime identifier they were packed on.
+
+## Usage
+
+```xml
+
+
+
+
+
+```
+
+Each `HyperlightWit` item is a `.wit` file or directory. `World` selects a world when the package has several. Without it, the last world is used. `Namespace` sets the namespace of the generated types and defaults to the project's root namespace.
+
+For a world `example` that imports `host` and exports `adder`, the build generates:
+
+* `ExampleComponent`, with an `Adder` property for the export.
+* `ExampleComponent.RegisterImports`, which registers an `IHost` implementation as host functions.
+* C# types for the world's records, variants, enums and flags.
+
+```csharp
+using var sandbox = new WasmSandbox(new SandboxOptions
+{
+ RuntimePath = "example-runtime",
+});
+ExampleComponent.RegisterImports(sandbox, new Host());
+sandbox.Load("example.aot");
+
+var component = new ExampleComponent(sandbox);
+uint sum = component.Adder.Add(1, 2);
+```
+
+`docs/component-wire-format.md` in hyperlight lists how WIT types map to C#.
+
+## Guest runtime and components
+
+A component needs a guest runtime built for its WIT world:
+
+```sh
+WIT_WORLD=example-world.wasm cargo hyperlight build --release \
+ --manifest-path src/hyperlight_wasm_runtime/Cargo.toml --target-dir target/example-runtime
+```
+
+`example-world.wasm` comes from `wasm-tools component wit example.wit -w -o example-world.wasm`. Precompile the component with `hyperlight-wasm-aot compile --component`. The runtime, the AOT tool and the packages must come from the same hyperlight-wasm version.
+
+## Sandboxes
+
+* Register host functions, then load a component, then call it.
+* Calls are serialized. Host functions run on the calling thread and cannot call back into their own sandbox.
+* An exception thrown by a host function fails the guest call. The `HyperlightException` holds it as `InnerException`.
+* A trap or interrupted call poisons the sandbox. Restore a snapshot taken with `CreateSnapshot` to use it again.
+* `Call` takes a `CancellationToken`. Cancelling interrupts the guest. `CreateInterruptHandle` returns a handle that does the same from any thread.
+
+## Interrupt signal on Linux
+
+Hyperlight interrupts a running guest with the signal `SIGRTMIN + InterruptSignalOffset`. The .NET runtime suspends threads for garbage collection with `SIGRTMIN`, so the offset defaults to 1. With offset 0 a garbage collection can hang.
+
+## Development
+
+```sh
+just dotnet-test # builds the native libraries, a test runtime and component, then runs the tests
+just dotnet-pack # packs both packages into dotnet/artifacts
+```
+
+`HyperlightNativeDir` overrides where the projects find the native libraries.
diff --git a/dotnet/src/Hyperlight.Wasm.Build/GenerateHyperlightBindings.cs b/dotnet/src/Hyperlight.Wasm.Build/GenerateHyperlightBindings.cs
new file mode 100644
index 00000000..24eb4fd2
--- /dev/null
+++ b/dotnet/src/Hyperlight.Wasm.Build/GenerateHyperlightBindings.cs
@@ -0,0 +1,128 @@
+// Copyright 2026 The Hyperlight Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+using System.Runtime.InteropServices;
+using System.Text;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Hyperlight.Wasm.Build;
+
+/// Generates C# bindings for each WIT item.
+public sealed unsafe class GenerateHyperlightBindings : Microsoft.Build.Utilities.Task
+{
+ ///
+ /// WIT files or directories. Optional metadata: World selects a
+ /// world and Namespace overrides .
+ ///
+ [Required]
+ public ITaskItem[] Wit { get; set; } = [];
+
+ /// The native hyperlight_wasm_bindgen library.
+ [Required]
+ public string BindgenLibrary { get; set; } = "";
+
+ /// The directory for generated files.
+ [Required]
+ public string OutputDirectory { get; set; } = "";
+
+ /// The namespace of items without Namespace metadata.
+ public string DefaultNamespace { get; set; } = "";
+
+ /// The generated C# files.
+ [Output]
+ public ITaskItem[] Generated { get; set; } = [];
+
+ ///
+ public override bool Execute()
+ {
+ nint library;
+ try
+ {
+ library = NativeLibrary.Load(BindgenLibrary);
+ }
+ catch (Exception e)
+ {
+ Log.LogError("Cannot load the Hyperlight bindings generator '{0}': {1}", BindgenLibrary, e.Message);
+ return false;
+ }
+ try
+ {
+ var generate = (delegate* unmanaged)
+ NativeLibrary.GetExport(library, "hlw_bindgen_csharp");
+ var free = (delegate* unmanaged)NativeLibrary.GetExport(library, "hlw_bindgen_free");
+ Directory.CreateDirectory(OutputDirectory);
+ var generated = new List();
+ var outputs = new HashSet(StringComparer.OrdinalIgnoreCase);
+ foreach (var item in Wit)
+ {
+ var wit = Path.GetFullPath(item.ItemSpec);
+ var world = item.GetMetadata("World");
+ var ns = item.GetMetadata("Namespace");
+ if (ns.Length == 0)
+ {
+ ns = DefaultNamespace;
+ }
+ if (!TryGenerate(generate, free, wit, world, ns, out var text))
+ {
+ Log.LogError(null, "HLWIT001", null, item.ItemSpec, 0, 0, 0, 0, "Cannot generate Hyperlight bindings: {0}", text);
+ continue;
+ }
+ var name = Path.GetFileNameWithoutExtension(wit.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
+ if (world.Length > 0)
+ {
+ name += "." + world;
+ }
+ var output = Path.Combine(OutputDirectory, name + ".g.cs");
+ if (!outputs.Add(output))
+ {
+ Log.LogError(null, "HLWIT002", null, item.ItemSpec, 0, 0, 0, 0, "Two HyperlightWit items generate '{0}'. Give them different World metadata or file names.", output);
+ continue;
+ }
+ // Rewriting unchanged output would force a recompile.
+ if (!File.Exists(output) || File.ReadAllText(output) != text)
+ {
+ File.WriteAllText(output, text);
+ }
+ generated.Add(new TaskItem(output));
+ }
+ Generated = [.. generated];
+ return !Log.HasLoggedErrors;
+ }
+ finally
+ {
+ NativeLibrary.Free(library);
+ }
+ }
+
+ private static bool TryGenerate(
+ delegate* unmanaged generate,
+ delegate* unmanaged free,
+ string wit,
+ string world,
+ string ns,
+ out string text)
+ {
+ var witBytes = Encoding.UTF8.GetBytes(wit);
+ var worldBytes = Encoding.UTF8.GetBytes(world);
+ var nsBytes = Encoding.UTF8.GetBytes(ns);
+ byte* output;
+ nuint length;
+ int status;
+ fixed (byte* w = witBytes)
+ fixed (byte* wo = worldBytes)
+ fixed (byte* n = nsBytes)
+ {
+ status = generate(w, (nuint)witBytes.Length, wo, (nuint)worldBytes.Length, n, (nuint)nsBytes.Length, &output, &length);
+ }
+ try
+ {
+ text = Encoding.UTF8.GetString(output, checked((int)length));
+ }
+ finally
+ {
+ free(output, length);
+ }
+ return status == 0;
+ }
+}
diff --git a/dotnet/src/Hyperlight.Wasm.Build/Hyperlight.Wasm.Build.csproj b/dotnet/src/Hyperlight.Wasm.Build/Hyperlight.Wasm.Build.csproj
new file mode 100644
index 00000000..2e56304f
--- /dev/null
+++ b/dotnet/src/Hyperlight.Wasm.Build/Hyperlight.Wasm.Build.csproj
@@ -0,0 +1,36 @@
+
+
+
+ net8.0
+ true
+ Generates C# bindings for Hyperlight.Wasm from WIT files listed as HyperlightWit items.
+ hyperlight;wasm;webassembly;component;wit;bindgen
+ true
+
+ false
+ $(NoWarn);NU5100;NU5128
+ true
+
+
+
+
+
+
+
+ $(HyperlightNativeDir)$(HyperlightNativePrefix)hyperlight_wasm_bindgen$(HyperlightNativeSuffix)
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/src/Hyperlight.Wasm.Build/build/Hyperlight.Wasm.Build.props b/dotnet/src/Hyperlight.Wasm.Build/build/Hyperlight.Wasm.Build.props
new file mode 100644
index 00000000..8072c94d
--- /dev/null
+++ b/dotnet/src/Hyperlight.Wasm.Build/build/Hyperlight.Wasm.Build.props
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/src/Hyperlight.Wasm.Build/build/Hyperlight.Wasm.Build.targets b/dotnet/src/Hyperlight.Wasm.Build/build/Hyperlight.Wasm.Build.targets
new file mode 100644
index 00000000..613877cd
--- /dev/null
+++ b/dotnet/src/Hyperlight.Wasm.Build/build/Hyperlight.Wasm.Build.targets
@@ -0,0 +1,35 @@
+
+
+
+ $(MSBuildThisFileDirectory)../tasks/net8.0/Hyperlight.Wasm.Build.dll
+ <_HyperlightBindgenFile Condition="$([MSBuild]::IsOSPlatform('Windows'))">hyperlight_wasm_bindgen.dll
+ <_HyperlightBindgenFile Condition="$([MSBuild]::IsOSPlatform('Linux'))">libhyperlight_wasm_bindgen.so
+ <_HyperlightBindgenFile Condition="$([MSBuild]::IsOSPlatform('OSX'))">libhyperlight_wasm_bindgen.dylib
+ $(MSBuildThisFileDirectory)../tasks/net8.0/runtimes/$(NETCoreSdkRuntimeIdentifier)/native/$(_HyperlightBindgenFile)
+
+
+
+
+
+
+
+
+ $(IntermediateOutputPath)HyperlightBindings/
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/src/Hyperlight.Wasm/Component/Runtime.cs b/dotnet/src/Hyperlight.Wasm/Component/Runtime.cs
new file mode 100644
index 00000000..7ab7c96c
--- /dev/null
+++ b/dotnet/src/Hyperlight.Wasm/Component/Runtime.cs
@@ -0,0 +1,475 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2026 The Hyperlight Authors.
+
+// Support types for C# bindings generated by hyperlight-component-csharp.
+// The encoding is specified in docs/component-wire-format.md.
+
+#nullable enable
+// Members named after WIT types and object overrides need no docs.
+#pragma warning disable CS1591
+
+using System;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Hyperlight.Component
+{
+ /// Calls guest functions by wire name.
+ public interface IGuestCaller
+ {
+ ///
+ /// Calls with one encoded buffer per
+ /// parameter and returns the encoded result.
+ ///
+ byte[] Call(string function, byte[][] args);
+ }
+
+ /// Registers host functions by wire name.
+ public interface IHostFunctionRegistry
+ {
+ ///
+ /// Registers , which receives one
+ /// encoded buffer per parameter and returns the encoded result.
+ ///
+ void Register(string function, int parameterCount, Func implementation);
+ }
+
+ /// Thrown when encoded data does not match the expected type.
+ public sealed class WireFormatException : Exception
+ {
+ public WireFormatException(string message) : base(message) { }
+ }
+
+ /// The payload of a result case without a type.
+ public readonly struct Unit : IEquatable
+ {
+ public bool Equals(Unit other) => true;
+ public override bool Equals(object? obj) => obj is Unit;
+ public override int GetHashCode() => 0;
+ public override string ToString() => "()";
+
+ public static bool operator ==(Unit left, Unit right) => true;
+
+ public static bool operator !=(Unit left, Unit right) => false;
+ }
+
+ ///
+ /// A WIT option whose payload is itself an option.
+ /// Other options map to nullable types.
+ ///
+ public readonly struct Option : IEquatable