第8回: 小物アプリケーションのルールを守ろう

前回までで、「とりあえず」小物として動くアプリケーションを作ることができました。しかし、前回のようにただ*.f, *.dファイルを用意してMakefileを書き換えただけでは、小物アプリケーションが守るべき事項が守られていません。

たとえば、普通の小物アプリケーションは複数個起動ができないようになっています。そういった小物アプリケーションとして守るべき事項を今回は紹介します。lecture6-2のソースコードをどのように直せばよいか、という形で説明を進めていきます。

ソースコードはこちらです。お手元にぜひ用意して今回の説明を読み進めていってもらえれば、と思います。また、今回のプログラム全体を固めたものはこちらです: lecture8.tar.gz

1. 起動方法の種類と起動時処理の振り分け

WindowsでもUnixでも、アプリケーションは基本的に一つの起動方法しか持ちません。ところが、BTRON仕様OSではその点でちょっと事情が異なっています。そこら辺の様子をゆっくり見ていきましょう。

1.1. 起動メッセージの種類

前回説明を省いたMAIN関数の引数、MESSAGE *msgの簡単な説明をしておきましょう。

まず、アプリケーションの起動方式にはいくつかの種類があることを把握しておきましょう。仮身をダブルクリックする場合、コンソール上でコマンドを打ち込んで起動、の二つは皆さんご存じの通りですが、起動方法を全てまとめると、

仮身のオープン起動 (EXECREQ)
仮身がダブルクリックされたり、メニューから「実行」を選んで起動する場合の起動方法です。小物アプリケーションの起動もこれに当たります。
開いた仮身の表示起動 (DISPREQ)
仮身が開かれた時に、その中身を表示するためにアプリケーションが起動されます。
データの貼り込み起動 (PASTEREQ)
「仮身操作」メニューの「トレーから書き込み」を指定されたときに、トレーの中身を書き込むために起動されます。(実際にはほとんど使われていないようです。)
付箋のオープン起動 (FUSENREQ)
付箋をダブルクリックして起動した場合に使われます。
開いた仮身のTADデータ作成起動 (TADREQ)
開いた仮身の表示内容をTADデータとして取得するために起動されます。印刷などの時に使われ、表示起動(DISPREQ)とは違ってきれいな描画内容を取得できます。
コンソール起動 (0)
コンソールから起動した場合に対応します。BTRON3仕様書では定義されていません。

の6種類があります。

どの方法で起動されたかは、msg->msg_typeを見ることで調べることができます。上の一覧の(...)内に書いてあるのが、msg->msg_typeの中身に対応します。たとえば、msg->msg_typeEXECREQに等しいとき、アプリケーションは仮身のオープン起動によって起動されたことが分かります。

ところで、起動方式をきちんと調べて、その中身によって処理を振り分けることは非常に重要です。たとえば、前回のソースコードをそのまま原紙にして使った場合、開いた仮身にしようとした瞬間ウィンドウが出てくる、という非常に怪しい状況になります。したがって、起動処理の振り分けは特別な理由がない限り、最終的にはなるべく欠かさないようにしましょう。対応していない起動方法で起動されたときにも、きちんとした終了処理を行うべきです。

1.2. 起動方法のエラーの返し方

それぞれの場合にどのような処理をすればよいか、というのはそれぞれ異なっており、かなり複雑です。ここではとりあえず、正常にエラーを返す方法だけまとめておきましょう。全ての場合にエラーを返すには、MAIN関数の先頭部分で、このように処理を行います。

  switch (msg->msg_type){
  case EXECREQ: // 仮身のオープン起動
    oend_req((((M_EXECREQ*))msg)->vid, NULL, 0);
    ext_prc(-1);
    break;
  case FUSENREQ: // 付箋のオープン起動
    oend_prc(((M_FUSENREQ*)msg)->vid, NULL, 0);
    ext_prc(-1);
    break;
  case DISPREQ: // 開いた仮身の表示起動
    oend_req(((M_DISPREQ*)msg)->vid, 1);
    ext_prc(-1);
    break;
  case PASTEREQ: // データの貼り込み起動
    oend_req(((M_PASTEREQ*)msg)->vid, 1);
    ext_prc(-1);
    break;
  case TADREQ: // 開いた仮身のTADデータ作成起動
    oend_req(((M_TADREQ*)msg)->vid, 1);
    ext_prc(-1);
    break;
  default: // その他(コンソール起動など)
    ext_prc(-1);
  }

