Learning WASM #7
February 18, 2022•249 words
前回はqjsのカスタモジュールにreadメソッドとwriteメソッドを生やして、hostのdotnetランタイムと文字列のやり取りをできるようにした。
今回は、文字列がやり取りできるようになったのでJSON-RPCを流して、dotnetからjsの関数をコールしてその結果を得たい。
RPC処理ロジックをカスタムモジュールにするには、Cで書く必要がありちょっと大変なので、いったんmain.jsの中にJavaScriptで書いた
import * as host from 'host';
import * as std from 'std';
import ejs from './ejs.mjs';
ejs.fileLoader = function(filename) {
const file = std.open(filename,'r');
return file.readAsString();
};
const handlers = {};
function register(methodName, handler) {
handlers[methodName] = handler;
}
function listen() {
while(true) {
const line = host.readLine();
if (line == '.quit') {
break;
}
const data = JSON.parse(line);
const func = handlers[data.method];
if (typeof func == 'function' && Array.isArray(data.params)) {
func.apply(null, data.params.concat([function (result) {
host.writeLine(JSON.stringify({
jsonrpc: '2.0',
result,
id: data.id,
}));
}]));
}
}
}
register('transform', function (payload, callback) {
ejs.renderFile('template.ejs', payload, {}, function (err, str) {
if (err) {
std.err.puts(err);
}
else {
const transformed = {
name: payload.name,
html: str
};
callback(transformed);
}
})
});
listen();
関数を登録する register
とhostとのやり取りを開始するlisten
を作成した。 json-rpcにはないが、一応終了させるための .quit
を受信するとlistenが終了しプログラムが終了する。
dotnetは前回と同じ、ペイロードとなる rbuf
の初期値だけ違う
var rpccall = JsonSerializer.Serialize(new
{
jsonrpc = "2.0",
method = "transform",
@params = new[] { new { name = "iwate"} },
id = Guid.NewGuid().ToString()
});
using var rbuf = new MemoryStream(Encoding.UTF8.GetBytes($"{rpccall}\n.quit\n"));
using var wbuf = new MemoryStream();
using var engine = new Engine();
using var module = Module.FromFile(engine, "qjs.wasm");
using var linker = new Linker(engine);
using var store = new Store(engine);
linker.DefineFunction("env", "read", () => {
return (int)rbuf.ReadByte();
});
linker.DefineFunction("env", "write", (int c) => {
wbuf.WriteByte((byte)c);
});
linker.DefineWasi();
store.SetWasiConfiguration(new WasiConfiguration()
.WithPreopenedDirectory(dir, ".")
.WithInheritedStandardOutput()
.WithArgs("--", "main.js"));
var instance = linker.Instantiate(store, module);
var run = instance.GetFunction(store, "_start");
run.Invoke(store);
wbuf.Seek(0, SeekOrigin.Begin);
var str = Encoding.UTF8.GetString(wbuf.ToArray());
Console.WriteLine(str);
実行結果はこんな感じ、EJSの実行結果がJSON-RPC形式で返ってきてる。
{"jsonrpc":"2.0","result":{"name":"iwate","html":"<html>\r\n<body>\r\n<p>Hello, Wasmtime! I am iwate.</p>\r\n</body>\r\n</html>"},"id":"e4c82100-a5b8-4389-bcbf-41b77682f311"}
次は、dotnet側をTask化して、main.jsをlisten状態で常駐させておいて、任意のタイミングで呼び出せるようにしたい。
それか、qjs側のJSON-RPC処理部のCモジュール化とJSON-RPCエラー系の実装か。
どれ最初にやろうかな。まよう。
自由にjs処理を呼べる下地ができてきていよいよ楽しくなってきた。