SND-MSG命令コードとメッセージ サブファイル
もしあなたが、今もグリーンスクリーン アプリケーションを書いている大勢のIBM i プログラマーのうちの1人であるとしたら、あなたが書くプログラムが、ユーザーとどのようにコミュニケーションを取っているか考えてみてください。ユーザーに対して、今、入力した値が無効だということや、先に進むにはコマンド キーを押す必要があるということを、どのようにして伝えているでしょうか。いくつかの方法を目にしてきましたが、よく用いられている方法(私のお気に入りでもあります)は、メッセージ サブファイルを通じてコミュニケーションする方法です。
メッセージ サブファイルの素晴らしいところは、一度に複数のメッセージを伝えられる点です。私は、コンピューターにできるだけ多くのエラーを見つけてもらって、ユーザーがそれらをすべて処理してから再試行できるようにしたいと思います。一度に1つのエラーを修正するように求められると、そのアプリケーション(さらにはWebページも)を使用するのが不愉快になってしまいます。しかし、そのように動作するグリーンスクリーン アプリケーションを目にしたことがあります。あるいは、私もいくつか書いたことがあるかもしれません。
プログラムでメッセージ サブファイルを使用するには、表示装置ファイルでメッセージ サブファイルと、対応するサブファイル制御レコードを定義する必要があります。以下は、私がいつも使用しているソース コードです。唯一、変更するのは、SFLMSGRCDキーワードの値であり、これは、画面サイズ(24×80または27×132)あるいはウィンドウ サイズに応じて変更します。
A R MSGSFL SFL
A SFLMSGRCD(24)
A MSGKEY SFLMSGKEY
A PGMNAME SFLPGMQ
A R MSGCTL SFLCTL(MSGSFL)
A OVERLAY
A SFLDSP
A SFLDSPCTL
A SFLINZ
A N99 SFLEND
A SFLSIZ(10)
A SFLPAG(1)
A PGMNAME SFLPGMQ
RPGプログラムは、以下の方法でメッセージ サブファイルと対話します。
- プログラムが、プログラム メッセージ待ち行列に適切なメッセージを送信する。
- ユーザーがそれらのメッセージを表示した後で、プログラムはプログラム メッセージ待ち行列をクリアする。
プログラム メッセージ待ち行列へメッセージを送る通常の方法は、「プログラム・メッセージの送信(Send Program Message)」(QMHSNDPM)APIを使用する方法です。ユーザーにメッセージが表示され続けないように、メッセージを削除するには、「プログラム・メッセージの削除(Remove Program Messages)」(QMHRMVPM)APIを使用します。『IT Jungle』では、長年にわたって、こうしたAPIをテーマとした、かなりの数の記事を公開してきました。 こちらの記事 は、20年前のものです。ソース コードは旧式ですが、それでも根本部分は今でも通用します。
SND-MSG命令コードの導入により、IBMがこのプロセスをさらに簡単にしてくれたことを、先日知って嬉しく思いました。SND-MSGとQMHSNDPMは、ほぼ同じ処理を行いますが、SND-MSGの方が使いやすいと思います。IBMがそれと同等のRPGの機能を提供していないため、まだQMHRMVPMは必要です。
以下に、興味をお持ちであれば、コンパイルして試してみることができる例を示します。まず、以下は、表示装置ファイルTHQ0051DのDDSです。
A DSPSIZ(24 80 *DS3)
A CA03(03 'F3=EXIT')
A R REC1
A OVERLAY
A 1 36'Data Entry'
A 3 3'Fill in the blanks, press Enter.'
A 5 3'Your name . . . . . :'
A NAME 24 B 5 25
A 61 DSPATR(PC RI)
A 7 3'Your age . . . . . :'
A AGE 3 0B 7 25
A 62 DSPATR(PC RI)
A 9 3'Your address . . . :'
A STREET 24 B 9 25
A 63 DSPATR(PC RI)
A 10 5'City, state, ZIP :'
A CITY 16 B 10 25
A 64 DSPATR(PC RI)
A STATE 2 B 10 43
A 65 DSPATR(PC RI)
A ZIP 5 0B 10 47
A 66 DSPATR(PC RI)
A 23 5'F3=Exit'
A R MSGSFL SFL
A SFLMSGRCD(24)
A MSGKEY SFLMSGKEY
A PGMNAME SFLPGMQ
A R MSGCTL SFLCTL(MSGSFL)
A OVERLAY
A SFLDSP
A SFLDSPCTL
A SFLINZ
A N99 SFLEND
A SFLSIZ(10)
A SFLPAG(1)
A PGMNAME SFLPGMQ
REC1は、単純なデータ入力フォーマットです。ユーザーは、氏名、年齢、住所を入力します。
以下は、RPGプログラムTHQ0051Rです。画面の処理を行います。
**free
ctl-opt actgrp(*new) option(*srcstmt: *nodebugio);
dcl-f THQ0051D workstn;
dcl-ds psds psds;
PgmName char(10);
end-ds;
dcl-pr QMHRMVPM extpgm;
CallStackEntry char(10) const;
CallStackCounter int (10) const;
MessageKey char( 4) const;
MessagesToRemove char(10) const;
ErrorCode int (10) const;
end-pr QMHRMVPM;
dcl-s DataIsValid ind;
*inlr = *on;
dou *in03
or DataIsValid;
write MsgCtl;
exfmt Rec1;
QMHRmvPM (PgmName: *zero: *blanks: '*ALL': *zero);
if not *in03;
exsr Validate;
endif;
enddo;
// Do something with the data
return;
begsr Validate;
%subarr(*in: 61: 6) = *all'0';
if Name = *blanks;
*in61 = *on;
Snd-Msg 'Your name is required.';
endif;
if Age < 18;
*in62 = *on;
Snd-Msg 'Age must be 18 or greater.';
endif;
if not (State in %list('TX': 'OK': 'LA'));
*in65 = *on;
Snd-Msg 'The state "' + State + '" is not authorized.';
endif;
// Similar code to validate the other fields.
DataIsValid = not ( *in61 or *in62 or *in63
or *in64 or *in65 or *in66 );
endsr;
Validateサブルーチンに注目してください。即時通知メッセージをプログラム メッセージ待ち行列へ送信する、SND-MSG命令の簡単さにお気付きでしょうか。これより簡単な方法を私は知りません。
例を分かりやすくしたいという思いから、DDSおよびRPGソース コードは、短く、単純にしています。このコードを本番用に書くとしたら、以下のように、いくつか機能強化を行うでしょう。
- コンパイラーがグローバル変数をフィールドに割り当てないように、 表示装置ファイルを修飾 する。
- グローバルなデータを使用する代わりに、パラメーターを通じてデータを渡すことができるように、データを検証するのにサブルーチンではなくサブプロシージャーを使用する。
- グローバルなキャッチオール モニター を追加して、ユーザーが「プログラム・メッセージの表示(Display Program Messages)」画面を表示することのないようにする。幸いにも、この状況で有用な「エスケープ・メッセージの再送信(Resend Escape Message)」(QMHRSNEM)APIがIBMから提供されている。
以下は、同じアプリケーションに対して、少し機能強化を行ったものです。表示装置ファイルで変更が必要なのは1か所のみです。すなわち、RPGプログラムでファイルを修飾するには、表示装置ファイルのDDSにINDARAキーワードを指定する必要があります。
A DSPSIZ(24 80 *DS3)
A INDARA
A CA03(03 'F3=EXIT')
. . . and so on . . .
以下は、前述の変更を行ったRPGプログラムです。
**free
ctl-opt main(THQ0054R) actgrp(*new) option(*srcstmt: *nodebugio);
dcl-f Display workstn qualified
extdesc('THQ0052D') extfile(*extdesc)
alias
indds(wsi)
usropn;
dcl-ds Rec1_t LikeRec(Display.REC1 : *all) template;
dcl-ds MsgCtl_t LikeRec(Display.MsgCtl: *all) template;
// Indicator data structure for display file
dcl-ds wsi len(99) qualified inz;
ExitRequested ind pos( 3);
ErrorInds char(6) pos(61);
ErrorInd_Name ind overlay(ErrorInds: 1);
ErrorInd_Age ind overlay(ErrorInds: 2);
ErrorInd_Street ind overlay(ErrorInds: 3);
ErrorInd_City ind overlay(ErrorInds: 4);
ErrorInd_State ind overlay(ErrorInds: 5);
ErrorInd_Zip ind overlay(ErrorInds: 6);
end-ds wsi;
dcl-ds psds psds qualified;
PgmName char(10);
end-ds;
dcl-pr QMHRMVPM extpgm;
CallStackEntry char(10) const;
CallStackCounter int (10) const;
MessageKey char( 4) const;
MessagesToRemove char(10) const;
ErrorCode int (10) const;
end-pr QMHRMVPM;
dcl-pr QMHRSNEM extpgm;
MessageKey char( 4) const;
ErrorCode int (10) const;
end-pr QMHRSNEM;
dcl-proc THQ0054R;
monitor;
THQ0054R_Main ();
on-error;
QMHRsnEM (*blanks: *zero);
endmon;
end-proc THQ0054R;
dcl-proc THQ0054R_Main;
dcl-s DataIsValid ind;
dcl-ds Rec1_data likeds(Rec1_t) inz;
dcl-ds MsgCtl_Data likeds(MsgCtl_t) inz;
open Display;
dou wsi.ExitRequested
or DataIsValid;
MsgCtl_data.PgmName = psds.PgmName;
write Display.MsgCtl MsgCtl_data;
exfmt Display.Rec1 Rec1_data;
QMHRmvPM (psds.PgmName: *zero: *blanks: '*ALL': *zero);
if not wsi.ExitRequested;
Validate (Rec1_data: DataIsValid);
endif;
enddo;
// Do something with the data
close Display;
end-proc THQ0054R_Main;
dcl-proc Validate;
dcl-pi *n;
inRec1_data likeds(Rec1_t) const;
ouDataIsValid ind;
end-pi;
wsi.ErrorInds = *zeros;
if inRec1_data.Name = *blanks;
wsi.ErrorInd_Name = *on;
Snd-Msg 'Your name is required.' %target(psds.PgmName);
endif;
if inRec1_data.Age < 18;
wsi.ErrorInd_Age = *on;
Snd-Msg 'Age must be 18 or greater.' %target(psds.PgmName);
endif;
if not (inRec1_data.State in %list('TX': 'OK': 'LA'));
wsi.ErrorInd_State = *on;
Snd-Msg ('The state "' + inRec1_data.State + '" is not authorized.')
%target(psds.PgmName);
endif;
// Similar code to validate the other fields.
ouDataIsValid = (wsi.ErrorInds = *zeros);
end-proc Validate;
このコードのすべてを説明する必要はないと思いますが、SND-MSG命令に対する変更については指摘しておく必要があるでしょう。それがこの記事のテーマだからです。SND-MSGがサブプロシージャー内にあるため、%TARGETキーワードを使用して、プログラム メッセージ待ち行列の名前を指定する必要があります。
Snd-Msg 'Your name is required.' %target(psds.PgmName);
また、RPGプログラムは、メッセージ サブファイル レコードそのものではなく、メッセージ サブファイル制御レコードを使用することについても指摘しておきたいと思います。これは、表示装置ファイルが修飾されているかどうかにかかわらず、そうなります。
コミュニケーション能力というのは、人生のあらゆる場面で重要なものです。とは言っても、話し言葉であれ、書き言葉であれ、うまくコミュニケーションを取る能力が身に着いている人が多くないというのは実に残念なことです。しかし、幸いなことに、グリーンスクリーン プログラムにユーザーとコミュニケーションを取らせることは難しいことではありません。そして、SND-MSG命令コードが加わったことで、これまで以上に簡単になっています。