C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

WSL の内側の世界から、外側の Windows の世界の PATH が "何もしていないのに" 通っている件、をどうにかしたい場合のメモ

WSLの内側の世界では、外側の世界の Windows で実行可能なバイナリー「も」実行できます。

実際この機能はしばしば便利な事もあります。例えば、

  • 諸事情によりWSLの内側の世界で作業中に
  • PE32+ (†1) なバイナリーファイルを
  • nm (†2) したいけど ELF (†3) じゃないのでもちろんできないので
  • 仕方がないので dumpbin (†4) しなければならないが
  • わざわざWSLの外側の世界で cmdpowershell で実行するのは面倒 (†5)

みたいな事が起こるユーザーにはとても便利です。 PATH 通すか aliasfunction で呼べるようにしておけばね💪

そういうわけで PATH の話に無理やり繋げました。 WSL の内側の世界では Windows (のWSLの仕組み) が "気の利かせ" て Windows の世界の PATH を WSL の内側の Linux の世界へ全自動デフォルトで注入してくれます。野暮なお節介ババア的な感じで。すごくいい人だけどいい人すぎて余計なお世話のトラブルメーカー的な。

つまりこういう事が起こる:

  1. WSL の内側の世界でプラットフォームのパッケージマネージャーを介して yarn を入れてあるとしましょう:
  << which yarn
/sbin/yarn
  1. その yarnglobal bin 置き場は default な具合でこうなっていて、なにか適当なアプリが入っていたとしましょう (↓の例では neon; 例なのでなんでもいいです ):
  << yarn global bin --offline
/home/usagi/.yarn/bin

  << ll $(yarn global bin --offline)
total 8.0K
drwxr-xr-x 2 usagi usagi 4.0K 2020-03-22T13:20:24 ./
drwxr-xr-x 3 usagi usagi 4.0K 2020-03-20T22:04:21 ../
lrwxrwxrwx 1 usagi usagi   48 2020-03-22T13:20:24 neon -> ../../.config/yarn/global/node_modules/.bin/neon
  1. ところがこの yarn を介して導入したはずのアプリを実行すると何か奇妙な現象に見舞われて、原因を探るためにまず which とか readlink とか headhexdump
  << which neon
/mnt/c/Users/usagi/AppData/Local/Yarn/bin/neon

/(^o^)\

みたいな事が起こります。起こりました。単独で他のファイルに絡まずに実行可能なスクリプトでは実害のある問題は起こらない事もありますが、そうではない場合はわりと危険が危ないです。これについて、ぱわーのあるユーザーなら PATH について .zshrc 的ないつものところへ PATH=$( 💪 MY AWESOME BLACK MAGIC BE DESTROY AUNT MEDDLESOME WINDOWS GIVEN HOLY PATH 💪 ) みたいなみなぎる筋肉をぶつけていくスタイルでマッチョに解決していた時代も実際あったようです。 perl でも php でも awk でも python であれ、何か得意な黒魔術で対処療法の時代。

現在は既に対処療法ではなく遺伝子操作やワクチンによる解決が実用可能になっていて:

WSL 内へ摂取する"ワクチン的"でお手軽な方法として:

  1. /etc/wsl.conf を書く:
[Interop]
appendWindowsPath = False
  1. 管理者権限の powershellRestart-Service LxssManager しておく ( 全ての WSL 環境は安全に閉じてから↑の設定後に1回する )

f:id:USAGI-WRP:20200322150610p:plain

これだけでよくなったようです。🎉

f:id:USAGI-WRP:20200322150804p:plain

外の世界の Windows のお節介による PATH が消えたので先の例の WSL 内 yarn を介して導入した neonwhich でも確認できるようになりました。 yarn だけなら💪的に export PATH="$(yarn global bin --offline):$PATH" だけでも事実上は解決できるのだけど、それだけ、という事ではなく解消しておきたい場合には一般的に WSL のお節介機能を無効化するのが安全です。

"遺伝子操作"的なやつがお好みの場合は regedit して HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Lxss のなかをもぞもぞする方法もあるようです。私は楽にすませたいので /etc/wsl.conf したのでこちらは試しませんでしたけれど。

reddit で話題になった Tauri と NEON についてのメモ、ついでに template-rust-backend-with-electron-frontend の開発理由についてのメモ

reddit の Rust コミュニティーtemplate-rust-backend-with-electron-frontend のリリースについてポストしたところ、主に2つ、 Tauri と NEON について話題になりました。( up-vote たくさんとちょうど使いたかったんだありがとう的なポストも頂きました。幸いです。 )

1. Tauri vs. Electron ( 間接的には template-rust-backend-with-electron-frontend にも関連します )

こちらは template-rust-backend-with-electron-frontend にとっては via via 的な間接的でややオフトピックよりな感じの内容ではありますが、特徴的な部分についての対決姿勢的な議論が生じていました。主な主張は次の2通り(意訳と補足を含みます):

  1. Tauri 上げ上げ系
    • Tauri なら高速でフットプリントも高々6MBに収まるよ、 Electron はすごい重いっす😭
  2. Tauri じゃない理由を推論してくれる系
    • Tauri のフットプリントが小さいのはブラウザーがバンドルされておらず、OS が提供するブラウザーを WebView として使うから。つまり…
      • 動作や見た目が実行環境によって変わる可能性(ブラウザーの種類やバージョンによる互換性問題)をアプリ側で考慮する必要が生じる
        • Electron だと WebView をバンドルする特定版の Chromium に決め打ちできる (少なくともユーザー環境に依存したブラウザーの互換性問題は起こらない)
        • 実際問題: OSX -> Safari が起動、 WindowsMSHTML が起動するとつらい😭 (まともに更新されている一般的なWindows 10しか考慮しなくていい世界ならEdgeだけどね😜)
    • Tauri はまだ若く TODO な部分がまだたくさんあるんだ

