Super Technique 講座

Xプログラミング入門〜プログラミング・モデル

このページではXプログラミングの初歩を解説する。だから、少しも「Super」な内容ではない。しかし、「単に書けりゃいいやん」というノリの解説ではなくて、Xプログラミングの全体像を理解することを目的として書いたのである。まあ、原則的に Ahtena ツールキットによるプログラミングをベースにしているが、他のツールキットのやり方もある程度見据えている。


プログラミング・モデル

Xのツールキットを使ったプログラムでさえも、このような「Xlib〜イントリンシクス〜Widet」への階層構造を意識して扱う必要がある。なぜならば、画像の表示といったイメージ操作は、直接ウィジットによって扱われないので、Xlib によって定義される。だから、ツールキットの中から Xlib の関数を呼び出してプログラムする必要があり、また、そのためツールキットのウィジットから Xlib で使う Window などの情報を取得するマクロが用意されている。そのため、Xlib とツールキットを混在させれば、技術力によってはいくらでも自由度の高いプログラムを書くことができる。それだけではなく、複数のツールキットによって定義されたウィジットを混在させて使ったり、自前でウィジットを定義したりすることさえ可能なのである。

このような階層構造について混乱しないために、関数名やマクロ名の命名に関する通則もあったりする。これらを整理してまず紹介しよう。


Xlib

最下層でXサーバとの通信を行うライブラリである。大きく分けて Xlib では3種類の関数などが定義されている。

  1. まず、実際の通信を制御する関数。これには XOpenDisplay(現実の接続を行う)、XFlush(キューに溜ったリクエストを強制的にサーバに送る)、XCloseDisplay(接続を切断する) があり、大変重要な役割を果たしているが、プログラマが操作できる関数は少ない。
  2. 実際のXプロトコルを発行する関数。これはXプロトコルの数だけ存在するのと同時に、Xプロトコルの機能を、フルスペックではなく一部だけの機能に限定して発行するコンビニエンス関数と呼ばれるものもある。たとえば、ウィンドウを生成する XCreateSimpleWindow() など、実に数が多く、全部で 400個程度ある(プロトコルは130個弱)。
  3. すでに生成された構造体などから、興味のあるデータを抜き出すもの。これはほとんどマクロとして定義される。たとえば、ルートウィンドウを取得する RootWindow( d, 0 ) など。

以上の例で見たように、Xlib 関数は必ず「X」で始まり、マクロは「X」以外の大文字で始まることがわかる。

また、Xlib は通信を行うライブラリなので、基本的に大量のデータを送信するのはオーバーヘッドになる。だから、データ型として、ウィンドウを表す Window, グラフィックコンテキストを表す GC, カラーマップを表す Colopmap などの「型」があるが、これらは実際にはサーバから「ハンドル」としてそれらをサーバ内部で識別するためのID番号が返されるだけである(long 型整数)。

ヘッダファイルは X11/Xlib.h がメインのヘッダファイルとなる。あと、Xutil.h や Xatom.h などが必要になるケースがある。ライブラリは libX11.so である。

プログラムの例は次の通り。現実的には Xlib だけを使ってアプリケーションを作るのは、Wizard技と見なされることが多い。きめ細かい制御は当然可能だが、その面でリソースとの連係が難しい。リソースによって制御されるはずのさまざなな数値が即値で引数になっていることがプログラムから判るだろう。

#include <stdio.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>

int main( int argc, char **argv )
{
     Display *d;
     XTextProperty win_name;    
     Window top, lab, quit;
     GC gc;
     XEvent event;
     char *lab_str = "Xlib(not use ToolKit!)";
     char *quit_str = "Quit";

     /* サーバとの接続 */
     if( (d = XOpenDisplay( NULL )) == NULL ) {
          fprintf( stderr, "Xサーバに接続できません\n" );
          return 1;
     }

     /* メインウィンドウの作成 */
     top = XCreateSimpleWindow( d, RootWindow( d, 0 ), 100, 100, 
                                150, 60, 1, 
                              /* 幅 高さ 境界線 .... カスタマイズ不可 */
                                BlackPixel( d, 0 ),
                                WhitePixel( d, 0 ) );

     /* ウィンドウ名などのプロパティのセット(こんなこともしないと....) */
     win_name.value = "XLib";
     win_name.encoding = XA_STRING;
     win_name.format = 8;
     win_name.nitems = strlen( win_name.value );
     XSetWMName( d, top, &win_name );

     /* GC の作成 */
     gc = XCreateGC( d, top, 0, 0 );

     /* サブウィンドウの作成 */
     lab = XCreateSimpleWindow( d, top, 10, 5,
                                140, 20, 0, BlackPixel(d,0), WhitePixel(d,0) );

     quit = XCreateSimpleWindow( d, top, 50, 30,
                                40, 20, 1, BlackPixel(d,0), WhitePixel(d,0) );

     /* 受け取るイベントをサーバに通知 */
     XSelectInput( d, top, ExposureMask );
     XSelectInput( d, quit, ButtonPressMask | EnterWindowMask | LeaveWindowMask );

     /* ウィンドウのマップ */
     XMapSubwindows( d, top );
     XMapWindow( d, top );
     XFlush( d );

     /* イベントループ。XtAppMainLoop() もこんなことをしている*/
     while( 1 ) {
          XNextEvent( d, &event );  /* イベントキューから取り出す */
          switch( event.type ) {
          case Expose:        /* 再描画など */
               if( event.xexpose.count == 0 ) {
                    XDrawString( d, lab, gc, 3, 15, lab_str, strlen(lab_str) );
                    XDrawString( d, quit, gc, 8, 15, quit_str, strlen(quit_str) );
                    XFlush( d );
               }
               break;
          case ButtonPressMask:  /* クリックがなされた */
               return 0;
               break;
          case EnterNotify:     /* マウスがウィンドウに入った */
               XSetWindowBorderWidth( d, quit, 2 );
               XFlush( d );
               break;
          case LeaveNotify:     /* マウスがウィンドウから出た */
               XSetWindowBorderWidth( d, quit, 1 );
               XFlush( d );
               break;
          }
     }
}

