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

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

Vala言語で外部プロセスを実行する(GLib.Process.spawn_async_with_pipes()を使用・メインループを用いた例・コード例)

ここでは「Vala言語で外部プロセスを実行する(GLib.Process.spawn_async_with_pipes()を使用・メインループを用いた例・メモ)」の内容を踏まえてGLib.Process.spawn_async_with_pipes()を用いた外部プロセスの実行とステータス値の取得のテストをGLibのメインループを用いて行う。
今回は子プロセスからの出力の(IOチャンネルによる)処理は行っておらず、子プロセスの出力は親プロセスの端末への出力としてそのまま出力される。

GLibのメインループをそのまま使用した例

[任意]ファイル名: spawnasyncwithpipestest_glib.vala

using GLib;

/*
 * valac -o spawnasyncwithpipestest_glib spawnasyncwithpipestest_glib.vala
 */

namespace SpawnAsyncWithPipesTestGLib
{
  class MainClass
  {
    static GLib.MainLoop loop;
    static uint id_childwatch;
    /*
     * 子プロセスが終了すると呼ばれる関数
     * 2番目の引数がステータス値
     */
    static void close_child (GLib.Pid pid, int status)
    {
      /*
       * statusは戻り値以外の情報も持っていて
       * (GLib.Process.if_exited()でチェック後)GLib.Process.exit_status()を通すと
       * 戻り値が得られる
       */
      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);
      /* 監視の解除 */
      GLib.Source.remove (MainClass.id_childwatch);
      /* メインループを抜けて終了 */
      MainClass.loop.quit ();
    }
    public static int main (string[] args)
    {
      GLib.Pid child_pid;
      /*
       * コマンド行引数を子プロセスのコマンド行としてそのまま渡すことにする
       * main()のコマンド行の0番は本プロセスのパスなので
       * 1番から後ろを新しいstring配列にコピーする
       */
      string[] argv;
      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]);
      try
      {
        /*
          * 1:作業ディレクトリ(string 親を継承するならnull)
          * 2:コマンド行(string配列)
          * 3:環境変数(string配列 親を継承するならnull)
          * 4:SpawnFlags(glib-2.0.vapi内の「public enum SpawnFlags」から指定)
          *   SEARCH_PATHを指定しない場合は実行ファイルの場所を絶対パスで指定する
          *   DO_NOT_REAP_CHILDを指定すると子プロセス終了を待たない(自分で処理)
          * 5:exec()の直前に子プロセスで実行する関数(null可)
          * 6:<out>子プロセスのプロセスIDを格納する変数(GLib.Pid)
          * 7:<out>子プロセスの標準入力のファイル記述子を格納する変数(int・null可)
          * 8:<out>子プロセスの標準出力のファイル記述子を格納する変数(int・null可)
          * 9:<out>子プロセスの標準エラー出力のファイル記述子を格納する変数(int・null可)
          */
        if (GLib.Process.spawn_async_with_pipes (null,
                                                 argv,
                                                 null,
                                                 GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
                                                 null,
                                                 out child_pid,
                                                 null,
                                                 null,
                                                 null))
          /*
           * DO_NOT_REAP_CHILDを指定した場合
           * 子プロセスの終了時に呼ばれる関数を指定
           */
          MainClass.id_childwatch = GLib.ChildWatch.add (child_pid, MainClass.close_child);
      }
      catch (GLib.SpawnError e)  // 子プロセス起動に失敗
      {
        GLib.warning ("spawn failed: %s", e.message);
        return 1;
      }
      /* メインループを開始 */
      MainClass.loop = new GLib.MainLoop (null, false);
      MainClass.loop.run ();
      return 0;
    }
  }
}

このプログラムはコマンド行引数に指定されたコマンド行を子プロセスとして実行し、子プロセスの終了後に親プロセスも(メインループを抜けて)終了する。下は実行例。

(正常に終了する例)
$ ./spawnasyncwithpipestest_glib cal 7 2009
** (process:[プロセスID]): DEBUG: spawnasyncwithpipestest_glib.vala:57: argv[0]: cal
** (process:[プロセスID]): DEBUG: spawnasyncwithpipestest_glib.vala:57: argv[1]: 7
** (process:[プロセスID]): DEBUG: spawnasyncwithpipestest_glib.vala:57: argv[2]: 2009
      7月 2009      
日 月 火 水 木 金 土
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

child exited, raw status: 0 exit status: 0

