試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

Vala言語で外部プロセスを実行する(GLib.Process.spawn_async_with_pipes()を使用・メインループを用いない場合と出力メッセージの処理)

ここではGLibのメインループを用いない場合の子プロセスのステータス値の取得についてとGLib.Process.spawn_async_with_pipes()から得られたファイル記述子を用いた出力の処理についてを扱う。

メインループを用いずに子プロセスのステータスを取得

Vala言語で外部プロセスを実行する(GLib.Process.spawn_async_with_pipes()を使用・メインループを用いた例・メモ)」ではGLib.ChildWatch.add()を用いて、子プロセスが終了したときに実行される関数をメインループの仕組み上で呼ぶ形で子プロセスのステータス値を取得したが、メインループを用いない場合、Posix.waitpid()によってステータスを取得することができる(最初の引数(プロセスID)はPosix.pid_t型に型変換する)。

public pid_t waitpid (pid_t pid, out int status, int options);

もちろん、この関数を用いると子プロセスが終了するまでの間は待機状態のままとなる。
ただし、この方法はOS依存(広い意味でのUNIX系OSでのみ利用可能)となり、Windows上ではGLib.Process.spawn_async_with_pipes()によって得られたGLib.Pid型のデータが子プロセスの「ハンドル」になっていてPosix.waitpid()の代わりにWindows API(C言語)のGetExitCodeProcess()という関数を呼ぶことにより戻り値が得られるらしいが、Vala 0.7.4の時点ではWindows APIの言語バインディングはないので、Vala言語でこの処理を行えるようにすることはできない?

IOチャンネルによる標準出力/標準エラー出力の読み込み

GLib.Process.spawn_async_with_pipes()によって得られた(標準出力と標準エラー出力の)ファイル記述子をそれぞれ処理するのにはIO(アイオー)チャンネルというGLibの機能を用いる。
下はメモとなるが、処理の流れについては他にも色々とやり方がある。

  • IOチャンネルオブジェクトはGLib.IOChannel.unix_new()コンストラクタの引数に(GLib.Process.spawn_async_with_pipes()から得られた)ファイル記述子を指定して生成
  • メンバ関数read_line()により行単位の読み込みができる
  • データはループで処理してread_line()がGLib.IOStatus.NORMALを返さないか、例外GLib.IOChannelErrorあるいはGLib.ConvertErrorが発生した場合に抜ける
  • GTK+を用いてテキストビューの後ろにデータを追加する場合の流れはPyGTKのときと同じ要領でテキストバッファ(Gtk.TextBuffer)オブジェクトのメンバ関数get_end_iter()でテキストバッファの最後を示すGtk.TextIterオブジェクトを得てテキストバッファのplace_cursor()でカーソル位置を移動、insert_at_cursor()で現在保持している行のデータを追加、最後にテキストビュー(Gtk.TextView)オブジェクトのscroll_to_mark()でスクロールとなる
  • IOチャンネルはメンバ関数shutdown()で後始末

GTK+上で日本語を正常に表示するためには、(「Vala言語で外部プロセスを実行する(簡単な例・コード例と出力結果)」で行っているLC_CTYPE指定の代わりに)IOチャンネルのエンコーディングロケールに合わせることになる。*1

string encoding;
GLib.IOChannel ioch = new GLib.IOChannel.unix_new ([ファイル記述子]);
if (GLib.get_charset (out encoding) == false)
{
  try
  {
    ioch.set_encoding (encoding);
  }
  catch (GLib.IOChannelError e)
  {
    ;
  }
}
(以下IOチャンネルからの読み込み処理)

下の例の実行結果は「Vala言語で外部プロセスを実行する(GLib.Process.spawn_async_with_pipes()を使用・メインループを用いた例・コード例)」のspawnasyncwithpipestest_glib.vala(1つ目の例)と同様となる(具体的な出力は省略)。
[任意]ファイル名: spawnasyncwithpipestest2.vala

using GLib;
using Posix;

/*
 * valac --pkg posix -o spawnasyncwithpipestest2 spawnasyncwithpipestest2.vala
 */