基本的には ext_prc(-1) して処理を中断し、プロセスを終了すればよいのですが、DISPREQ, PASTEREQ, TADREQの場合には、oend_reqを行う必要があります。また、EXECREQ, FUSENREQをエラー終了させるときにはoend_prcを実行する必要があります。

これらの意義については詳しく述べませんが、実身/仮身マネージャに、仮身の使用が終わった、あるいは、親プロセスに処理がうまくいったかどうか応答を返す、という意味合いがあるようです。たとえば、仮身をダブルクリックすると仮身が網掛け状態になりますが、それを元にもどすにはoend_prcしないといけません。そういう何かしらの「終了通知」的な意味合いがあるものと、とりあえずは理解しておいてください。

具体的なエラーの返し方は、それぞれソースコードの方を参考にして、細かいところについては仕様書を調べてみてください。正常終了するときは、終了するときにそれぞれoend_req, oend_prcを発行します。

1.3. 仮身のオープン起動の処理

原紙にしても小物にしても、基本的にはこの起動方法がもっとも一般的でしょうから、仮身のオープン起動処理の一連の流れについて説明します。つまり、上の"全ての場合にエラーを返す処理"を、小物起動の時に正しく動作させる方法について説明します。

まず、小物起動の時だけ正しく動作させるには、

  case EXECREQ: // 仮身のオープン起動
    oend_prc((((M_EXECREQ*))msg)->vid, NULL, 0);
    ext_prc(-1);
    break;

の部分を、

  case EXECREQ: // 仮身のオープン起動
    {
      M_EXECREQ* msg0 = (M_EXECREQ*)msg;
      if ((msg0->mode & 0x0002) == 0) {
        // 小物起動のみ認め、仮身起動は異常終了
        oend_req(msg0->vid, NULL, 0);
        ext_prc(-1);
      }
    }
    break;

のように書き換えます。msg0->modeに入っているのはさらに詳細な起動タイプですが、ここの2番目のビットが立っているときが小物起動、そうでない場合が仮身のオープン起動にあたります。小物起動でない場合には、単にoend_reqして、ext_prcする、エラー時処理を実行すれば良いだけです。

ウィンドウを開いた後の処理として、「そのウィンドウIDが仮身を使う」ということをウィンドウ・マネージャに通知する必要があり、それを行うための関数がosta_prc関数です。具体的には、重複起動を防いだり、仮身を網掛け表示させたり、などの効果として現れてきます。ウィンドウを開く部分の処理は、以下のようになるはずです。

  winfo[0].wid = wopn_wnd(WA_STD, 0, &r, NULL, 2, text, NULL, NULL);
  if (winfo[0].wid < 0){
    printf("wopn_wnd: %d\n", winfo[0].wid);
    ext_prc(1);
  }
  osta_prc(((M_EXECREQ*)msg)->vid, winfo[0].wid);
  winfo[1].wid = 0;

そのあと、プログラム中で、マウスイベントを処理したり画面に何かを表示したり…さまざまな処理を続ければ良いわけですが、全てが終わった後、ウィンドウを閉じ、ext_prcしてプログラムを正常終了させる前にやることがありますよね? そうです。oend_prcです。ウィンドウを閉じる前の処理は以下のようになります。

  // 終了時の処理
  oend_prc(((M_EXECREQ*)msg)->vid, NULL, 0);

もちろん、これはEXECREQ, FUSENREQの場合であって、TADREQ, DISPREQ, PASTEREQの場合には、oend_prcの代わりにoend_reqを行います。

起動時処理の振り分け、という概念は他のOSにはありませんし、とても複雑です。正しく理解するのはとても大変ですが、なるべく"正しい"プログラムを書けるよう、頑張ってみてください。

2. データボックスの初期化と解放

データボックスについては第3回でも少しだけ触れましたが、復習しておくと、プログラムからデータを分離して管理できるようにしたものです。Windowsの世界で「リソース」と呼ばれているものに似た意義のものです。

