RAP 未解決課題
Keith Edwards
keith@cc.gatech.edu
1995年3月31日
本文書では、Georgia Tech の RAP 実装(とても試験的なもの)が抱えている「未解決課題」について述べる。
+++ このコードで何ができるか?
同梱されている現在のコードは、 RAP 規則の部分集合を試験的に実装したもの(sample implementation)であり、この部分集合はメルカトル(Mercator)で使われていたものである。「sample implementation」という言葉を用いたのは、本コードは速度に改善の余地があり、エラー処理も堅牢でない可能性があるからである。既知の問題と欠陥について詳しく知りたければ、後述の「このコードでは何が出来ないのか?」の節を見てほしい。
RAP は、その規定作業段階において、前の版の Mercator が Xt アプリケーションとの通信に使っていたプロトコル(単に「Xt プロトコル」と呼んでいた)の超集合になるものと考えられていた。RAP のメッセージ系の基幹部分は Xt プロトコルを基に作られているが、同メッセージ系は諸事情により Xt プロトコルとは異なっている。
- R6 からのフック・オブジェクト(HooksObject)の存在を受けて、複数のメッセージを追加した。 - Xt プロトコルで得た経験に基づいて、メッセージ系を「浄化」した。 - 別のスクリーンリーダ(screenreader)の開発者や他の(スクリーンリーダでない)外部エージェントの為めになると臆って、幾つか新しいメッセージを追加した。
RAP の見本実装を作り Mercator と連繋させる過程では、RAP の中の Mercator で使う当てのあった部分にしか注力しなかった。実際、この部分集合に基づいて、以前のシステムとほぼ同じ機能のスクリーンリーダをなんとか起動・実行することができたのである。
実装したプロトコル・メッセージを挙げると、
見本実装は三つのディレクトリから成る。
common | - エージェントとクライアントの両方で使う、共通のヘッダ・ファイルとコードが入っている。今のところ、共同で利用されるのは以下のファイルである。 | |
--- | ||
RapProto.h | - プロトコルの定義。 | |
RapLibint.h | - プロトコル・ストリームに対してデータの読込み書込みを行うマクロ(RAP の実装者にしか関係ない)。 | |
libXtea.a | - Will のランデヴのコード。まだ不明であるが、これを「client」ディレクトリに移し、ランデヴのエージェント側を削除するかどうするか・・・ | |
--- | ||
client | - RAP のクライアント側半分を実装したコードが入っている。この場所にあるのは以下のファイルである。 | |
--- | ||
libRap.a | - Will の libRap.a から少々部品を抜いたもの。「ICE インフラストラクチャ」のコードのみから成る。プロトコルを登録したり、メッセージとエラーのハンドラ(処理器)をインストールしたりするコードである。 | |
client-in.c | - クライアントに届くメッセージを処理するコード。大部分はかなり簡単なものである。届いたメッセージを読み、client-out.c にある適切な定型処理(routine)を呼び出して、必要なリプライ(返答)を返す。記録管理の仕事もある(追加・削除・通知など)。 | |
client-out.c | - クライアントから出て行くメッセージを生成するコード。なかなか大きなファイルであり、必要な情報を取り出す為にXt 内部のデータ構造を穿り返すコードが多数含まれている。通知(Notify)を生み出すべくフック・オブジェクトに登録されるコールバックもまた、ここに記述される。 | |
client.h | - client-in.c 及び client-out.c の関数型(prototypes)を記載。 | |
client.c | - 見本のクライアント。 | |
--- | ||
agent | - RAP のエージェント側半分を実装したコードが入っている。X の公開版にこのコードを同梱する必要は無いであろう。なぜかというと、エージェントを書く人の数は少なかろうし、API から RAP プロトコルに至るまで色々な要求があるであろうから。また、標準化しなければならないのはクライアント側半分だけなので。 エージェント側のコードは、基本的に次のようなものから出来ている。出て行くメッセージを生成する関数と、入って来るメッセージを捉え、受信メッセージ中の情報が入った C 構造体を流す関数とである(エージェントのコードでは、以後この情報を自由に使って良い)。 このディレクトリのファイルは以下のファイルである。 | |
--- | ||
agent-out.c | - エージェントからクライアントへ出て行くメッセージを生成する定型処理(routine)。大体どれもかなり簡単なものである。 | |
agent-in.c | - クライアントからエージェントに入って来るメッセージを扱う定型処理(routines)。この処理群を使って、メッセージを開封し、中身は呼出し元コードに返すべく構造体に書き込む。この構造体がもはや不用になった時のために、これを解放する定型処理(routines)も用意してある。 | |
agent.h | - agent-out.c と agent-in.c にある関数の型を宣言(prototypes)。 | |
agent.c | - 簡易エージェント。このエージェントは標準入力(stdin)から命令を読み込み、接続しているクライアントにメッセージを発する。(このエージェントは、とてもとても大雑把なので、あまり期待しないように。) |
+++ このコードでは何が出来ないのか?
以下のメッセージを実装していない。
また、プロトコルの仕様において、メッセージ・ヘッダ中の 2 バイトを RAP の続き番号(sequence number)として使うように書いてある。これは実装していないし、実際、役に立つものなのか分からない。ICE が独自の続き番号(32ビット)を備えているので、続き番号が必要な時は、単にそれを使うことにするのが良さそう。
+++ 既知の問題は?
- バイト交換(byte swapping)は行われない。メッセージを読み込む度に IceSwapping を呼び出せば、簡単に修正できる。
- 接続の切断を上手く処理できない。
- エラーの検出と処理が徹底してない(特に ICE のエラー群)。
- ウィジェットが 32 ビットの値として遣り取りされる(これは Alpha や他の 64 ビットの Xt 実装では不適切)。
- このプロトコルで遣り取りされるデータは 32 ビットに揃っており、その上で詰め物をして 64 ビットに合わせている。可変長データは全て、詰め物で 64 ビット境界に合わせる。少し工夫すれば、もっと効率的に符号化できると思われ・・・。
- RapProto.h には RAP エラー・コードの列挙定義があるものの、それが見本実装で吐かれることはない。
- 関数の実装には、まだまだ堅牢でも高速でもないものが幾つかある(主に client-out.c 内に若干数)。「query tree」系のリクエストを使うと、client-out.c 内部のコードによって再帰的にウィジェット木を手繰ることになる。この結果として得られるデータを一時的に記憶する領域(scratch buffers)の大きさは、関数呼出し時点ではわからない。そのため、先のコードでは始めから終わりまでの再帰を「二回」実行している。一回は、作業領域(scratch buffer)の大きさを確定するため、もう一回は、実際にバッファをデータで埋めるためである。もっと速く動くように実装できるはず。
- ChangeHook コールバック手続きにおいては、XtSetValues を呼び出す場合に適当とはいえない動きがある。同関数は常に、呼出し元がリソース値として設定しようと「試みた」引数値の配列を返す。しかし、様々な理由から SetValues 関数は蹉跌し得る(読込み限定のリソース、適切な変換器(converter)の闕如など)。ただ XtSetValues に渡しただけの引数配列を返すのではなく、関数呼出しを終えた後のリソースの「実際の」値を返すべきである。
+++ まだ解決していない問題は?
- X Technical Conference(X 技術会議)の後、新たなメッセージを追加した。RapFullQueryTreeRequest (及びその返答)である。メルカトルの起動処理はいつも同じである。常にアプリケーションのウィジェット木構造、各ウィジェットの各リソース、及び各リソースの各値の把握が行われる。これらを別々に求めるよりも、全部一纏まりにして取得できた方が効率良いと考えた。
おそらく多くのスクリーンリーダでも(もしかすると別種の外部エージェントでも)、このような機能を必要としていると思う。一応、より単純な RapQueryTreeRequest も依然として存在する(この関数は木構造の情報「だけ」を返し、リソースや値は返さない)。
- リソース値を返す関数は全て、以下の情報も送り返してくる。
native type | - 文字列。アプリケーションの中でリソース値が どのような表現型式で記憶されているかを表す。 |
return type | - 文字列。外部エージェントへ返って来るリソース値の 実際の型式。 |
value | - 可変長バイトの文字列(長さを含む)。return type で指定した 表現型式である。 |
私の初期の考えでは、HelloRequest/Reply の所でエージェントとクライアントの間にある種の交渉が行われるはずであった。この交渉では次の情報が授受される予定であった。エージェント側の理解できるリソースの型と、エージェント側にとって望ましい受信リソース値の型とに関する情報である。
これが本当に必要である(実際 HelloRequest と HelloReply では今のところ何らリソース型の交渉は行われない)かどうかは分らないが、native type/return type はプロトコル中に存在するこの交渉の所産なのである。
現在の実装においては、return type は常に native type と同じである(つまり、リソース値は常に native type の型で送られて来る)。
- 目下、クライアント側において GetValuesReply と ChangeNotify の実装の大規模工事が行われている。リソースの型を見ただけで、リソースの値が直接的に記録されているのか、或いは間接的に記録されているのかを判別することができない。言いかえると、外部エージェントに転送するべく XtGetValues を使って値を取り出した時、取得した値をポインタとして扱うべきであろうか、それとも・・・。
現在、型が XtRString であるか否かを直接調べている。これは、ポインタとして記憶される型の中で、メルカトルが頻繁に使用する型の一つが文字列であるから。しかし、このハックは到底受け入れられない。エージェントへ転送するために値を複写する場合について、複写の仕方の決定方法を考える必要がある。既に決定方法があって、上のハックは専ら Xt の型の仕組みに関しての私の無知による、ということなら良いのだが。
全般的に、値を取り出すコードは大雑把であり、おそらく書き直しが必要であろう。
値は常にバイト文字列として転送する(これは 32 ビットの長さを表す領域と、バイトの配列とが存在し、それらが詰め物によって 8 バイトの境界に揃うことを意味する)。このやり方は、大抵のリソース値では「非常に」効率が悪くなる。多くのリソース値は、たった一つの 32 ビット・ワード(word)と同じか、それ以下の大きさしか持たない。この方法で動作させるのは簡単であったが、効率を重視して修正すべきであろう。
プロトコルの中の殆ど全てが 32 ビットの値一つを用いて授受される(配列の長さのような情報も含む)。これは恐らく過剰であるが、しかし、このように整列するのは実に簡単であった。効率を考えると、プロトコル中の単位要素の大きさを再検討したくなる。
エージェントは、クライアントに指示を出してコアダンプさせることができる(そして実際簡単である)。エージェントからクライアントへウィジェットと GC の ID が渡ると、同クライアントはこれをポインタとして利用する。誤ったウィジェットや GC が送られた場合、クライアントは死ぬ。
これらの値を使う前に調べる方法は無かろうか。
- X 技術会議(X Technical Conference)での発表以降、私は GC の ID と結び付いた GCValues を取り出すために二つのメッセージを追加した。このコードはまだ十分に検証していない。
CreateHook は基本的にウィジェットに関する全情報を送り返してくる。この情報にはリソースやリソースの値も含まれる(基本的に FullQueryTree と同じ)。これで良いのか。大抵のスクリーンリーダではこの情報が必要だと思われるが、別のエージェントにとっては過剰であるということもあり得る。
どのリクエストとイベントをエージェントに送信するのかを選択するメッセージは、どのコールバック(通知)をインストールするのかを選択するメッセージと異なり、対称(symmetrical)ではない。AddNotify と RemoveNotify という二つのメッセージがあり、これを用いてフック・オブジェクト内のコールバックを追加・削除することが出来る。一方 SelectRequest と SelectEvent は、それまで選択されていたリクエストやイベントの組を「上書き」する。どちらの場合にも、どのリクエスト、イベント、通知が選択されているのかを知る術は無い。
- HelloRequest と HelloReply というメッセージの番いがある。元々、起動時のあらゆる交渉をここで行うつもりであった。今のところ、クライアントの最上位ウィンドウの ID をエージェントへ送り返すことにしか使われていない。x-agent のメーリングリストにおける Ralph 某の投稿で、これを ICE 認証手続きの中で行ってはどうかという提案が為されている。
私は、GC が Xlib 内でどのように動作しているのか完全には理解していない。メルカトルの古い版では、libX11 に手を加え、新たな GC を作成する度にエージェントへ通知するようにしていた。また、古いメルカトルではクライアント側の拡張が取り入れられ、XESetCreateGC を通じて GC の誕生を知ることが出来るようになっていた。私には、この二つの必要性が今一つ分からないし、GC 捕捉の最良の方法が何であるのかも分からない。
序でに、エージェント側で GC の ID を把握していない状況において、クライアントの作った全ての GC を要求する手段はあるのか。これが可能であれば、とても便利であろう。
- エージェントが初めてクライアントに接続する場合、エージェントが GC 生成リクエストの通知を求めれば何時でも、GC 生成の際に呼び出される拡張手続きがインストールされる。したがって、エージェントに X_CreateGC メッセージを送るコードは、二系統あると言える。XESetBeforeFlush で捉えて「X プロトコルから直接」という筋と、XESetCreateGC で捉えて「GC の拡張を通じて」という筋とである。
これが綺麗なやり方だとは思えないので、私は好きではない。エージェントの持っている GC がどちらのコードを通ってきたのか、当のエージェントには判別がつかないし(それとも両経路の GC に内容上の区別があるのか、私は知らない)、クライアント側のコードも、XESetCreateGC 経由で捉えた GC を X プロトコルの X_CreateGC メッセージに見せかけるべく余分な作業をしてしまっている。
- GC 拡張手続きのインストールに関する問題全般によって、次の問いが浮かんでくる。RAP は、その他のクライアント側拡張にコールバックをインストールする機能をも備えるべきであろうか。
- X リクエストをエージェントに返す際、ICE メッセージ一つにつき一つのリクエストが返る形をとっている。FluchProc に渡す引数を利用すると、複数のリクエストを一つの ICE メッセージに詰め込むことが出来るのではあるが。効率を考えると、おそらく後者の形式も実装するべきであろう。
+++ ここからどの方向へ発展させようか。