vs. な姿勢については、 Rust バックエンドに Web 系テクノロジーのフロントエンドをマーシャリングする選択肢について「でも、現時点の選択肢としては両方からどちらもかんたんに選べるのが嬉しいじゃろ」と思います。 Tauri が順調に TODO を消化し、その上でさらにビルド・プロセスで特定のブラウザーをバンドルする能力を獲得する日が来たら、 Tauri は Electron に対してほとんど上位互換的な技術要素になり得ますが、 TODO 消化はともかくバンドル能力の獲得は恐らく Tauri の開発思想的に無さそうな気はします。

Tauri は Rust のフロントエンド向け技術要素として現状に対しては良い選択肢の1つだと思います。名前が STARGATE の地球の人の意味の Tau'ri っぽいのも面白い点です。しかし、ブラウザー互換性問題への対処可能性がアプリ開発のプロジェクトチームの負担となる事は、特に「日本企業」とか「役所」とか「瑕疵担保」などを考える場合には2020年現在もまだネガティブな要因として考慮から外せないかなって思います。そうした懸念の低い分野のプロジェクトでは積極的に採用しても面白いのかな、と思います。

また、 Tauri の公式 README.md を見る分には TODO もそれほど困るコア機能的な部分は既に無さそうには見えます(どう見ても OSX 好きな開発者が中心になっているので WindowsGNU/Linux ではうまく動かない機能とかある"かも")。 Tauri vs. Electron については reddit で生じていた議論も核心の1つではありますが、 Tauri 公式の README.md の Comparison between Tauri and Electron が網羅的でわかりやすい1つ比較例として有用そうです:

参考: https://github.com/tauri-apps/tauri

  1. フットプリント: 例えるなら、
    • Electron はスイス・アーミー・ナイフ、
    • Tauri は刃先は現地で調達した何かを挿して使えるナイフの柄みたいなものかなーと思うので、「Tauri、せやろな」と素直に思います
  2. Interface Service Provider:
    • 多様性とユーザーへ選択肢を与える事は思想的にも、もしもの「こんなこともあろうかと」への備えとしては理想性が高いけれど、
      • 現実は reddit でも議論になったように可能性があるだけでもしんどい上に実際にもブラウザー互換性問題はしんどい部分がまだ捨てきれません
    • 開発チームの現実の負荷と生産性へのデメリットの視点からは Tauri は不利に思います
  3. Backend Binding:
    • Tauri vs. template-rust-backend-with-electron-frontend ではどちらも Rust になるし、
      • どちらもネイティブなバイトコードが実行されるので差はないですね
  4. FLOSS:
    • Electron も現実的に問題になる可能性は既に極めて低いので、
    • あとは宗教的/政治的な意図で Free/Libre 互換性を気にしたいかどうか次第じゃないかな
  5. Multithreading:
    • Tauri が Yes と堂々と書いていてつよいなーとは思います。技術変態的な用途で遊びたいなら Tauri が楽しいかもしれません。
    • ただ、実用性としてはそもそもデスクトップアプリのフロントエンド部分としての Electron について、 ES の Worker、そもそもの ES の実行効率、また Tauri が堂々と Yes と言うからにはそこにはブラウザー互換性とそれをカバーするであろうサブシステムもあるのでしょうし、遊びかなーって思います。
    • バックエンド部分を分離したアーキテクチャーでフロントエンド部分で MT が活きる用途というのは実際には非常に限られます。もしレンダリングをMT化できれば嬉しいのでは、と思う人もいるかもしれませんが、OSの描画コンテキストを生で扱える処理系でもレンダリングのMT化はまだまだ極めて困難な技術です。仮にGLコンテキストを使うならWindowsではGLコンテキストのMT対応は不十分ですし、そもそもプラットフォーム依存という仕様で困難が大きいです。加えて、ブラウザーで標準化された Web Worker (厳密にはこれはJavaScriptECMAScriptの仕様ではなくブラウザー側で追加されるWeb APIに含まれる仕様です)は MT としてはメッセージングベースで…閑話休題
  6. Auto Updater:
    • Electron にはあります。製品の要求にこの機能が欲しい事はしばしばあるので便利です
    • Tauri も Soon らしいので期待したいですね。
  7. Android, iOS:
    • Electron の弱点の1つかもしれません
    • この点は「 Tauri なら」と言える強みかもしれません
  8. No localhost option:
    • Electron の実行環境がマルチユーザーなシステムの場合にファイアーウォールを比較的高度に設定できないと脆弱性になり得えます
    • この点も「 Tauri なら」と言えそうです
      • もし開発するアプリのリリースターゲットがマルチユーザー同時ログオンを考慮する必要があって、その上でリッチなGUIデスクトップアプリを実行させたいなら、というニッチな状況も想定しなければならないなら、だけど
  9. Desktop Tray:
    • Tauri では Soon
    • Electron では No
      • native バックエンドを持つアプリなら必要に応じてトレイ常駐的な機能の後付けはどうとでもならない事もない
      • プロセス内から実現しにくい場合はトレイ用の別プロセスを立ててIPCすればそれほど難しい機能ではありません (クロスプラットフォーム対応したいとなると少々しんどくなります)