とはいえ、ドローツールなどでは、ウィンドウに対する描画が必要になる。この機能は Xlib レベルでサポートされているので、Xlib に関する知識が必要になる。たとえば GC や Pixmap の使い方を理解しなくてはならない。


X Intrinsics

イントリンシクスではウィジットを実装するための型紙に当る機能と、実際にウィジットを使ってクライアント・プログラムをするための機能とがある。ここではウィジットを実装するための機能については触れない。

イントリンシクスの関数で普通使うものには次のものがある。実に数が少ない。

XtVaAppInitialize()
ツールキットを初期化する。要するに通信を開き、リソースを初期化する。
XtVaCreateManagedWidget()
これが各種ウィジットのインスタンスを生成する関数である。各ウィジットセットに対してそれぞれの生成関数があるのではなく、どんなウィジットセットに対しても、この関数で生成することになっている。Va のついた関数名は、一般に可変長引数であることを示すが、要するにリソースも同時に設定できるのである。
XtAddCallback()
生成されたウィジットに「コールバック」を与え、何かのイベントが起きたらその関数が実行されるようにセットする。
XtRealizeWidget()
生成されたウィンドウを表示する。
XtAppMainLoop()
メインループ(イベントループ)を開始する。

これで見るように、イントリンシクスの関数はすべて「Xt」で始まる。これはイントリンシクスで定義される各種マクロも同じである。

また、各種データについては、書かれたプログラムの移植性などを考慮して、特別な型がイントリンシクスの中で定義されている。Cardinal(符号なし整数)、XtPointer(voidポインタ)、String(char *)など、一見して何の typedef か判るものもある。しかし、ウィジットを示す Widget 型は、ウィジットインスタンス構造体へのポインタである。

イントリンシクスのヘッダファイルは X11/Intrinsic.h が主となるが、文字定数を定義した X11/StringDefs.h も必須になる。ライブラリは libXt.so である。


狭義のツールキット

さて、狭義のツールキットで定義されるのは、具体的なウィジットである。だから、標準的なイントリンシクスを使ったツールキットでは、個別に生成などの関数を持つのではなく、標準的な手段でどのウィジットセットも生成する。

一応、どんなX環境でも間違いなく使えるのは、Xのディストリビューションと同梱されて配付される Athena ツールキットである。Athena ツールキットは、イントリンシクスによるツールキット作成の見本として作られたものである。あまり見た目は良くなく、ウィジットの機能もあまりないために、さまざまなウィジットセットが商品/フリーを問わず作られて使われている。イントリンシクスの上にほぼ Athena と同様の方法で定義されたウィジットセットには、Athena の表示を3次元化しただけで互換性のある Xaw3d や、商用ツールキットとして流行した Motif がある。これらのプログラミングは基本的に Athena のものと大差はなく、単に生成するウィジットクラス名とリソース名が違う程度で、プログラム構造はそのまま流用できるほどである。

だから、狭義のツールキットでは、作成された具体的なウィジットに対して、何か特別な状態変更を求める関数以外は定義されない。ヘッダファイルの中を見ても、そのウィジットを示すクラスの extern 定義と、そのウィジットのリソースで使う文字定数だけであることが多い。

一部のウィジットは具体的なウィジットを操作するための関数を持つ。たとえば、「リスト」を Athena Widget で定義するクラス listWidgetClass には、XawListChange(リスト内容を動的に変更する)、XawListHighLight(リスト内容の指定部をハイライト表示する)など、どうしても関数にしないと都合が悪い機能が、関数として定義されている。同じ機能は Motif では、XmListReplaceItems() や XmSelectPos() などで実現する。

