これは 【その1】ドリコム Advent Calendar 2015 - Adventar の8日目です。
7日目は すべてがKになる (@ka_nipan) | Twitter さんによる ドリコムを支えるデータ分析基盤がTD+AWSに移行した話 - かにぱんのなく頃に です。
【その2】ドリコム Advent Calendar 2015 - Adventar もあります。
自己紹介
配線工や商業作曲家等を経て、現在はドリコムにてクロスプラットフォーム開発向けのミドルウエアやデータプロテクション等の研究開発をしています。
ドリコム釣り部CFO(Chief Fishing Officer)
本日の話
私が手元で使うツールの爆速開発を支えているのがご存じMotifですが、その開発効率化に取り組んでみた話しをゆる〜くお話しします
Motifと採用の経緯に関しては去年のエントリーを参照して頂ければと思います。
ドリコムの俺を支えるUIツールキット - sazae657
MrmとUIL
GUIツールを作る際、コードでベタベタとUIを書くと各種生成、属性や座標の調整、各種解放やらのコードがどんどん増えるのはWin32 APIやCarbonで書いた事がある方ならわかって頂けると思います(おもいたい!!)
そこで、生成や外観の処理を隠蔽してしてロジックの実装に集中させてくれるDCCツールや各種フレームワークが数多くリリースされています。
いくらAPIがシンプルかつ洗練されているMotifとてUIの要素が増えてくるとその傾向は看過できない量になって、爆速開発とモチベーションに暗い影を落とし始めます。
そこで Mrm の登場です。
Mrm (Motif Resource Manager) と UIL (User Interface Language) がMotif アプリケーションにデザインとロジックの分離をもたらしてくれます
Mrmの歴史はかなり長く、1992年にリリースされた OSF/Motif 1.2に[d|i|g|i|t|a|l]によって実装されたという所までは掴めましたが、資料がほとんど無い上にその頃は小学生だったのでリリース当時の状況等は把握出来ておりません
Mrm+UIL では UIL に外観を定義してCソースにイベント処理等のロジックを書きます。
ウイジェットの生成、各種登録処理はUILを基にMrmが面倒を見てくれます。
猛烈に大雑把に書くとこのような感じになります。
UILの記法はDCEを書いたことがあればお馴染みの記法です
module モジュール名 セクション 名前 : 型 { (セクション内記述) }; end module;
文字列付きラベルウイジェット(XmLabel)を定義するとこのような記述になります
object my_label : XmLabel {
arguments {
XmNlabelString = compound_string("2015年11月26日 (木)発売!!!");
};
};
これをCで書くとこのようになります
Widget my_label; Arg arg[1]; XmString str; str = XmStringCreateLtoR( "2015年11月26日 (木)発売!!!", XmSTRING_DEFAULT_CHARSET); XtSetArg(arg[0], XmNlabelString, str); my_label = XmCreateLabel(parent ,"my_label", arg, 1); XtManageChild(my_label);
UIL 素敵 !!!
作ってみる
手元の環境は以下です
- openSUSE Tumbleweed
- OpenMotif 2.3.4
ウインドウマネージャーはtwmを愛用しています。
右端のタマネギ模様のリサイズコーナーとドラッグ時に安心感のある滑り止付きタイトルバーが最高です。
では、このようなアプリケーションをUILで書いてみます
ウインドウにテキストラベルが貼り付いていて、そこに文字が出るだけです
halo.uil
module halo object halo_world : XmRowColumn { controls { label : XmLabel { arguments { XmNlabelString = compound_string('右隣の部屋にはテレビが2台',separate=true) & compound_string('左隣の部屋にはスライスたくあんが1kgあるとする',separate=true) & compound_string('顧問がPCを1台着服したと仮定して、現在の情報処理部のPC台数を求めよ'); }; }; }; }; end module;
次に対応するCソースを書きます ※エンコーディングはEUC-JP厳守!!※
chief.c
#include <stdlib.h> #include <Xm/XmAll.h> #include <Mrm/MrmPublic.h> static char *fallback_resource[] = { "*fontList: -misc-fixed-medium-r-normal--14-*-*-*-*-*-*-*:", "*title: HALO WORLD!!", NULL }; int main(argc, argv) int argc; char **argv; { XtAppContext app_context; Widget topLevel, app; Arg al[20]; int ac; /*uilコンパイラーでコンパイルしたファイルを列挙 */ static String uid_files[] = { "halo.uid" }; MrmHierarchy hierarchy_id; MrmCode class ; /* お約束(1) */ XtSetLanguageProc( NULL, (XtLanguageProc)NULL, NULL); /* Mrmの初期化 */ MrmInitialize(); /* お約束(2) */ ac = 0; topLevel = XtAppInitialize(&app_context, "halo", NULL, 0, &argc, argv, fallback_resource , al, ac); /* UIDから MrmHierarchyを作る */ MrmOpenHierarchyPerDisplay(XtDisplay(topLevel), (MrmCount) XtNumber(uid_files), uid_files, (MrmOsOpenParamPtr*)NULL, &hierarchy_id); /* halo_world を取得 */ MrmFetchWidget (hierarchy_id, "halo_world", topLevel, &app, &class); /* halo_world を表示 */ XtManageChild(app); XtRealizeWidget(topLevel); XtAppMainLoop(app_context); return 0; }
uil をコンパイルして uid ファイルを生成
% uil halo.uil -o halo.uid
C ソースをコンパイル & リンク
% cc -g -I/usr/dt/include -c chief.c -o chief.o % cc -g -o svchost.exe chief.o -L/usr/dt/lib64 -lXm -lMrm -lXt
実行すると ※EUC-JP環境でない場合はロケールをEUC-JPに!!※
% env LANG=ja_JP.eucJP ./svchost.exe
あら簡単!
UTF-8で使ってみたかった・・・
このuilをUTF-8で保存してコンパイルすると無慈悲なメッセージを吐いてエラーに終わります。
コメント行も許されません。
UILパーサーのコメントを見ればわかりますが、最終更新は[d|i|g|i|t|a|l]による1997年です。
UTF-8に対応してたらオーパーツです。
/* * HISTORY */ #ifdef REV_INFO #ifndef lint static char rcsid[] = "$TOG: UilLexAna.c /main/14 1997/03/12 15:10:52 dbl $" #endif #endif /* * (c) Copyright 1989, 1990, DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASS. */ /*
という事でUIにマルチバイト文字を使いたい場合、以下のような選択肢があります
ですが、自分で使うツールは手早く手間無く見た目は格好良く(Motif)爆速開発したいのでゴリ押しで uil を何とかしてみます。
まず、OpenMotifのUILコンパイラーのソースを読んでみます
問題の箇所が1145行目のココです
switch (z_cell.backup) /* backup holds special processing code */ { case control_char: /* encountered a control char in a string or * comment - issue a diagnotic and continue */ issue_control_char_diagnostic( c_char ); break;
Motif / Code / [9f0120] /clients/uil/UilLexAna.c
ここで d_control_char が発生して無慈悲なエラーを吐いています
ココをコメントアウトしたバイナリーを作って、冒頭のUILをコンパイルしてみます
あ・・・
予想通り、ゴッソリ削れてしまいました。
次にエラー診断をしている箇所に飛びます
severity = diag_rz_msg_table[ message_number ].l_severity;
http://sourceforge.net/p/motif/code/ci/master/tree/clients/uil/UilDiags.c#l227
diag_rz_msg_table[d_control_char] の l_severityでエラーレベルが定義されています d_control_char = 9ですので、この定義を見ると
typedef struct { XmConst int l_severity; char XmConst *ac_text; } diag_rz_msg_entry; XmConst diag_rz_msg_entry diag_rz_msg_table[81] = (略) { 3, msg9 },
Motif / Code / [9f0120] /clients/uil/UilMessTab.h
d_control_char の l_severity は 3 となっているので、展開すると以下のようになります
{ 3, "unprintable character \\%d\\ ignored" }
l_severity = 3の定数を見てみます
#define Uil_k_error_status 3
Motif / Code / [9f0120] /clients/uil/Uil.h
d_control_char の severity は Uil_k_error_status という事になっていそうです
UilDiags.c の診断ルーチン では Uil_message_count[Uil_k_error_status] の件数をインクリメントしています
もうダメそうな匂いが漂ってきました・・・
Uil_message_count[ severity ]++;
流れ流れてエントリーポイントに戻ってきました Uil_message_count[Uil_k_error_status] はUilDiags.cでインクリメント済みで明らかにゼロではないのでチェックメイトです。
/* If there are any error/severe messages, then don't return */ /* a symbol table for the callable compiler - clean up here */ if ( Uil_message_count[Uil_k_error_status]>0 || Uil_message_count[Uil_k_severe_status]>0 ) {
Motif / Code / [9f0120] /clients/uil/UilMain.c
という事で・・・
最初に上げた switch 文で z_cell.backup が control_char になった状態を見てみます
わかりやすいように、UILの文字列は compound_string('ゆずこしょう'); に変更しました
(gdb) p/x z_cell $2 = {action = 0x4, next_state = 0x8, backup = 0x1, unused = 0x0}
z_cellを作っている場所を探します
l_class = class_table[ c_char ]; /* determine its class */ z_cell = token_table[ l_state][l_class ]; /* load state cell */
Motif / Code / [9f0120] /clients/uil/UilLexAna.c
関連変数を見ていきます
(gdb) p/x c_char $3 = 0x82 (gdb) p/x l_state $4 = 0x8 (gdb) p/x l_class $5 = 0x10
class_table[c_char = 130] とtoken_table[l_state = 8][l_class = 16]を見てみます
static cell XmConst token_table[ max_state+1][ max_class+1] = { (中略) /* class_illegal */ { special, state_str_1, control_char }, static char class_table[ 256 ] = { (中略) /* 80 */ class_illegal, class_illegal, class_illegal, class_illegal,
Motif / Code / [9f0120] /clients/uil/UilLexAna.c
つまるところ
「ゆ」(0xE3 0x82 0x86) は0x82から文字コードとして class_illegal 扱いになって、制御文字(control_char)として片付けられていました。
ここまで来たら・・・
事象は掴めたので即行動してみました。
Motif は UTF-8に対応しているので、UIL コンパイラーの文字判定で class_illegal+control_charを喰らったらUTF-8判定を掛けて、処理を強行する簡単な改造を実施してみました
(デカいのでgistに貼りました)
UILにUTF-8を無理矢理通すゴリ押し · GitHub
手元で使えりゃいーじゃんレベルですので厳密な検証はしていません、ご了承ください
ちゃんと表示された!!!
UILを使いまくろう
Motifで超絶面倒くさいと評判のメニューバーとプルダウンメニューを扱ってみます。
コードで書くとXmCreateXXの嵐で大変な事になるのですが、UILで書けば定義するだけでコードの変更は必要ありません
冒頭のコードにメニューバーの定義を追加して実行してみます
object menu_bar : XmMenuBar { controls { XmCascadeButton main_menu; }; }; object main_menu : XmCascadeButton { controls { XmPulldownMenu { controls { member1 : XmPushButton { arguments { XmNlabelString = compound_string("紫"); }; }; member2 : XmPushButton { arguments { XmNlabelString = compound_string("ピンク"); }; }; member3 : XmPushButton { arguments { XmNlabelString = compound_string("明るい茶色"); }; }; }; }; }; arguments { XmNlabelString = compound_string("情報処理部"); }; };
あら簡単!
次はメニューが選択された時の処理を書いてみます
イベント駆動ですのでコールバックを登録します。
まず、UILにイベントと呼んで欲しい関数を宣言します。
procedure member_select();
次に呼んで欲しいウイジェットの呼んで欲しいイベントに定義したコールバックを設定します 今回はメニュー項目が選択されたら member_select() というコールバックを呼んでもらいます
member1 : XmPushButton { arguments { XmNlabelString = compound_string("紫"); }; callbacks { XmNactivateCallback = procedure member_select(); }; };
あとはSystem.Reflectionで・・・という訳には行かないのでCのコードに定義を追加します。
呼び出し元のラベルの文字列を取得してコンソールに出力するコードを書いてみます
/* member_select コールバック */ static void member_select(widget, call_data, client_data) Widget widget; XtPointer client_data; XtPointer call_data; { XmString str; char* cstr; Arg ar[1]; /* 呼び出し元のラベルの文字列を取得してみる */ XtSetArg(ar[0], XmNlabelString, (XtArgVal)&str); XtGetValues(widget, ar, 1); /* C文字列へ変換 */ cstr = (char *)XmStringUnparse(str, NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL); /* コンソールに出してみる */ fprintf(stderr, "SELECT: %s\n", cstr); /* 後始末 */ XtFree(cstr); XmStringFree(str); }
つぎに MRMに登録する為のリストを定義します。
key - valueで列挙していく形です (スクリプト言語の拡張モジュールを書いたことがあればお馴染みですね)
static MrmRegisterArg register_args[] = { {"member_select", (XtPointer) member_select } };
これで member_select() を member_select という名前でマッピングしました
最後に MRMへコールバックを登録します。
Hierarchyへ登録するタイプとグローバルへ登録する2タイプがありますが、今回はHierarchyへ登録します。
/* Hierarchy へコールバックの登録 */
MrmRegisterNamesInHierarchy (hierarchy_id,
register_args, XtNumber(register_args));
あとは実行するだけです
あら簡単!
後ろのxtermはUTF-8ですので、ちゃんとウイジェットからUTF-8で取れています。
参考資料など
20年ほど前に書籍などが出版されていたようですが、絶版です。
OpenMotifのソースコードとman位しか現存していません。。。。
まとめ
Motif はかっこいい
Motif はモダン
Motif 最高
9日目は [hang chang] さんです