以上の比較表の外で Tauri の TODO から気になるのは Multiwindow Mode です。「にゃんにゃんしますか?(でんっ)」みたいなダイアログも原理的にはマルチウィンドウなのだけど、そういう部分なのか、また少し違ったニュアンスなのかは調査してみないとわかりませんが、チョット気になります。

(1と2のハザマ): template-rust-backend-with-electron-frontend の開発理由

私が template-rust-backend-with-electron-frontend を準備したモチベーションはそもそも…

  • WebView 系フロントエンド技術要素の開発
  • Electron や関連プロジェクトへのコミット
  • Tauri や関連プロジェクトへのコミット
  • FLOSS の普及/布教または単にボランティア・ハートによる奉仕したみ
  • Rust バックエンドに対応する GUI Toolkit の開発
  • 何であれとにかく GUI Toolkit 的なフロントエンド技術の開発

ではなく、寧ろそのようなあたりへ触れている人生の資源の余裕は残念ながら無いので、

  • Rust でアプリ作りたいけど
  • GUI Toolkit 的なフロントエンド技術の成熟がまだ弱い
    • Qt や Gtkバインディングはあるけど高価 or Free/Libre なライセンス汚染が生じてしまう ( OSS 作りたいだけならいいんだけど… )
    • conrod だとビジネス的なGUI部品やデフォルトでの見栄え的に弱い (日本語の入出力ちゃんとできてすごいし、ゲーム開発ならGUI部品も絵作りから独自にやりたいからその用途で使うなら問題にならないんじゃが…)
    • OrbTkdruid にも期待したいけど、"今"使うには若すぎる…せめて日本語の入出力…ウィジェット(≃コントロール)もモウチョット欲しい… (作ったりミコットしたりしつつ、それを使った製品本体も作る余力はない用途にも使いたいんじゃ…スマヌ…)
  • どうせ "今を凌ぐ仮のフロントエンド技術" を採用するなら…
    • できるだけバックエンドとは疎結合ですげ替えやすさを維持できる要素技術だと嬉しい
    • あれがない、これはできない、それは作らないといけない…そうした事ができるだけ少ない成熟した技術をぽんっと使っておきたい
      • (=プロジェクトチームの生産性を "仮のフロントエンド" にできるだけ注ぐ必要が少ないと嬉しい)
    • ルック&フィール、意匠設計的な部分に手を付けるのにフレームワークの深い理解が必要ない方が嬉しい
      • ("顧客が本当に必要だったGUI" への要求に手早く簡潔に対応できるものだとなお嬉しい)
    • 表示とUIとしての入出力部分だけきちんとうまいことやって貰えたら、あとはバックエンドの開発力でどうとでもするぞい💪💪💪

と、いうわけで、そういう事なら…

  • .NET Core/WPF/Prism
  • Node.js/(...brabrabra...)/React/Electron

どっちかが私や関連するプロジェクトチームのスキルセット的にも有利な選択肢ネ、でも Rust 純度高い技術の可能性も捨てたくは…やっぱり少なくとも今はまだダメでした…となった流れと、 .NET Core の WPF だと Windows 以外へのクロスプラットフォーム・ポータビリティー的に不安しかない、 Node.js/React/Electron 系を採用しておけばバックエンド Rust と融合させて Windows, OSX, GNU/Linux での心配は少ないし、チョットどうにかすれば Web サービス、さらにモウチョットどうにかすれば Android / iOS 向けにも fork しやすい…ハズ…。

と、いうことでにゃんにゃんしてできたのが template-rust-backend-with-electron-frontend です。要素技術のシンプルなマーシャリングを実現しただけの状態のプロジェクトテンプレートはバックエンドプロジェクトに余計な束縛を与えない事も大事な要素です。もちろん、ある程度はアレ向け、ソレ向け、と用途にフォーカスした派生プロジェクトテンプレートも用意したいとは考えていますし、できれば Rust cdylib と Electron の間のマーシャリング・コードの自動生成機能を付けたい気持ちもあります。但し、しつこいようですが、バックエンドの Rust コードが主体で、その開発に制約を設けたり、特定のツールやビルド方法への強めの束縛は取り込みたくありません。

2. NEON; Rust と Node.js を繋ぐマーシャリング技術

NEON 自体は Rust と Node.js のマーシャリング技術要素部分に注力した「補助的なビルドツール」のプロジェクトです。

実用していなかったので単純に存在を忘れていました、という正直なところはあるのですが、 reddit で「どうして NEON 使わないんだい?」的な議論から試みました。いい感じに template-rust-backend-with-electron-frontend へ組み込めたら素直に嬉しい技術です。

Windows で試したところ、開発環境への NEON ツール群の導入は簡単にできたものの、プロジェクトテンプレートを作成するコマンドライン・インターフェースが挙動不審で制御が帰ってこなくなりました😭

