環境
Firefox 65.0
toolchain: stable-i686-pc-windows-msvc
rustc 1.32.0 (9fda7c223 2019-01-16)
目的
MDN にある Native messaging の Python による ping_pong サンプルを Rust で実装してみます。Windows 環境での例です。
ネイティブアプリ側
MDN の説明にある通り標準入出力を使ってメッセージのやり取りを行います。それぞれのメッセージの先頭 4bytes がメッセージ長になってます。Windows の場合はリトルエンディアンなので下記のようなメッセージ構造になります。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
6 | 0 | 0 | 0 | " | p | i | n | g | " |
まずはメッセージ長を取得する処理です。vec![0u8; 4]
で 4bytes 分のバッファを確保して、read_exact
で読み込み、copy_nonoverlapping
でu32
に変換という流れです。dst.to_le()
は書かなくても良いですが、リトルエンディアンということが分かるように書いてます。
pub fn read_size() -> io::Result<usize> {
let mut buf = vec![0u8; 4];
io::stdin().read_exact(buf.as_mut_slice())?;
let size = unsafe {
let mut dst: u32 = 0;
ptr::copy_nonoverlapping(buf.as_ptr(),
&mut dst as *mut u32 as *mut u8, 4);
dst.to_le() as usize
};
Ok(size)
}
メッセージ長が取得できたのでメッセージ本文を読み込みます。vec![0u8; size]
でメッセージ長分のバッファを確保して読み込み、String::from_utf8_lossy()
で Rust の世界の文字列に変換します。ここでは読み込んだ文字列をコールバック関数に渡しています。
pub fn read_content(callback: Option<CB>) -> io::Result<()> {
let size = read_size()?;
let mut buf = vec![0u8; size];
io::stdin().read_exact(buf.as_mut_slice())?;
let content = String::from_utf8_lossy(buf.as_slice());
if callback.is_some() {
(callback.unwrap())(content.as_ref());
}
Ok(())
}
コールバック関数の型定義とメインループです。
type CB = fn(value: &str);
pub fn main_loop(callback: Option<CB>) {
loop {
read_content(callback).unwrap();
}
}
入力側の処理は以上です。
次は出力側の処理を書いていきます。アプリ側からの送信するメッセージも先頭 4bytes がメッセージ長で、それにメッセージ本文が続きます。
まずはメッセージ長を出力します。アプリ側からのメッセージ送信は 1MiB に制限されているので、それを超えていたらエラーを返すようにしています。その次がu32
を[u8; 4]
に変換する処理です。
pub fn write_size(size: usize) -> io::Result<()> {
if size > 1024 * 1024 {
return Err(io::Error::from(io::ErrorKind::InvalidData));
}
let size: [u8; 4] = unsafe {
*(&(size as u32).to_le() as *const u32 as *const [u8; 4])
};
let _ = io::stdout().write(&size)?;
Ok(())
}
メッセージ本文を出力する処理です。
pub fn write_content(content: &str) -> io::Result<()> {
let size = content.len();
write_size(size)?;
io::stdout().write(content.as_bytes())?;
io::stdout().flush()?;
Ok(())
}
以上を native_messaging module にして、main.rs から呼ぶようにしています。native_messaging.rs と main.rs をまとめてどうぞ。
use std::io::{self, Read, Write};
use std::ptr;
type CB = fn(value: &str);
pub fn main_loop(callback: Option<CB>) {
loop {
read_content(callback).unwrap();
}
}
pub fn read_content(callback: Option<CB>) -> io::Result<()> {
let size = read_size()?;
let mut buf = vec![0u8; size];
io::stdin().read_exact(buf.as_mut_slice())?;
let content = String::from_utf8_lossy(buf.as_slice());
if callback.is_some() {
(callback.unwrap())(content.as_ref());
}
Ok(())
}
pub fn read_size() -> io::Result<usize> {
let mut buf = vec![0u8; 4];
io::stdin().read_exact(buf.as_mut_slice())?;
let size = unsafe {
let mut dst: u32 = 0;
ptr::copy_nonoverlapping(buf.as_ptr(),
&mut dst as *mut u32 as *mut u8, 4);
dst.to_le() as usize
};
Ok(size)
}
pub fn write_size(size: usize) -> io::Result<()> {
if size > 1024 * 1024 {
return Err(io::Error::from(io::ErrorKind::InvalidData));
}
let size: [u8; 4] = unsafe {
*(&(size as u32).to_le() as *const u32 as *const [u8; 4])
};
let _ = io::stdout().write(&size)?;
Ok(())
}
pub fn write_content(content: &str) -> io::Result<()> {
let size = content.len();
write_size(size)?;
io::stdout().write(content.as_bytes())?;
io::stdout().flush()?;
Ok(())
}
main.rs はメインループを実行して、メッセージを受け取ったら、コールバック関数で "pong" というメッセージを返すだけです。
mod native_messaging;
use native_messaging::{main_loop, write_content};
fn main() {
main_loop(Some(callback));
}
fn callback(_content: &str) {
write_content("pong").unwrap();
}
あくまでもサンプルなのでエラー処理はいい加減です。crates.io に chrome_native_messaging という crate があるのでそちらも参考にしてください。Firefox でも動くはず?
manifest.json
以下の内容の manifest ファイルを ping_pong.exe と同じフォルダに保存します。
{
"name": "ping_pong",
"description": "Example host for native messaging by Rust",
"path": "ping_pong.exe",
"type": "stdio",
"allowed_extensions": [ "ping_pong@example.org" ]
}
この manifest ファイルへのパスを以下のようにレジストリに書き込みます。C:¥path¥to¥manifest.json
のところは自分の環境に合わせて適宜書き換えてください。
アドオン側
MDNとまったく同じなので(略