なので、関数名では、ウィジットセットの間での共通性はまったくない。しかし各ウィジットセットは、各セット名の略称を頭に付けた名称で統一されている。たとえば、Athena では「Xaw」、Motif では「Xm」をそれぞれ共通した接頭辞として使う。

ヘッダファイルは各ウィジットごとに分かれているのが普通である。たとえば、Athena の Label.h には、そのウィジットで設定可能なリソースの一覧の他に、labelWidgetClass で使うリソース設定用の文字定数や、固有の操作関数のヘッダが収められる。各ウィジットセットは通常 X11/ の下に個別の接頭辞に応じたディレクトリを作り、 そこに収められる。だから、Athenaのラベルは X11/Xaw/Label.h をヘッダファイルとし、Motif のラベルは X11/Xm/Label.h をヘッダファイルとする。だから、ヘッダファイルが各ウィジットの最大のマニュアルであると言っても過言ではない。

ライブラリはウィジットセットごとにまとまっている。Athena なら libXaw.so であり、Motif なら libXm.so である。この中にすべてのウィジットセットの実体が収められている。

このため、ツールキットプログラムの場合には、沢山のライブラリをリンクする必要があった。しかし、現在では共有ライブラリでほとんどサポートされているために、ツールキットのライブラリだけをリンクするだけで大体足りる。Athena と Motif の場合のコンパイルオプションを筆者の環境で示す。

athena  :       athena.c
        $(CC) -Wall -I/usr/X11R6/include -L/usr/X11R6/lib -o $@ $< -lXaw

motif   :       motif.c
        $(CC) -Wall -I/usr/X11R6/include -L/usr/X11R6/lib -o $@ $< -lXm -lXt

ひょっとしたら Motif がフリーの Lesstif である時、Linux とのライブラリバージョン問題で次のようなメッセージが出ることがある。

/usr/X11R6/lib/libXm.so: undefined reference to `_xstat'
collect2: ld returned 1 exit status

これは要するに、libc の内部で使う xstat という名の関数が、バージョンによってあったりなかったりするために起きているようである。これを回避するためには、次のライブラリをリンクするというクィックハックが存在する。阿呆な話だが、こんなことに引っかかっているのは時間の無駄というものだ。

motif   :       motif.c
        $(CC) -Wall -I/usr/X11R6/include -L/usr/X11R6/lib -o $@ $< -lXm -lNoVersion-2.1.2 -lXt
#include <stdio.h>
#include <unistd.h>
#include <X11/StringDefs.h>

#include <X11/Intrinsic.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Command.h>

/* コールバック関数 */
void Quit( Widget w, XtPointer client, XtPointer called )
{
     exit( 0 );
}

int main( int argc, char **argv )
{
     XtAppContext app_con;
     Widget top, form, lab, quit;

     /* ツールキットの初期化 */
     top = XtVaAppInitialize( &app_con, "Athena", NULL, 0, &argc, argv, NULL, NULL );
                                    /*  クラス名       引数のリソース指定を解釈*/
     /* 台紙ウィジットの作成 */            /* ウィジットクラス */
     form = XtVaCreateManagedWidget( "sheet", formWidgetClass, top, 
                                   /* ウィジット名 */      /* 親ウィンドウ */
 /* リソースの指定 */                XtNorientation, XtorientVertical,
 /* いくつでもOKなので最後はNULL */  NULL );
     lab = XtVaCreateManagedWidget( "lab", labelWidgetClass, form,
         /* ラベルの内容 */         XtNlabel, "Athena Widget",
         /* 横幅 */                 XtNwidth, 150,
         /* 高さ */                 XtNheight, 20,
         /* 境界線の太さ */         XtNborderWidth, 0,
                                    NULL );
     quit = XtVaCreateManagedWidget( "quit", commandWidgetClass, form,
                                     XtNlabel, "Quit",
                                     XtNwidth, 50, XtNheight, 20,
                                     XtNborderWidth, 1,
         /* 配置方法 */              XtNfromHoriz, NULL,
         /* ウィンドウの左端から */  XtNhorizDistance, 53,
         /* lab ウィジットの下に */  XtNfromVert, lab,
         /* 5pixおいて */            XtNvertDistance, 5,
                                     NULL );
     /* コールバックの登録  リソース*/
     XtAddCallback( quit, XtNcallback, Quit, NULL );
                 /* 対象ウィンドウ     関数名 */

     XtRealizeWidget( top ); /* ウィンドウの具現化。これを呼ばないと表示もしない */
     XtAppMainLoop( app_con ); /* メインループに突入 */
     return 0;
}



copyright by K.Sugiura, 1996-2006