2013年4月10日

xpcshell と jsctypes

普段のコマゴマとしたスクリプトのもう半分は JavaScript で書いています。最近だと ServerSide JavaScript が広まってきたようで、いろんな処理系がありますね。

私は xpcshell を使っています。なんたってアドオンと同じように書けるんだもん。というのも、xpcshell は XPConnect を使うための JavaScript シェルなのです。(xpcshell はバイナリとして配布はされてないので、mozilla のソースコードから自分でビルドする必要があります)

xpschell では XPCOM が使えるので、アドオンでできるような事はほぼ出来ます。ただしスクリプトを書く上で色々足りない機能もあったりします。

たとえば、popen が無いのです。他のコマンドと連携するようなコマンドラインアプリケーションを書くとなるとこれはイタイ。 system も XPCOM で無理矢理作れない事もないけど、やっぱり不便です。

xpcshell を使い始めた頃は xpcshell 本体のコードを書き換えてビルドしてました。 しかし、バージョンアップする度にパッチを当て直してビルド、というのも面倒だし、他所に持って行ったら使えないし、という事で困ってました。

そこに登場したのが jsctypes です。これを使うと外部のライブラリに定義されている C 等の関数が直接呼べます、ヤッタネ。

system については非常に簡単です。関数を定義するだけで動きます。

let Cu = Components.utils;

Cu.import ("resource://gre/modules/ctypes.jsm");

let lib_c = ctypes.open ("/usr/lib/libc.dylib");
let system
 = lib_c.declare ("system",
                  ctypes.default_abi,
                  ctypes.int,
                  ctypes.char.ptr);
system ("ls")
lib_c.close ();

popen はどうかというと、こっちはちょっと面倒だったりします。せっかくなので入出力両方できる popen が欲しいところですし、欲張って作ってみましょう。

let Ci = Components.interfaces;
let Cc = Components.classes;
let Cu = Components.utils;

let toUTF8 = function (text) {
  let converter
  = Cc ["@mozilla.org/intl/scriptableunicodeconverter"]
  .createInstance (Ci.nsIScriptableUnicodeConverter);
  converter.charset = "UTF-8";
  return converter.ConvertFromUnicode (text) + converter.Finish ();
};

let toCString = function (s) {
  let u = toUTF8 (s);
  let len = u.length;
  let p = new (ctypes.char.array (len + 1));
  for (let i = 0; i < len; i ++) {
    p [i] = u.charCodeAt (i);
  }
  p [len] = 0;
  return p;
};

let lib_c = ctypes.open ("/usr/lib/libc.dylib");

let f_pipe
= lib_c.declare ("pipe",
                 ctypes.default_abi,
                 ctypes.int,
                 ctypes.int.array (3));

let f_fork
= lib_c.declare ("fork",
                 ctypes.default_abi,
                 ctypes.int);

let f_read
= lib_c.declare ("read",
                 ctypes.default_abi,
                 ctypes.int,
                 ctypes.int,
                 ctypes.char.ptr,
                 ctypes.int);
let f_write
= lib_c.declare ("write",
                 ctypes.default_abi,
                 ctypes.int,
                 ctypes.int,
                 ctypes.char.ptr,
                 ctypes.int);
let f_close
= lib_c.declare ("close",
                 ctypes.default_abi,
                 ctypes.int,
                 ctypes.int);
let f_dup2
= lib_c.declare ("dup2",
                 ctypes.default_abi,
                 ctypes.int,
                 ctypes.int,
                 ctypes.int);
let f_execv
= lib_c.declare ("execv",
                 ctypes.default_abi,
                 ctypes.int,
                 ctypes.char.ptr,
                 ctypes.char.ptr.array (4));
let f_exit
= lib_c.declare ("exit",
                 ctypes.default_abi,
                 ctypes.int,
                 ctypes.int);
let f_waitpid
= lib_c.declare ("waitpid",
                 ctypes.default_abi,
                 ctypes.int,
                 ctypes.int,
                 ctypes.voidptr_t,
                 ctypes.int);

let popen = function (cmd) {
  let pfd_pc = new (ctypes.int.array (3));
  let pfd_cp = new (ctypes.int.array (3));
  
  f_pipe (pfd_pc);
  f_pipe (pfd_cp);
  
  let pid = f_fork ();
  if (pid == 0) {
    f_close (pfd_pc [1]);
    f_close (pfd_cp [0]);
    
    f_close (0);
    f_dup2 (pfd_pc [0], 0);
    f_close (pfd_pc [0]);
    
    f_close (1);
    f_dup2 (pfd_cp [1], 1);
    f_close (pfd_cp [1]);
    
    let argv = new (ctypes.char.ptr.array (4));
    argv [0] = toCString ("/bin/sh");
    argv [1] = toCString ("-c");
    argv [2] = toCString (cmd);
    argv [3] = null;
    
    f_execv ("/bin/sh", argv);
    
    f_exit (1);
  }
  
  f_close (pfd_pc [0]);
  f_close (pfd_cp [1]);
  
  let p = {
    pid: pid,
    ifd: pfd_cp [0],
    ofd: pfd_pc [1],
    
    readline: function () {
      if (this.ifd == 0) {
        return null;
      }
      
      let iseof = true;
      let p = new (ctypes.char.array (1));
      let c = 0;
      let buf = "";
      do {
        let size = f_read (this.ifd, p, 1);
        if (size == 0) {
          break;
        }
        c = p [0];
        iseof = false;
        if (c == 13) {
        }
        else if (c == 10) {
          break;
        }
        else {
          buf += String.fromCharCode (c);
        }
      } while (c);
      
      return iseof ? null : buf;
    },
    write: function (str) {
      if (this.ofd == 0) {
        return null;
      }
      let p = toCString (str);
      return f_write (this.ofd, p, p.length - 1);
    },
    iclose: function () {
      f_close (this.ifd);
      this.ifd = 0;
    },
    oclose: function () {
      f_close (this.ofd);
      this.ofd = 0;
    },
    wait: function () {
      f_waitpid (pid, null, 0);
    }
  };
  
  return p;
};

本当なら返り値としては nsIInputStream と nsIOutputStream を実装したようなヤツがいいんでしょうけど、そこは面倒なので使いそうな適当なメソッドだけ用意します。(readline の実装はあんまりかな……)

C の関数とやりとりする時に重要なのが、データ型の変換。基本的な型なら jsctypes がやってくれますが、配列とかポインタとかになってくると自前でやる必要があります (toCString とか)。

0 件のコメント:

コメントを投稿