namespace SpawnAsyncWithPipesTest2
{
  class MainClass
  {
    public static int main (string[] args)
    {
      string[] argv;              // コマンド行
      string line;                // 出力の1行を格納
      GLib.Pid pid;               // 子プロセスのプロセスID
      int stdin, stdout, stderr;  // 標準入力/標準出力/標準エラー出力のファイル記述子
      GLib.IOStatus iostatus_stdout, iostatus_stderr;  // 出力の読み込みで使用
      size_t length, terminator_pos;  // IOチャンネルから書き込まれる
      string encoding;            // 確認用
      int status;
      if (args.length == 1)
      {
        print ("usage: %s [child_process] ( [args...] )\n", args[0]);
        return 1;
      }
      argv = new string[args.length - 1];  // 子プロセスのコマンド行
      for (int i = 0; i < args.length - 1; i++)
        argv[i] = args[i + 1];
      /* 子プロセスのコマンド行を出力(確認用) */
      for (int i = 0; i < argv.length; i++)
        GLib.debug ("argv[%d]: %s", i, argv[i]);
      /* LC_CTYPEをsetlocale()で指定しないと日本語が「?」になる */
      weak string? lc_ctype = GLib.Environment.get_variable ("LC_CTYPE");  // 環境変数
      if (lc_ctype != null)
        GLib.Intl.setlocale (GLib.LocaleCategory.CTYPE, lc_ctype);
      GLib.get_charset (out encoding);  // 確認用途・ロケールによって変わる
      try
      {
        GLib.Process.spawn_async_with_pipes (null,
                                             argv,
                                             null,
                                             GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
                                             null,
                                             out pid,
                                             out stdin,
                                             out stdout,
                                             out stderr);
        /* ファイル記述子を開くのにIOチャンネルを使用する */
        GLib.IOChannel ioch_stdout = new GLib.IOChannel.unix_new (stdout);
        GLib.IOChannel ioch_stderr = new GLib.IOChannel.unix_new (stderr);
        /*
         * 標準出力と標準エラー出力を表示
         * 終了するまでread_line()で止まる
         */
        for (;;)
        {
          try
          {
            /* 出力から1行読み込む */
            iostatus_stdout = ioch_stdout.read_line (out line, out length, out terminator_pos);
          }
          catch (GLib.IOChannelError e)
          {
            break;
          }
          catch (GLib.ConvertError e)
          {
            break;
          }
          if (iostatus_stdout != GLib.IOStatus.NORMAL)
            break;
          print ("%s", line);
        }
        for (;;)
        {
          try
          {
            iostatus_stderr = ioch_stderr.read_line (out line, out length, out terminator_pos);
          }
          catch (GLib.IOChannelError e)
          {
            break;
          }
          catch (GLib.ConvertError e)
          {
            break;
          }
          if (iostatus_stderr != GLib.IOStatus.NORMAL)
            break;
          print ("%s", line);
        }
        /*
         * メインループがない場合はPOSIX関数のwaitpid()を用いて
         * 子プロセスのステータスを取得
         * Windows上では(child_pidをハンドルとして)代わりに
         * C言語のWindows APIのGetExitCodeProcess()を用いるのだが
         * Valaのバインディングは0.7.4の時点ではないので、戻り値は取れない?
         */
        Posix.waitpid ((Posix.pid_t) pid, out status, 0);
        print ("child exited, raw status: %d", status);
        if (GLib.Process.if_exited (status))
          print (" exit status: %d", GLib.Process.exit_status (status));
        print ("\n");
        /*
         * 必ずclose_pid()を呼んで後始末する
         * 特にWindows上ではハンドルの後始末として必須
         */
        GLib.Process.close_pid (pid);  // これは必須
        /* 後始末 */
        try
        {
          ioch_stdout.shutdown (false);
          ioch_stderr.shutdown (false);
        }
        catch (GLib.IOChannelError e)
        {
          ;
        }
      }
      catch (GLib.SpawnError e)  // 子プロセス起動に失敗
      {
        GLib.warning ("spawn failed: %s", e.message);
        return 1;
      }
      return 0;
    }
  }
}

関連記事:

参考URL:

使用したバージョン:

  • Vala 0.7.4

*1:この作業をしないとEUC-JPのロケール上で正常に日本語が表示されない