Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Cloudflare Workers 向けアプリを C# で構築する ~WASM Native...

Avatar for neno neno
September 19, 2026

Cloudflare Workers 向けアプリを C# で構築する ~WASM Native AOT への道~

C# Kaigi 2026 の登壇資料

GitHub: cloudflare-workers-dotnet

Avatar for neno

neno

September 19, 2026

More Decks by neno

Other Decks in Technology

Transcript

  1. 自己紹介 何縫ねの。 • 所属: NTTドコモビジネス株式会社 イノベーションセンター • Microsoft MVP for

    Developer Technologies (2024~) • .NET / Web Development • 趣味 • C#, OSS, ドール, 一眼(α7 IV), シーシャ nenoNaninu nenoMake ブログ https://blog.neno.dev その他 https://neno.dev 1
  2. OSS 紹介 TypedSignalR.Client https://github.com/nenoNaninu/TypedSignalR.Client • C# の SignalR Client を強く型付けするための

    Source Generator こんな SignalR の Hub と Receiver の interface が あったとして… Before After (using TypedSignalR.Client) 脱文字列! 全てが強く型付け! 3
  3. OSS 紹介 MessagePack Hub Protocol 対応! TypedSignalR.Client.TypeScript https://github.com/nenoNaninu/TypedSignalR.Client.TypeScript • TypeScript

    の SignalR Client を強く型付けするための .NET Tool / library Before 属性を付与するだけ! After (using TypedSignalR.Client.TypeScript) 脱文字列! 全てが強く型付け! TypeScript 用の型を C# から自動生成 4
  4. OSS 紹介 SignalR 版 SwaggerUI TypedSignalR.Client.DevTools https://github.com/nenoNaninu/TypedSignalR.Client.DevTools • SignalR 使ったアプリを快適に開発するための

    GUI を自動生成する library • 2 step で利用可能! • http pipeline に middleware の追加 • Hub と Receiver を定義してる interface に属性を付与 • JWT 認証 サポート • パラメータのユーザ定義型サポート • JSON で入力! 5
  5. OSS 紹介 AspNetCore.SignalR.OpenTelemetry https://github.com/nenoNaninu/AspNetCore.SignalR.OpenTelemetry • トレースのための計装 • 最低限のログ Inspired by

    HttpLogging • 接続時 • Transport 層の情報も出力(WebSocket 等) • メソッド呼び出し時 • HubName.MethodName の素朴なログ • メソッド呼び出し毎にログのスコープを追加 • HubName, MethodName, InvocationId を 振っているのでログの検索性が向上 • Duration • 切断時 • 切断時に例外が発生していれば例外もログに出力 https://github.com/nenoNaninu/AspNetCore.SignalR.OpenTelemetry SignalR のメソッド呼び出し毎に スパンが切られるように 6
  6. Cloudflare Workers とは Cloudflare Workers の技術的仕組み • V8 isolate で

    JavaScript / WASM を動かす • V8 isolate は JavaScript を実行するための VM インスタンス V8 isolate は 複数回の request で 使い回される 10
  7. Cloudflare Workers とは Cloudflare Workers がサポートしている言語 Python は WASM 製の

    Python インタプリタ (Pyodide) で実行 Rust は WASM にコンパイル 13
  8. Cloudflare Workers とは Cloudflare Workers がサポートしている言語 C# のサポートなし! Python は

    WASM 製の Python インタプリタ (Pyodide) で実行 Rust は WASM にコンパイル 14
  9. OSS 紹介 cloudflare-workers-dotnet https://github.com/nenoNaninu/cloudflare-workers-dotnet • C# + Minimal API スタイルで

    Cloudflare Workers 向けアプリを記述可能! • パッケージ名は Cloudflare.Workers.Hosting • D1, R2, KV 等の Cloudflare サービスのバインディングも提供 16
  10. OSS 紹介 cloudflare-workers-dotnet の使い方 • dotnet publish すると app.wasm と

    index.js が出力される • wrangler.jsonc を用意 • wrangler deploy で Cloudflare Workers にデプロイ完了! wrangler.jsonc https://github.com/nenoNaninu/cloudflare-workers-dotnet 17
  11. C# と WASM の現状と Cloudflare Workers が求めるもの C# と WASM

    の現状 • Blazor WASM • C# が公式に WASM を利用している唯一の実例 19
  12. C# と WASM の現状と Cloudflare Workers が求めるもの Blazor WASM の仕組み

    • WASM 製の .NET Runtime で MSIL の dll を実行 • .NET 7 まではインタプリタ • .NET 8 からは JIT コンパイルも搭載 (Jiterpreter) • 全てが JIT コンパイルされるわけではなく、基本はインタプリタ • .NET 8 以降もオプションで JIT コンパイルを切り インタプリタとして動かす事も可能 20
  13. C# と WASM の現状と Cloudflare Workers が求めるもの WASM console app

    というテンプレートが experimental で存在はする Node.js では動くが Cloudflare Workers は Node.js 環境ではないので 動くかは絶妙なところ 21
  14. C# と WASM の現状と Cloudflare Workers が求めるもの Cloudflare Workers で求められる

    WASM • JIT コンパイルが禁じられている https://developers.cloudflare.com/workers/runtime-apis/web-standards/ 22
  15. C# と WASM の現状と Cloudflare Workers が求めるもの Cloudflare Workers で求められる

    WASM • JIT コンパイルが禁じられている https://developers.cloudflare.com/workers/runtime-apis/web-standards/ JIT コンパイルは無理でも インタプリタなら 理屈上動かせるハズ…? 23
  16. C# と WASM の現状と Cloudflare Workers が求めるもの 実験的プロジェクトに目を向けると… • dotnet/runtimelab

    に LLVM 経由で WASM に NativeAOT する実験的プロジェクトが存在する!! • パッケージ名は Microsoft.DotNet.ILCompiler.LLVM https://github.com/dotnet/runtimelab/tree/feature/NativeAOT-LLVM 30
  17. C# と WASM の現状と Cloudflare Workers が求めるもの 実験的プロジェクトに目を向けると… • dotnet/runtimelab

    に LLVM 経由で WASM に NativeAOT する実験的プロジェクトが存在する!! • パッケージ名は Microsoft.DotNet.ILCompiler.LLVM https://github.com/dotnet/runtimelab/tree/feature/NativeAOT-LLVM 31
  18. C# と WASM の現状と Cloudflare Workers が求めるもの Microsoft.DotNet.ILCompiler.LLVM ができる事 •

    ブラウザ向け WASM に NativeAOT • Emscripten が必要 • WASI 向け WASM に NativeAOT • WASI SDK が必要 • WASI preview 2 をサポート 32
  19. C# と WASM の現状と Cloudflare Workers が求めるもの WASI とは •

    WebAssembly System Interface 略して WASI • ブラウザ環境以外でも WASM を動かすために生まれた仕様 • Cloudflare Workers は当然ブラウザ環境ではないので WASI 向けビルドを選択する事になる 33
  20. C# と WASM の現状と Cloudflare Workers が求めるもの WASI のバージョン •

    Preview 1 • Core WASM にのみ依存 • Preview 2 • WIT + Component Model に依存 • Preview 3 • 2026-06-11 に仕様がリリース • 非同期対応が主 34
  21. C# と WASM の現状と Cloudflare Workers が求めるもの WASI のバージョン •

    Preview 1 • Core WASM にのみ依存 • Preview 2 • WIT + Component Model に依存 Microsoft.DotNet.ILCompiler.LLVM が サポートしているのは Preview2 • Preview 3 • 2026-06-11 に仕様がリリース • 非同期対応が主 35
  22. C# と WASM の現状と Cloudflare Workers が求めるもの WASI のバージョン •

    Preview 1 V8 は Core WASM にしか 対応していない • Core WASM にのみ依存 • Preview 2 • WIT + Component Model に依存 Microsoft.DotNet.ILCompiler.LLVM が サポートしているのは Preview2 • Preview 3 • 2026-06-11 に仕様がリリース • 非同期対応が主 36
  23. C# と WASM の現状と Cloudflare Workers が求めるもの WASI のバージョン •

    Preview 1 V8 は Core WASM にしか 対応していない • Core WASM にのみ依存 • Preview 2 • WIT + Component Model に依存 故に Cloudflare Workers で 動作するのは WASI Preview1 Microsoft.DotNet.ILCompiler.LLVM が サポートしているのは Preview2 • Preview 3 • 2026-06-11 に仕様がリリース • 非同期対応が主 37
  24. Cloudflare Workers で動く WASM にビルドする 取れる選択肢 • なんとか WASI Preview

    1 向けにビルドする • なんとか WASI Preview 2 向けにビルドされた WASM を V8 で動かせるようにする https://github.com/bytecodealliance/jco 40
  25. Cloudflare Workers で動く WASM にビルドする 取れる選択肢 • なんとか WASI Preview

    1 向けにビルドする • なんとか WASI Preview 2 向けにビルドされた WASM を V8 で動かせるようにする jco を使えば Preview2 の .wasm から Component Model を排除して Core WASM のみの .wasm を作れる https://github.com/bytecodealliance/jco 41
  26. Cloudflare Workers で動く WASM にビルドする 取れる選択肢 • なんとか WASI Preview

    1 向けにビルドする • なんとか WASI Preview 2 向けにビルドされた WASM を V8 で動かせるようにする jco を使えば Preview2 の .wasm から Component Model を排除して Core WASM のみの .wasm を作れる import を jco が自動マッピングするが Node.js 向け https://github.com/bytecodealliance/jco 42
  27. Cloudflare Workers で動く WASM にビルドする 取れる選択肢 • なんとか WASI Preview

    1 向けにビルドする • なんとか WASI Preview 2 向けにビルドされた WASM を V8 で動かせるようにする jco を使えば Preview2 の .wasm から Component Model を排除して Core WASM のみの .wasm を作れる import を jco が自動マッピングするが Node.js 向け https://github.com/bytecodealliance/jco Cloudflare Workers で動くの? 43
  28. Cloudflare Workers で動く WASM にビルドする 取れる選択肢 • なんとか WASI Preview

    1 向けにビルドする • なんとか WASI Preview 2 向けにビルドされた WASM を V8 で動かせるようにする 単純に依存を 増やす事になるので 渋い jco を使えば Preview2 の .wasm から Component Model を排除して Core WASM のみの .wasm を作れる import を jco が自動マッピングするが Node.js 向け https://github.com/bytecodealliance/jco Cloudflare Workers で動くの? 44
  29. Cloudflare Workers で動く WASM にビルドする 取れる選択肢 • なんとか WASI Preview

    1 向けにビルドする こちらが望ましい • なんとか WASI Preview 2 向けにビルドされた WASM を V8 で動かせるようにする 単純に依存を 増やす事になるので 渋い jco を使えば Preview2 の .wasm から Component Model を排除して Core WASM のみの .wasm を作れる import を jco が自動マッピングするが Node.js 向け https://github.com/bytecodealliance/jco Cloudflare Workers で動くの? 45
  30. Cloudflare Workers で動く WASM にビルドする Preview 1 でビルドするためには? • WASM

    へビルドする際には WASI SDK 同梱の clang を使う • clang を叩く際に –target で制御可能 46
  31. Cloudflare Workers で動く WASM にビルドする Preview 1 でビルドするためには? • WASM

    をビルドする際には WASI SDK 同梱の clang を使う • clang を叩く際に –target で制御可能 という事は、ビルドプロセスを定義している MSBuild のファイル内に何かあるハズ 47
  32. Cloudflare Workers で動く WASM にビルドする Microsoft.DotNet.ILCompiler.LLVM のビルドプロセスに介入する • Microsoft.DotNet.ILCompiler.LLVM が定義している

    MSBuild の .target ファイルを眺めてみる コレ! https://github.com/dotnet/runtimelab/blob/feature/NativeAOT-LLVM/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets 48
  33. Cloudflare Workers で動く WASM にビルドする Microsoft.DotNet.ILCompiler.LLVM のビルドプロセスに介入する • IlcLlvmTarget はビルドプロセスの途中で設定される

    • そのため、素朴に csproj の PropertyGroup で設定するのではなく、 IlcLlvmTarget に wasm32-unknown-wasip2 が設定された後に wasm32-unknown-wasip1 で上書きする必要がある https://github.com/nenoNaninu/cloudflare-workers-dotnet/blob/main/src/Cloudflare.Workers.Hosting/build/Cloudflare.Workers.Hosting.targets 49
  34. ライブラリの API と WASM 事情 ライブラリが提供する API • Minimal API

    スタイルにしたい Top-level statement を使う以上 OutputType が Exe である必要がある 53
  35. ライブラリの API と WASM 事情 Cloudflare Workers が HTTP リクエストの処理をキックする仕組み

    • JavaScript の fetch が呼び出される • Socket で listen しているとかではない • Main で起動して動かしっぱなしにしているとかではない fetch 内で WASM の instance を立ち上げて WASM に実装されているメソッドを呼び出す https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/ 54
  36. ライブラリの API と WASM 事情 WASI には 2 種類ある (Preview1

    の場合) • Command (exe) • _start() が export される • _start() は引数なし、返り値なし • _start() 終了後は、その instance からexport されている メソッドを叩いてはいけない • Reactor (lib) • _initialize() が export される • _initialize() を呼び出し終えたら instance から export されているメソッドを自由に叩いてよい https://github.com/WebAssembly/WASI/blob/d8b286c697364d8bc4daf1820b25a9159de364a3/design/application-abi.md 55
  37. ライブラリの API と WASM 事情 WASI には 2 種類ある (Preview1

    の場合) • Command (exe) • _start() が export される • _start() は引数なし、返り値なし • _start() 終了後は、その instance からexport されている メソッドを叩いてはいけない • Reactor (lib) Cloudflare Workers 的には こちらが望ましい • _initialize() が export される • _initialize() を呼び出し終えたら instance から export されているメソッドを自由に叩いてよい https://github.com/WebAssembly/WASI/blob/d8b286c697364d8bc4daf1820b25a9159de364a3/design/application-abi.md 56
  38. ライブラリの API と WASM 事情 WASI には 2 種類ある (Preview1

    の場合) • Command (exe) • _start() が export される • _start() は引数なし、返り値なし • _start() 終了後は、その instance からexport されている メソッドを叩いてはいけない • Reactor (lib) Cloudflare Workers 的には こちらが望ましい しかし C# 側で top-level statements 使いたい… • _initialize() が export される • _initialize() を呼び出し終えたら instance から export されているメソッドを自由に叩いてよい https://github.com/WebAssembly/WASI/blob/d8b286c697364d8bc4daf1820b25a9159de364a3/design/application-abi.md 57
  39. ライブラリの API と WASM 事情 WASI には 2 種類ある (Preview1

    の場合) • Command (exe) • _start() が export される • _start() は引数なし、返り値なし • _start() 終了後は、その instance からexport されている メソッドを叩いてはいけない • Reactor (lib) Cloudflare Workers 的には こちらが望ましい 加えて 非同期に 対応するには Reactor にするしかない (後述) しかし C# 側で top-level statements 使いたい… • _initialize() が export される • _initialize() を呼び出し終えたら instance から export されているメソッドを自由に叩いてよい https://github.com/WebAssembly/WASI/blob/d8b286c697364d8bc4daf1820b25a9159de364a3/design/application-abi.md 58
  40. ライブラリの API と WASM 事情 C# の OutputType は Exe,

    WASM は Reactor にしたいのですが… • できます!!! https://github.com/dotnet/runtimelab/blob/feature/NativeAOT-LLVM/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets 59
  41. WASM における Interop 事情 最終的な JavaScript のイメージ (あくまでイメージ) V8 isolates

    は 複数リクエストで 使い回されるので WASM の instance はキャッシュ 63
  42. WASM における Interop 事情 最終的な JavaScript のイメージ (あくまでイメージ) V8 isolates

    は 複数リクエストで 使い回されるので WASM の instance はキャッシュ リクエストを実際に処理するための WASM 側のエントリーポイント 64
  43. WASM における Interop 事情 最終的な JavaScript のイメージ (あくまでイメージ) V8 isolates

    は 複数リクエストで 使い回されるので WASM の instance はキャッシュ リクエストを実際に処理するための WASM 側のエントリーポイント どうやって C# から WASM 側に export するか? 65
  44. WASM における Interop 事情 C# のメソッドを WASM で export する

    UnmanagedCallersOnly をつける事で export 可能 67
  45. WASM における Interop 事情 C# のメソッドを WASM で export する

    引数は全て int UnmanagedCallersOnly をつける事で export 可能 68
  46. WASM における Interop 事情 Interop どうするの? C# のメソッドを WASM で

    export する 引数は全て int UnmanagedCallersOnly をつける事で export 可能 69
  47. WASM における Interop 事情 Interop の前に WASM Core の仕様 •

    4 種類の primitive type しか存在しない • i32 / i64 / f32 / f64 • WASM では Linear memory というところでメモリ管理する • Primitive type 以外は全て Linear memory を介してやりとりする必要がある 70
  48. WASM における Interop 事情 Interop 例: JavaScript 側から文字列 (not primitive)

    を取得する C# instance.exports.memory.buffer が Linear memory のバッファ JavaScript 73
  49. WASM における Interop 事情 Interop 例: JavaScript 側から文字列 (not primitive)

    を取得する C# instance.exports.memory.buffer が Linear memory のバッファ JavaScript C# 側で取得したポインタがそのまま Linear memory のバッファの インデックスとなる 74
  50. WASM における Interop 事情 C# から JavaScript のメソッドを呼ぶ C# DllImport

    + WasmImportLinkage を設定 JavaScript Instance を new する時に バインド先を設定する 77
  51. 非同期対応 非同期は C# (WASM) 側と JavaScript 側を行き来させる • await できるものは基本的に

    JavaScript 側の Promise • WASM はシングルスレッドなので元より Task.Run とか使えない • C# 側からは Promise が完了したら呼び出される continuation を用意 • それを JavaScript 側に continuation を登録 • そして制御を JavaScript 側に戻す • JavaScript は Promise が完了したら WASM から export されているメソッドを呼び出し WASM 内で continuation を発火させる 80
  52. 非同期対応 非同期は C# (WASM) 側と JavaScript 側を行き来させる • await できるものは基本的に

    JavaScript 側の Promise • WASM はシングルスレッドなので元より Task.Run とか使えない • C# 側からは Promise が完了したら呼び出される continuation を用意 • それを JavaScript 側に continuation を登録 • そして制御を JavaScript 側に戻す C# (WASM) と JavaScript を 行き来するので Reactor である必要がある • JavaScript は Promise が完了したら WASM から export されているメソッドを呼び出し WASM 内で continuation を発火させる 81
  53. まとめ cloudflare-workers-dotnet を公開しました! • C# で Cloudflare Workers 向けアプリを構築できるように!! •

    最低限必要なものは一式実装済みしかし Cloudflare の膨大なサービス全てのバインドを提供するには至らず… • 流石に物量が凄い • D1 も最低限のバインディングは提供しているが全 API には至っていない • Future work • Microsoft.Extensions 系資産の統合 Interop は全て public なので ライブラリユーザが 自前実装可能なものの流石に面倒… • Future work https://github.com/nenoNaninu/cloudflare-workers-dotnet 92