環境

rustc 1.32.0 (9fda7c223 2019-01-16)
Node.js 10.15.1

Rust から Node.js に Callback したい

というわけで、Cargo.toml です。cdylibってやると動的ライブラリができます。Windows では dll ってやつですね。

[package]
name = "callback"
version = "0.1.0"
authors = ["benki"]
edition = "2018"

[dependencies]

[lib]
name = "callback"
crate-type = ["cdylib"]

続いて、lib.rs です。rust_funcっていう関数は第一引数に C の文字列へのポインタ、第二引数にコールバック関数へのポインタを受け取るようになってます。

rust_func関数の1行目で Node.js の世界からやってきた C 文字列へのポインタを Rust の世界の文字列に変換して、2行目でdbg!マクロでその内容を表示しています。

その後の処理はカレントディレクトリにあるファイルのパスをコールバック関数の引数に渡しています。Rust の世界の文字列は NULL 終端文字がないので、そのまま Node.js の世界に渡すと結果がおかしなことになります。なので、path.push(‘\0’);で NULL 終端文字を追加する必要があります。(もうちょっとうまいことできる方法があったら教えてください)なので、std::ffi::CStringで C の世界の文字列に変換してます。(tatsuya6502さんにコメントで教えてもらいました)

use std::fs;
use std::os::raw::c_char;
use std::ffi::{CStr, CString};

type CallBack = fn(*const c_char);

#[no_mangle]
pub fn rust_func(string: *const c_char, cb: CallBack) {
    let str_from_node = unsafe { CStr::from_ptr(string).to_str().unwrap() };
    dbg!(str_from_node);

    fs::read_dir(".").unwrap()
        .for_each(|r| {
            if let Ok(r) = r {
                // let mut path = r.path().to_string_lossy().to_owned().to_string();
                // path.push('\0');
                // cb(path.as_ptr() as *const c_char);
                if let Ok(path) = CString::new(r.path().to_string_lossy().as_bytes()) {
                    cb(path.as_ptr() as *const c_char);
                }
            }
        });
}

最後に Node.js 側のコードです。

let ffi = require("ffi");
let util = require("util");

let lib = ffi.Library("<path to callback.dll or so>",
    {"rust_func": // Rust の世界の関数の名前
        ["void", // 戻り値
            ["string", // 第一引数
            "pointer"] // 第二引数(コールバック関数へのポインタ)
        ]
    }
);

let callback = ffi.Callback("void", ["string"], (path) => {
    console.log(path);
});

let rust_func = util.promisify(lib.rust_func.async);

(async () => {
    await rust_func("Hello from Node!", callback);    
})().catch(e => {
    console.log(e);
});
广告
将在 10 秒后关闭
bannerAds