誰も助けてくれない気配を感じたので NEON の repos を git clone して NEONcli 部分についてコードを確認、 TypeScript と判明したのでデバッガー(今回は VSCode を使いました)に必要な設定の変更と追加をして適当にブレークポイントを挿しつつステップ実行してみました: -> https://github.com/neon-bindings/neon/issues/406#issuecomment-601832883

結果: console.log() で制御が行方不明になる -> https://github.com/neon-bindings/neon/issues/406#issuecomment-601832883

原因がわかればもちろん修正して PR したいところでしたが、わたしの Node.js / TypeScript のワカリでは状況に対して修正すべき原因に適切な直感が綺麗には働きませんでした。残念。

ただ、不幸中の幸い、この問題はどうやら neon new の主だった処理を終えた後、残すところフレーバー的な表示の出力だけになった状態で発生する、実質的にはそれがわかっていれば実害は無い部分という事もわかりました。WSL2 を使えば Windows と共有するファイルシステム上にも neon new 期待動作できる事は先の Issue #406 へのレポートのようにわかっていましたし、さいあく neon new できなくても neon build --release できるプロジェクトをどうにかできれば NEON 自体は WSL2 も使えない Windows 開発環境のメンバーがプロジェクトチームに居たとしても大丈夫…かも…などなどありましたが、その前提の "さいあく" はとりあえず回避できました。

  • neon build --release

これで "Hello, world" 的な .js は出力できました。build では new のような問題が起こらなくて一安心です。続いて、NEON 公式の docs の確認を進めると、

を見つけました。「ぉ、これはオレオレテンプレートからもりもりしたり、 neon new から Electron/React/その他いっぱいのプロジェクト初期状態の構築や依存関係のうにゃーとかせずに使えて便利になるのかも?」とちょっと期待しました。

ダメでした…。よく見ると neon-hello は4年も保守されていません。 昨年あったらしい PR #1 にも Author の応答が無さそうです。お手軽にはじめるテンプレート・プロジェクト的な部分だと思うので、手動でどうにかすればいいのかも、とも思いますが…

  1. 少なくとも "いま" フロントエンド部分の要素技術に手間暇を割きたくはありません ( 趣味のOSS開発&コミット活動としてなら何であれ参加できるのはたのしーのだけど… )
  2. neon build --release しかできない束縛ちょっと嫌かなぁ…
    • たぶん NEON ではマーシャリング部分のみ neon して、結果的には3層構造の Node.js 処理系 <--> neon した .rs から .js と cdylib のマーシャリング部分 <--> .rs pure な rlib なアーキテクチャーを前提にしているのかなーと思うので、そのために --release しか受け取らないのかなーとは感じるものの

加えて、NEON本体のソースについても要素技術の更新がかなーーり古いまま保守されていない部分もあり、「うーむ、これなら… template-rust-backend-with-electron-frontend にマーシャリング・コードの自動生成機能を cargo か npm / yarn のカスタムビルドでオレオレ機能追加した方がいいかなぁ…というお気持ちも。 NEON と良し悪しで戦う気はありませんが、アプローチの思想的にも、アーキテクチャー的にも差異を感じつつ、もう少し NEON については試しておこうかな…といった具合です。いまのところ。

RustのcdylibをバックエンドにElectronをフロントエンドにするcargo-generateプロジェクトテンプレートを公開したメモ

「RustのcdylibをバックエンドにElectronをフロントエンドにする」プロジェクトを3秒くらいでお手軽作れるcargo-generateできるプロジェクトテンプレートを公開しました。

f:id:USAGI-WRP:20200320172806p:plain

使い方:

cargo generate --git https://github.com/usagi/template-rust-backend-with-electron-frontend.git

使い方としての細かいことは README.md#Usage を読んでください。

このメモの主体は↑の使い方ではなく、作る最中に遭遇したトラブルシュートのメモです。

作るまでに遭遇したトラブルたち

  1. Rust の GUI Toolkit を採用したプロジェクトを作ろうと思ったけれど:
  2. Electron を GUI フロントエンド部に採用しようと思い:

こんな事がありました。ありましたが💪で解決できたのでとりあえずこの Rust をバックエンドに cdylib を書いて Electron をフロントエンドにイケイケできるプロジェクトのテンプレートを cargo-generate できるように公開しました。👍

ちなみに、この template-rust-backend-with-electron-frontend で GUI 部分をイケイケに作り込む場合はさらに TypeScript やクライアントサイド向けのフレームワークなどの要素技術スタックがアーキテクチャーに仲間入りしたり、 React を Angular / Vue へ変えたくなる事もあるかもしれません。🤔 バックエンド部の Rust も何をするアプリかにより crate を追加し、ビジネスロジックをにゃんにゃん書きます。もちろん、最小限の試作程度ならこの CSS なにそれ美味しいの的な HTML で GUI をごりっと書いて Rust でビジネスロジックの核心的な部分を書いて十分、それもアリです。

全ての技術要素を完全に理解して使う事は難しいと思いますが、このテンプレートを使えば Rust と Electron で可能性は無限大かつ現実的なやつがすぐに作れちゃうぞ的な美味しい感じでわくわくプロジェクトを始められると思います。たぶん。Rust 純度高めでも QML みたいなお手軽GUIホイホイできるようになるその日まで。(ΦωΦ)フフフ…