(エラーになる例)
$ ./spawnasyncwithpipestest_glib ls -5
** (process:[プロセスID]): DEBUG: spawnasyncwithpipestest_glib.vala:57: argv[0]: ls
** (process:[プロセスID]): DEBUG: spawnasyncwithpipestest_glib.vala:57: argv[1]: -5
ls: invalid option -- '5'
詳しくは `ls --help' を実行して下さい.
child exited, raw status: 512 exit status: 2

(停止する例)
$ ./spawnasyncwithpipestest_glib sleep 2
** (process:[プロセスID]): DEBUG: spawnasyncwithpipestest_glib.vala:57: argv[0]: sleep
** (process:[プロセスID]): DEBUG: spawnasyncwithpipestest_glib.vala:57: argv[1]: 2
(2秒間停止する)
child exited, raw status: 0 exit status: 0

(引数なしで実行した場合)
$ ./spawnasyncwithpipestest_glib           
usage: ./spawnasyncwithpipestest_glib [child_process] ( [args...] )

GTK+を使用した例(出力は端末に出る)

下のコードはテキスト入力欄を持つGTK+のウィンドウを表示し、入力したコマンドをEnterで実行する。出力結果は端末に出るが、子プロセスの実行中に親プロセスのGUIが固まることはない。
[任意]ファイル名: spawnasyncwithpipestest_gtk.vala

using GLib;
using Gtk;

/*
 * valac --pkg gtk+-2.0 -o spawnasyncwithpipestest_gtk spawnasyncwithpipestest_gtk.vala
 */

namespace SpawnAsyncWithPipesTestGtk
{
  class MainWindow : Gtk.Window
  {
    Gtk.AccelGroup accelgroup;
    Gtk.ImageMenuItem item_quit;
    Gtk.Menu menu_file;
    Gtk.MenuItem item_file;
    Gtk.MenuBar menubar;
    Gtk.Entry entry;
    Gtk.VBox vbox;
    uint id_childwatch;
    public MainWindow ()
    {
      /* ショートカットキー(アクセラレータ) */
      this.accelgroup = new Gtk.AccelGroup ();
      this.add_accel_group (this.accelgroup);
      /* メニュー項目 */
      this.item_quit = new Gtk.ImageMenuItem.from_stock (Gtk.STOCK_QUIT, accelgroup);
      this.menu_file = new Gtk.Menu ();
      this.menu_file.add (item_quit);
      this.item_file = new Gtk.MenuItem.with_mnemonic ("_File");
      this.item_file.set_submenu (menu_file);
      this.menubar = new Gtk.MenuBar ();
      this.menubar.append (item_file);
      this.entry = new Gtk.Entry ();
      this.vbox = new Gtk.VBox (false, 0);
      this.vbox.pack_start (this.menubar, false, false, 0);
      this.vbox.pack_start (this.entry, false, false, 0);
      this.add (this.vbox);
      /* テキスト入力欄をフォーカス */
      this.entry.grab_focus ();
      /* シグナル */
      this.entry.activate += this.on_entry_activated;
      this.item_quit.activate += Gtk.main_quit;
      this.destroy += Gtk.main_quit;
    }
    void on_entry_activated (Gtk.Entry source)
    {
      GLib.Pid child_pid;  // 子プロセスのプロセスID/ハンドル
      string[] argv;       // コマンド行
      /* テキスト入力欄から文字列を取り出す */
      string cmdline = source.text;
      source.text = "";    // クリア
      try
      {
        /* コマンド行文字列を解釈して文字列配列の形式に変換 */
        GLib.Shell.parse_argv (cmdline, out argv);
      }
      catch (GLib.ShellError e)
      {
        GLib.warning ("Cannot parse command line: %s\n(%s)", cmdline, e.message);
        return;
      }
      GLib.debug ("cmdline: %s", cmdline);
      try
      {
        /*
          * 1:作業ディレクトリ(string 親を継承するならnull)
          * 2:コマンド行(string配列)
          * 3:環境変数(string配列 親を継承するならnull)
          * 4:SpawnFlags(glib-2.0.vapi内の「public enum SpawnFlags」から指定)
          *   SEARCH_PATHを指定しない場合は実行ファイルの場所を絶対パスで指定する
          *   DO_NOT_REAP_CHILDを指定すると子プロセス終了を待たない(自分で処理)
          * 5:exec()の直前に子プロセスで実行する関数(null可)
          * 6:<out>子プロセスのプロセスIDを格納する変数(GLib.Pid)
          * 7:<out>子プロセスの標準入力のファイル記述子を格納する変数(int・null可)
          * 8:<out>子プロセスの標準出力のファイル記述子を格納する変数(int・null可)
          * 9:<out>子プロセスの標準エラー出力のファイル記述子を格納する変数(int・null可)
          */
        if (GLib.Process.spawn_async_with_pipes (null,
                                                 argv,
                                                 null,
                                                 GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
                                                 null,
                                                 out child_pid,
                                                 null,
                                                 null,
                                                 null))
          /*
           * DO_NOT_REAP_CHILDを指定した場合
           * 子プロセスの終了時に呼ばれる関数を指定
           */
          this.id_childwatch = GLib.ChildWatch.add (child_pid, this.close_child);
      }
      catch (GLib.SpawnError e)  // 子プロセス起動に失敗
      {
        GLib.warning ("spawn failed: %s", e.message);
      }
    }
    /* 子プロセスが終了すると呼ばれる関数 */
    void close_child (GLib.Pid pid, int status)
    {
      /*
       * statusは戻り値以外の情報も持っていて
       * (GLib.Process.if_exited()でチェック後)GLib.Process.exit_status()を通すと
       * 戻り値が得られる
       */
      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);
      /* 監視の解除 */
      GLib.Source.remove (id_childwatch);
    }
  }
  class MainClass
  {
    public static void main (string[] args)
    {
      Gtk.init (ref args);
      MainWindow win = new MainWindow ();
      win.show_all ();
      Gtk.main ();
    }
  }
}

関連記事:

使用したバージョン:

  • Vala 0.7.4