ところで、今までのプログラムでは、

  dopn_dat(NULL);

としてきましたが、これは、システムデータボックスを使う、ということをプログラム中で指定するためのものです。ただし、システムデータボックスは勝手にいじることが出来ないので、自分で新しいデータを好き勝手に置くことはできません。

*.bzアーカイブを作るときに、*.dファイルを用意したことを覚えていますでしょうか? 実はこの*.dファイルがデータボックスの中身を定義するためのファイルです。そして、この*.dファイルの中に書いたデータをプログラム中から参照するには、以下のような処理を、dopn_dat(NULL);の代わりにプログラム中に記述します。

  LINK lnk;

  prc_inf(0, PI_LINK, (VP)&lnk, sizeof(LINK));
  opendbox(&lnk, USER_DATA, 0x2000);

この中の、USER_DATA, 0x2000という部分は、*.dの中で、

	{% USER_DATA:L	0L}		-- データタイプ
	{# 0x2000L	0L	0L}	-- データ番号

となっているので、それに合わせる必要があります。ちなみに、特に理由がない限り、USER_DATAの方はそのままにしておきましょう。

使い終わったデータボックスは、閉じることで解放することが出来ます。一応プロセスの終了時にデータボックスが解放されることは仕様書に明示されていますが、なるべく明示的にデータボックスをクローズするのが望ましいそうです。したがって、プログラムの最後のあたりに、

  closedbox();

の処理を入れておきましょう。opendboxを実行してからclosedboxを行うまでの間、データボックスの中身を参照することが出来ます。データボックスの使い方は、次回以降実際に試してみましょう。

3. (おまけ) ちょっとしたテクニックの紹介

今回はちょっとだけ内容が少ないので、ちょっとしたシェルやC言語のこつなどを少しだけ紹介してみたいと思います。もうすでにシェルについての基礎などを知っている人は、読み飛ばしてもらってもかまいません。とりあえず今回は3つだけ紹介しておきましょう。

3.1. どのヘッダーファイルを使えばいいのか?

例えばwopn_wnd関数を使いたいとします。そのときに、どのヘッダファイルを#includeすればよいか分からない場合には、以下のようなコマンドを実行して調べることができます。

$ grep -nr wopn_wnd /usr/local/brightv/include

このコマンドの実行結果は、以下のようになります。

/usr/local/brightv/include/btron/hmi.h:772:IMPORT       WID     b_wopn_wnd(UW attr, UW par, RECT *r, RECT *org, W pict, TC *tit, PAT *bgpat, WDDISP *atr);
/usr/local/brightv/include/btron/hmi.h:907:#define      wopn_wnd        b_wopn_wnd

したがって、wopn_wndは#include <btron/hmi.h>とすれば使えるようになることが分かります。多くの場合にはこの方法は有効に使えると思います。

3.2. 関数・システムコールの探し方

システムコールについて調べたいときに、仕様書のどこを見たらいいか、という問題ですが、大まかに言って

という傾向があります。ある程度このことを頭に入れておくと、仕様書を読むのが楽になると思います。

3.3. エラー処理は省かない

プログラムが長くなってくると、だんだん if (err < 0) { ... } のようなエラー処理を書くのが嫌になってきますが、なるべく省かないようにしましょう。エラーはまあおきないだろう、と変な仮定をもとにエラー処理を省くと、原因もエラーの発生場所も不明のバグに長時間悩まされることとなります。

たとえば、wopn_wndですが、絶対成功する、と思っていませんか? ところが、これはフルスクリーンのウィンドウがある状況で実行すると、ウィンドウが開けずにエラーを返します。エラーは起きるものだと思って、きちんとエラー処理をしましょう。目指せ100年ソフト! です。

最低でも、なるべく返り値を調べてエラーの場所が突き止められるようにはしておきましょう。また、ファイルの書きこみ関連部分では、エラーを無視して進むと、ファイルを壊したりするおそれがあるので、そこではしっかりとエラー処理を行うべきです。

演習問題


[ 戻る ]

Last modified: Sun Oct 3 16:39:21 2010
Valid XHTML 1.0!