はじめに
1つの Rust 製描画エンジンを、2つの入口から使う構成を紹介します。
- デスクトップアプリ:Avalonia UI(C#)で作るデザイナー
- エディタ拡張:VS Code 拡張(TypeScript)で作るビューア
この構成で、次の2種類の「マルチ」に対応するときに得た実践的な知見をまとめます。
- マルチプラットフォーム:Windows / macOS / Linux × x86_64 / ARM64 の6通り
- マルチベンダー:メーカーごとにプリンタ言語が異なるラベル/レシートプリンタ
以下のクレート名・リポジトリ名はすべてサンプル名です。
1. 全体構成
描画エンジンは Rust 1本です。UI からの入口を2つ用意し、出力先もプレビューとプリンタの2系統を持ちます。
report_core (Rust workspace)
├ report_ffi : C ABI の cdylib (.dll/.so/.dylib) → Avalonia アプリ (C#)
├ report_node : NAPI-RS の .node → VS Code 拡張 (TypeScript)
└ 出力 : プレビュー (Skia) / プリンタ各社のコマンド
ロジックは report_core にだけ置き、バインディング層は薄く保ちます。こうしておくと、UI が増えてもエンジン側の修正は1か所で済みます。
2. Rust エンジン側
- ワークスペース分割:コア、レンダラ、FFI、Node バインディングを別クレートに分けます。機能を足すときは既存関数を書き換えず、新しい公開関数を追加する形を基本にします。
- panic を FFI 境界を越えさせない:NAPI-RS の境界で panic するとアンワインドできず、Node プロセス(VS Code の拡張ホスト)ごと落ちます。エラー表示も出ないため、原因の特定が非常に難しくなります。フォントが見つからない、ファイルがない、といった「起こり得る失敗」は、panic にせず警告ログとフォールバックで処理します。
- フォントのフォールバック:OS ごとにフォント事情が違います。1文字ずつグリフが存在するかを確認し、なければ代替フォントに切り替えると、豆腐(□)表示を防げます。Linux 向けには CJK フォントを同梱しておくと確実です。
3. Avalonia UI 側
- C# からは
DllImport/LibraryImportで cdylib を呼びます。 - 配布物は
dotnet publish -c Release -r <RID> --self-containedで作り、出力フォルダに該当 OS の cdylib を同梱します。 - macOS:署名と公証(notarize)がないと Gatekeeper でブロックされます。無署名で配布するなら、README に「右クリック → 開く」の手順を書いておきます。
- Windows:無署名だと SmartScreen の警告が出ます。
4. VS Code 拡張側
-
ネイティブモジュールの読み込み:
requireで.nodeを読み込む処理は、必ず try/catch の中に入れます。外に出すと、読み込み失敗が黙って握りつぶされます。 -
NAPI-RS 出力名の罠:
napi build --releaseの出力はindex.nodeです。拡張が別名で読み込む設計なら、ビルドのたびに名前を変えてコピーする必要があります。忘れると、古いバイナリのままパッケージされてしまいます。 -
プラットフォーム別 vsix:
vsce package --target win32-x64のように、OS ごとに別々の .vsix を作ります。 -
Linux の Snap 版 VS Code は非対応:Snap のサンドボックス内は glibc が古い版で固定されているため、ネイティブモジュールが読み込めません。
activate()で Snap を検出してメッセージを出し、README には deb/rpm 版が必要と明記します。 -
Cursor 互換:
engines.vscodeの下限を上げすぎると Cursor に入りません(例:^1.85.0程度に抑える)。 -
キーバインド:Linux の GNOME では
Ctrl+Alt+Dがデスクトップ表示と競合します。macOS では Option+英字が特殊文字の入力に取られます。全 OS で安全なのはCtrl+Alt+数字系です。 -
公開前チェック:
- Open VSX はライセンスのない拡張を拒否します。
LICENSE.txtを用意し、"license": "SEE LICENSE IN LICENSE.txt"を書きます。 -
.vscodeignoreでテスト用の PDF などを除外しないと、.vsix が肥大化します。 - Marketplace の
nameとdisplayNameは、検索に出なくても予約済みで使えないことがあります。候補を複数用意しておきます。 -
package.nls.jsonとpackage.nls.ja.jsonなどでコマンド名を多言語化できます。
- Open VSX はライセンスのない拡張を拒否します。
5. マルチプラットフォーム(OS × CPU アーキテクチャ)
Windows・macOS・Linux の3つの OS と、x86_64・ARM64 の2つの CPU アーキテクチャを組み合わせると、ビルド対象は6種類になります。
| OS / CPU | VS Code --target
|
.NET RID | Rust ターゲット |
|---|---|---|---|
| Windows x64 | win32-x64 | win-x64 | x86_64-pc-windows-msvc |
| Windows ARM64 | win32-arm64 | win-arm64 | aarch64-pc-windows-msvc |
| Linux x64 | linux-x64 | linux-x64 | x86_64-unknown-linux-gnu |
| Linux ARM64 | linux-arm64 | linux-arm64 | aarch64-unknown-linux-gnu |
| macOS Intel | darwin-x64 | osx-x64 | x86_64-apple-darwin |
| macOS Apple Silicon | darwin-arm64 | osx-arm64 | aarch64-apple-darwin |
- 実機でビルドと検証を行う方針:描画の正確さは実機で確認するのが確実です。
- ARM の Windows と Linux:1台の Windows ARM 機でまとめて扱えます。Windows 側で win32-arm64 を、同じマシンの WSL2 Ubuntu で linux-arm64 をビルド・検証します。
- Linux:古めの Ubuntu でビルドすると、要求する glibc を低く抑えられます。
-
macOS のハマりどころ:
-
sudo xcodebuild -licenseに同意していないとリンクが失敗します。 - rustup が古いと edition2024 のクレートがビルドできません。
-
- ソース管理:各マシンは GitHub から clone します。フォルダのコピーによる同期はやめます。
6. マルチベンダー(ラベル/レシートプリンタ)
1つのエンジンで、メーカーごとに異なるプリンタ言語へ出力します。
帳票定義 + データ
↓
共通の中間描画モデル(特許出願中)
├ プレビュー : Skia で PNG/PDF
└ プリンタ出力: プロトコル別レンダラ
ESC/POS / StarPRNT / SBPL / ZPL / TPCL / TSPL / DMX …
- レイアウトは1回だけ計算する:中間モデルまでは全メーカー共通です。メーカーごとの差分は、出力段のレンダラだけに閉じ込めます。新しいメーカーに対応するときは、レンダラを1つ追加するだけで済みます。
- 座標単位の違いは出力段で吸収する:単位はプロトコルごとに異なります(ドット、0.1mm など)。内部の単位は統一しておき、変換は各レンダラの中で行います。
- 機種固有のずれは補正パラメータで持つ:メカ上のセンタリングずれや左余白のずれは、機種ごとの補正値として設定から調整できるようにします。
- 文字コードは同じプロトコルでも機種で違う:同じ ESC/POS でも、漢字モードの指定コマンドはメーカーによって異なります。
- プリンタ内蔵フォントの限界:出力はプリンタの内蔵フォントを使います。そのためプレビューが正確に再現できるのは、配置・折り返し・バーコードの座標までです。文字の形や字間は実機での確認が必要です。
- 画像の扱いを使い分ける:画像を転送するプロトコルと、プリンタに事前登録したグラフィックを名前で参照するだけのプロトコルがあります。
- 対応状況を公開する:メーカー・機種・プロトコル・検証済みかどうかを CSV で公開します。アプリ側からその CSV を取得すれば、ユーザーは検証済み機種を確認できます。
7. 配布の自動化(ビルドは実機、公開は Actions)
- 各実機で .vsix をビルドし、動作を確認します。
- 配布用リポジトリに Draft リリースを作り、全ターゲット分の .vsix を添付します。
- 「Publish release」を押すと、
.github/workflows/release-to-stores.ymlが起動します。 - Actions が添付ファイルを取得し、全ターゲット分が揃っているか、バージョンが一致しているかを検証します。
- 検証を通れば
vsce publish --packagePathとovsx publishを実行します。どちらも--skip-duplicateを付けておくと、再実行しても安全です。
トークンは VSCE_PAT(Azure DevOps、Marketplace の Manage 権限)と OVSX_PAT を、配布用リポジトリの Secrets に登録します。
Avalonia アプリも同じ Draft リリース方式で配布できます。ストアに公開しないので、Actions は不要です。
8. リポジトリ運用
- ソースと配布物は別リポジトリにします(例:
report-designer-srcとreport-designer-release)。 - ソースは Private、配布用は Public にします。