react-electron-ffi-(native dll/so/dylib) プロジェクトを作るメモ (2020-03-19版)

(1/2) react-electron

ここは今回のメモの本編ではないものの、"やり方"がころころ変わるようなので一応現時点での方法をついで程度に整理します。

(1) プロジェクトのディレクトリー(=リポジトリー)を create-react-app で生成:

npx create-react-app myapp
cd myapp

(2) electron 系のパッケージを追加:

yarn add electron electron-builder wait-on concurrently --dev
yarn add electron-is-dev

(3) public/electron.js を作る:

const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

const path = require('path');
const isDev = require('electron-is-dev');

let mainWindow;

function createWindow() {
  app.allowRendererProcessReuse = true;
  mainWindow = new BrowserWindow({width: 1920/4, height: 1080/4, webPreferences:{ nodeIntegration: true } });
  mainWindow.loadURL(isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}`);
  mainWindow.on('closed', () => mainWindow = null);
}

app.on('ready', createWindow);

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  if (mainWindow === null) {
    createWindow();
  }
});

(4) package.json に electron 用のお約束を追記:

(以下のコードはmergeする追記分のみ)

{ "main": "public/electron.js"
, "scripts":
  { "dev": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electron .\""
  }
}

ここまでの時点で yarn dev で package.json に追記した scripts の dev を呼ぶと一応 react-scripts を介して react-electron が期待動作してくれるはず。しなかったらこのメモの作成時点とは何か変化が必要にどこかが変わっている可能性があります。 Node.js, electron, Chromium など要素技術の変化はかなり激しいので、そういう事があっても慌てず問題を探りましょう、未来のわたしへ。

(5) rescripts を導入し eject せず create-react-app を扱える様に調整:

(5-1) rescripts を install

yarn add @rescripts/cli @rescripts/rescript-env --dev

(5-2) package.json / scripts の start, build, testreact-scripts から resccripts へ変更:

(以下のコードは該当部分の変更後のみ)

{ "scripts":
  { "start": "rescripts start"
  , "build": "rescripts build"
  , "test": "rescripts test"
  }
}

(5-3) package.json / scripts に postinstall, preelectron-pack, electron-pack を追加:

(以下のコードはmergeする追記分のみ)

{ "scripts":
  { "postinstall": "electron-builder install-app-deps"
  , "preelectron-pack": "yarn build"
  , "electron-pack": "electron-builder build -w"
  }
}

ここで electron-packelectron-builder build -w-wWindows 向けのパッケージングを有効にする引数。OSX向けを有効にしたければ -mGNU/Linux 向けを有効にしたければ -lWindowsOSXGNU/Linux 向けを同時に有効にしたければ -wml のように定義でき、プロジェクトに応じて変更します。必要ならプラットフォームのアーキテクチャーも --ia32, --x64 で設定できます。

(5-4) package.json / scripts から eject を削除

rescripts を使う場合 eject を使う必要は無いし、残しておいても事故の元になるだけなので scripts から完全に消し去ります。

(5-5) package.json に rescripts を追記:

(以下のコードはmergeする追記分のみ)

{ "rescripts": [ "env" ]
}

これを書かないと electron のウィンドウ(=client-side)は動いても react な中身(=server-side)が期待動作しなくなります。

(5-6) .webpack.config.js を追加:

module.exports = config =>
{
  config.target = 'electron-renderer';
  return config;
}

(5-7) .rescripts.js を追加:

module.exports = [require.resolve('./.webpack.config.js')]

(6) package.json にパッケージング用のメタデータを追加:

{ "author":
  { "name": "Author Name"
  , "email": "aurhot.email@example.com"
  , "url": "https://author-website.example.com"
  }
, "build":
  { "appId": "com.my-website.my-app"
  , "productName": "MyApp"
  , "copyright": "Copyright © 2020 ${author}",
  , "mac":
    { "category": "public.app-category.utilities"
    }
  , "files":
    [ "build/**/*"
    , "node_modules/**/*"
  ]
, "directories":
  { "buildResources": "assets"
  }
}

(7) assets / icon.png を作成

  1. assets ディレクトリーを作成
  2. 256x256 の icon.png を放り込む(アプリのアイコンになります)

以上で react-electron の 2020-03-19 頃の準備手順は完了です。 yarn dev で開発版を実行、 yarn electron-pack でリリース版パッケージを dist ディレクトリーへ生成できるようになっています。なっていなければ、このメモの作成時点とは何か変化が必要にどこかが変わっている可能性があります。 Node.js, electron, Chromium など要素技術の変化はかなり激しいので、そういう事があっても慌てず問題を探りましょう、未来のわたしへ。

動作を確認したら次の react-electron を react-electron-ffi-(native dll/so/dylib) へ進みましょう。

(2/2) react-electron から react-electron-ffi-(native dll/so/dylib) へ

react-electron を使わない Node.js で FFI して native dll/so/dylib を呼ぶ方法は1つ前回書いた Node.js と FFI の 2020-03-18 時点でのメモ; node-ffi 系 → node-ffi-napi 系 - C++ ときどき ごはん、わりとてぃーぶれいく☆ のように node-ffi 系ではなく node-ffi-napi 系を使う事だけ知っていればわりと簡単に yarn add ffi-napi ref-napi して、

var ffi = require('ffi-napi')
var ref = require('ref-napi')
var s = ref.types.CString
var nyanko = ffi.Library( 'nyanko.dll',{ 'f': [ s, [] ] } )
console.log( nyanko.f() )

のように使えばよいだけです。これを踏まえて、 (1/2) で作成した react-electron のプロジェクトで、

  1. yarn add ffi-napi ref-napi して
  2. src/App.js で前述のような FFI コードを記述して実行

すると:

f:id:USAGI-WRP:20200318225023p:plain

こんな具合で死にます。☠ Node.js, react, electron に慣れていない初心者にはわけがわからない上にググらビリティーに問題を抱えたエラーの様に感じられますが、わかってしまえばどうという事はなくなります:

(1) public/electron.js の new BrowserWindowwebPreferences.nodeIntegration を明示的に true に変更する:

  mainWindow = new BrowserWindow({width: 1920/4, height: 1080/4, webPreferences:{ nodeIntegration: false } });

これをやらないと次の(2)だけやっても "window.require is not a function" とか electron のウィンドウにエラーが表示される事になります。nodeIntegrationtrue にしないと Node.js 部分の機能が有効にならないため、 fs も使えないとかそういう事が起こります。そうなると ffi も使えません。(2/2) の最初の死亡状態で無いと言われていた exists はたぶん fsexists でしょう。そこに気がつければこの問題と原因と解決方法へ繋がります。

(2) src/App.js では window.require('electron').remoterequire して FFI する

const remote = window.require('electron').remote;
const ffi = remote.require( 'ffi-napi' );
const ref = remote.require( 'ref-napi' );

こんな具合で期待動作します。これで例えば:

let s = ref.types.CString;
let nyanko = ffi.Library( '../fuga/nyanko.dll',{ 'f': [ s, [] ] } ); // 例として前回適当に作った文字列値を返すだけのライブラリーを束縛
let mewing = nyanko.f();

としておいて、 function App()return{mewing} を:

          Learn React {mewing} !

など仕込んで実行すると:

f:id:USAGI-WRP:20200319013223p:plain

こうなります。成功👍

もし、 yarn dev する場合とパッケージングして実行する場合では native dll/so/dylib のパスが変わる場合は:

const is_dev = remote.require( 'electron-is-dev' );
const nyanko_path = is_dev ? '../fuga/nyanko.dll' : 'nyanko.dll';
let nyanko = ffi.Library( nyanko_path,{ 'f': [ s, [] ] } )

と electron-is-dev を使うと簡単です。呼び出す native dll/so/dylib もオリジナルに作ったものを使用したいプロジェクトではこのように対応します。あるいは、たぶん実行時の引数かシェル変数で渡す事にしておいて dev では package.json の scripts/start で特殊化しても対応できそうですが、私は今回 electron-is-dev で満足したのでその方法については思いついただけに留まります。

ついで、蛇足となりますが自分用メモとして、 electron-is-dev の値は Boolean なので、これを文字列で表示したい場合は {is_dev} ではなく {is_dev.toString()} しないと文字列では見れません。このメモを書いた時に一度「あれ、文字列には…」と期待動作しない状態を見てしまったので自分用備忘録。

参考

Node.js と FFI の 2020-03-18 時点でのメモ; node-ffi 系 → node-ffi-napi 系

今回のメモの Node.js は:

node -v
v13.5.0

です。先ずは Node.js 初心者らしく npm i ffi して死にました:

npm i ffi

f:id:USAGI-WRP:20200318171359p:plain

ほか多数のエラーを観測し、死んでしまいました。☠

f:id:USAGI-WRP:20200318170856p:plain

どんどん使われなくなっているので何か代替に置き換わったのだろうと思い調べてみると、 ffi は Node.js < 11 の時代で死んでしまったらしい事、 Node >= 11 用には fork した @saleae/ffi が対応しているらしい事がわかりました。

既にこちらも Weekly download が怪しいですが、とりあえず npm i @saleae/ffi してみました:

npm i @saleae/ffi

f:id:USAGI-WRP:20200318171506p:plain

死亡。☠

ここで元々の ffi の Issues を眺めてみると、

が見つかり、

  1. ffi-napi が Node >= 12 用に使えるらしい
  2. ffi-napi はコールバックの実装にまだ問題があるかもしれないらしい(このメモより14日前の情報)

事がわかりました。コールバックの問題点は、

  1. Fix crasher in callback calls by atishay · Pull Request #56 · node-ffi-napi/node-ffi-napi · GitHub (このメモより6日前に merged )
  2. Port fix for Multithreaded callback crash by atishay · Pull Request #50 · node-ffi-napi/node-ffi-napi · GitHub (このメモより2ヶ月くらい前に merged )

あたりについて、特に日付的に #56 の方の注意だと思います。現在は解決しているようです。とりあえず ffi-napi もインストールチャレンジしてみました:

npm i ffi-napi

しばらくのち:

+ ffi-napi@2.4.7
added 10 packages from 57 contributors, removed 4 packages and audited 17 packages in 18.686s
found 0 vulnerabilities

エラーなく導入できました。👍

実際に使う場合は型のバインディングref も欲しいのですが、こちらも node-ffi-napi シリーズの repos が一通りあるので:

npm i ref-napi します:

npm i ffi-napi

しばらくのち:

+ ref-napi@1.4.3
updated 1 package, moved 1 package and audited 23 packages in 5.993s
found 0 vulnerabilities

ref-napi も導入できました。👍

これでFFIの最低限の準備が整いました。適当に動作テストしてみます。

(1) テスト用に FFI で呼ばれる native な .dll (.so, .dylib) を作ります:

今回はなんとなく Rust で呼ぶと "にゃおーん" 文字列を返してくれる nyanko.dll を作ります。

# Windows でやっている場合は PowerShell で echo すると utf16 になるので 
echo '#[no_mangle] pub extern fn f() -> * const u8 { "にゃおーん".as_ptr() }' > nyanko.utf16.rs

# UTF8 に一手間増えます(Windowsの謎仕様でBOMが付きますが rustc はスルーしてくれます)
Get-Content nyanko.utf16.rs | Out-File -Encoding utf8 nyanko.rs

# このくらいなら Cargo さんに頑張ってもらわなくても rustc 直呼びで手早く済ませられます
rustc --crate-type cdylib nyanko.rs

これで Node.js から ffi-napi により FFI で呼ばれる側の nyanko.dll ができました。一応 nyanko.rs のソースだけ書き残しておくと

#[no_mangle] pub extern fn f() -> * const u8
{
  "にゃおーん".as_ptr()
}

(2) テスト用に FFI で呼ぶ側の Node.js な tester.js を作ります:

echo "console.log( require('ffi-napi').Library( 'nyanko.dll',{ 'f': [ require('ref-napi').types.CString, [] ] } ).f() )" > tester.utf16.js
Get-Content tester.utf16.js | Out-File -Encoding utf8 tester.js

ワンライナーにしちゃうとこちらはしんどいですね…。素直に書いたソースは:

var ffi = require('ffi-napi')
var ref = require('ref-napi')
var s = ref.types.CString
var nyanko = ffi.Library( 'nyanko.dll',{ 'f': [ s, [] ] } )
console.log( nyanko.f() )

です😏

Node.js で tester.js を実行して node-ffi-napi / node-ref-napi が期待動作しネイティブライブラリーの nyanko.dll から "にゃおーん" が無事に返り表示されるはずです:

node tester.js
にゃおーん

成功しました👍

コンパクト化したコマンドライン

ついでに npm -> yarn 化。

# テスト用のディレクトリーと npm init していない場合はここから
mkdir myapp
cd myapp
yarn init -y
# ffi-napi, ref-napi を追加
yarn add ffi-napi ref-napi
# テスト用の native dll を作成
echo '#[no_mangle] pub extern fn f() -> * const u8 { "にゃおーん".as_ptr() }' > nyanko.utf16.rs
Get-Content nyanko.utf16.rs | Out-File -Encoding utf8 nyanko.rs
rustc --crate-type cdylib nyanko.rs
# テスト用の .js を作成
echo "console.log( require('ffi-napi').Library( 'nyanko.dll',{ 'f': [ require('ref-napi').types.CString, [] ] } ).f() )" > tester.utf16.js
Get-Content tester.utf16.js | Out-File -Encoding utf8 tester.js
# 動作確認
node tester.js
にゃおーん

参考

さいきん試した Rust の GUI Toolkit 的な crate たちと日本語アプリでの実用性のメモ

現時点ではほぼダメです。試した中では唯一 conrod だけ日本語アプリを作る実用に耐えられそうです。

crate 日本語表示 日本語入力 コピペ UI部品の見栄え その他
conrod ○TTF可 ◎自然にできる ◎自然にできる ∞ 作り込み次第 vulkan対応で軽快, GL版だともっさり気味
druid ◎システム可、TTF可 ✘無理 △事実上無理 △ぼちぼち フォント回りや2Dドロー回りはたぶん界隈では最強
OrbTk ○TTF可 ✘無理 ✘無理 ○まあまあ 開発は活発なので続けばよくなるかも
azul ○システム可 ✘無理 ✘無理 △ぼちぼち exampleにも動作しない機能、壊れた機能が多い。開発が不活性化していて事実上終了かも

日本語表示の(システム可)はシステムフォントを名前で指定すれば使えるものです。使えないものはフォントファイルをバイナリーで直接ロードする仕組みになっています。

conrod はもともとゲーム開発向けなせいかデフォルトの UI 部品の見栄え的にはとてもしょぼいです。一般的にゲームではUI部品の絵から作り込むため、デフォルトでリッチな見栄えのUI部品を実装するモチベーションは低いのだろうと思います。また、使う場合は vulkan 版が前提になると思います。 GL 版はハイエンド環境でも動作がもっそりめで体感で30FPSでてるか怪しい状態でした。 vulkan 版はとても軽快に動作し、60FPS基準のゲームでも作り込めそうです。

druid は開発のなかのひとがフォント系に強く、 GUI Toolkit として完成度の高いフォント回りの実装を依存 crate 群と併せて実現できています。特別にフォントを設定しなくてもOSのフォントからフォールバックも含め使用できるため、他のライブラリーと違い何もしなくても日本語環境で実行すれば日本語も表示できます。ただし、キーコードで直接打てる以外のテキスト処理の実装状況はまだまだで、日本語入力する手段がコピペを含めてありません。コピペの実装もいまのところ独特な仕様で example の多くのライブラリー内蔵の AppLauncher を介した実行形態ではアプリの内部構造の内側でのみコピー&ペーストできる状態です。アプリの外とクリップボードを介してコピペ…できそうなPRはmergeされていましたが、 example の多くの AppLauncher を介した実行形態で使用できる状態になるのはまだ先の様子です。他の実行形態として druid-shell と呼ばれている実行形態と少量の example もありますが、このモードでは druid のほとんどの GUI 部品を使用できません。 開発はアクティブで、PRの対応もフレンドリー、ソースコードも綺麗で読みやすい方と思いましたので、 Authors のモチベーションが半年か1年か続けば化ける気はします。

OrbTk も開発のアクティビティーは良好で、ロゴデザインや UI 部品の意匠など良質な GUI Toolkit に育つかも…という期待はありますが、日本語入力やコピペはまだできず、こちらもあと1年くらい Authors のモチベーションが続けば…という気配です。

azul は今回検討対象を列挙した段階ではアーキテクチャーなど含め最良の選択肢の可能性を感じたものの…実際に試してみると未成熟というか…壊れてるというか…。 Authors のモチベーションまたは作業時間が既にほぼ無くなってしまっている気配が感じられるので、 fork が発展または Authors がヤルキを漲らせる様子が観測されなければこのまま錆びてしまうかも知れません。

ほか

今回の調査では非OSSなお仕事アプリでも使いやすい MIT, BSD系のライセンスに限り、Gtk, Qt のバインディングは除外しました。ライセンスに問題が無ければ、特にOSSでは素直に Gtk, Qt のバインディングを試すのが現状の開発途上な他のライブラリーに比べると圧倒的に苦労が少ないような気はします。

また、 Android 向けを主軸に iOSWindows, OSX などのクロスプラットフォームに対応した flutter のバインディング flutter-rs も試そうとしましたが、日常的に Android, flutter での開発に触れ Dart もそこそこ現役で使えるマンじゃないと使用は難しそうです。少なくとも私は少々遊んだ程度では flutter のエコシステムをゼロから理解して最初のビルドが完了できるまでには至りませんでした。

ちなみに

アプリの生産性と実用性(ユーザー視点でのUIへのとっつきやすさ、ユーザーがUIへ要望する事の実現のし安さなど)という点では、 バックエンドに Rust を採用するにしても、一般的なアプリケーションを非OSSで開発する場合にエンドユーザーが触る部分の GUI としてのフロントエンド部分は Rust ではないなにかで開発し、バックエンド部分の Rust とはマーシャリングする構成がよいのではないかと思います。少なくとも現状で半年や1年以内にリリースするアプリを作りたいのなら。

GUIフロントエンド部分にボタンやテーブルやメニューのような一般的なビジネスアプリ的なUI部品が不要な場合は conrod でもいいでしょうし、 UE4 や Unity でも、あるいは C++ で Siv3D とかも良いかもしれません。

一般的なビジネスアプリ的なUI部品でまとめれば良い場合は、 electron 系と組み合わせてもいいでしょうし、JVM 系のテクノロジーと組み合わせ、あるいはプラットフォーム次第では .net core WPF 系も良い事もあるかもしれません。

一般的なビジネスアプリ的なUI部品も欲しいし、GPUレンダリングも欲しい場合は…どうしましょうね。 electron 系と組み合わせると GPU レンダー部分を WebGL2 や Three.js で制限されたり、描画コンテキストとUI配置の仕組みで苦労するかもしれません。WebGL制約を受けたくなければバックエンド側でオフラインレンダリングしてフロントエンド側へテクスチャーとして渡すようなトリッキーでオーバーヘッドの心配な方法が必要になるかもしれません。GPUレンダリングパワーの需要が大きい場合はフロントエンド部分をUE4やUnityと組み合わせ、3Dコンテキスト系のUI部品でも顧客が本当にほしかったアプリのUIを実現するのが現実的かもしれません。その場合はウィンドウの上の方のメニューと下の方のステータスバーのようなUIもUE4やUnityで作れないわけではありませんから…。Excelっぽいやつ出してとかは…もうそうなったら素直に別プロセスでQtで作ってIPCとかで…いいんじゃないですかね…😭

ある日とつぜんWSL2が起動しなくなったメモ

きょうもいちにち。Windows Terminalを起動…しない。正確にはメインウィンドウを出そうとする辺りまでは起動はしているようだけど、一瞬で消えてしまうので実質起動しない。

とりあえず cmdwsl る:

f:id:USAGI-WRP:20200317074659p:plain

WSL 2 requires an update to its kernel component. For information please visit https://aka.ms/wsl2kernel

f:id:USAGI-WRP:20200317075210p:plain

と、いうわけで「このリンク」とやらを踏み、

f:id:USAGI-WRP:20200317075250p:plain

"Windows Subsystem for Linux Update Setup" を install する。よくわからないが一瞬で終わる。

f:id:USAGI-WRP:20200317075431p:plain

なんか知らんけど(†1) wsl が動くようになり、

f:id:USAGI-WRP:20200317075911p:plain

Windows Terminal も起動するようになりました。

Microsoftのエラー時に示されるリンクが有用だっただけでも感動できる素晴らしいUX体験ですね。もちろん嫌味です。

参考