CLP の MONMSG のように C/400 でもエラーを監視することができる。
しかしエラーをMONMSG してエラー・メッセージの内容を取り出して
メッセージを出力するにはテクニックが必要となる。
初めに
#include <errno.h>
:
volatile _INTRPT_Hndlr_Parms_T ca;
これがエラー・モニター・ハンドラー
#pragma exception_handler(MONMSG, ca, 0, _C2_MH_ESCAPE | _C2_ALL,
_CTLA_HANDLE)
(これが CLPの MONMSG に相当する。)
:
(何かエラーが発生 ?! )
MONMSG:
#pragma disable_handler
(エラーが発生すればここにジャンプする)
という記述において、まず
#pragma exception_handler(MONMSG, ca, 0, _C2_MH_ESCAPE | _C2_ALL,
_CTLA_HANDLE)
の _C2_ALL によってすべての CPFエラーを監視することを意味している。
例外エラーが発生するとエラー・モニター・ハンドラー ca の中に値が入る。
ca とは #include の中に定義されていて
【構造体: _INTRPT_Hndlr_Parms_T】
/* Interrupt handler parameter block */
typedef _Packed struct {
unsigned int Block_Size; /* Size of the parameter block
_INVFLAGS_T Tgt_Flags; /* Target invocation flags
char reserved[8]; /* reserved
_INVPTR Target; /* Current target invocation
_INVPTR Source; /* Source invocation
_SPCPTR Com_Area; /* Communications area
char Compare_Data[32]; /* Compare Data
char Msg_Id[7]; /* Message ID
char reserved1; /* 1 byte pad
_INTRPT_Mask_T Mask; /* Interrupt class mask
unsigned int Msg_Ref_Key; /* Message reference key
unsigned short Exception_Id; /* Exception ID
unsigned short Compare_Data_Len; /* Length of Compare Data
char Signal_Class; /* Internal signal class
char Priority; /* Handler priority
short Severity; /* Message severity
char reserved3[4]; /* reserved
int Msg_Data_Len; /* Len of available message data
char Mch_Dep_Data[10]; /* Machine dependent date
char Tgt_Inv_Type; /*Invocation type (in MIMCHOBS.H)
_SUSPENDPTR Tgt_Suspend; /* Suspend pointer of target
char Ex_Data[48]; /* First 48 bytes of excp. data
} _INTRPT_Hndlr_Parms_T;
かなりの十分な情報が収められているように見えるが実は肝心なところが
足らない。
エラー・メッセージを再現しようと思うとエラー・メッセージID は
Exception_Id;
であるが、これも CPFMSG のメッセージ番号だけである。
MSH エラーである場合の取得は不明である。
次に
Ex_Data[48]
がメッセージ・データであるが
「First 48 bytes of excp. data」とあるように、これは
メッセージ・データの最初の 48バイトに過ぎない。
つまりこのデータだけでは発生したエラーの完全なエラー・メッセージ全体を
受け取ることはできない。
となると残る手段は Msg_Ref_Key; (Message reference key=メッセージ参照キー)
である。
メッセージ参照キーとは各メッセージ毎に自動的にシステムによって与えられている
独自の特異な4桁のキー値のことである。
これによって元のメッセージを発したメッセージそのものを取得することができる。
例えばこうだ。
typedef struct {
Qmh_Rcvpm_RCVM0200_t rcvm0200_t;
char Msg[3000];
} RCVM0200;
RCVM0200 rcvm0200;
memcpy(msgkey, (void*)&ca.Msg_Ref_Key, 4);
msg_dta_len = sizeof(RCVM0200);
QMHRCVPM(&rcvm0200, msg_dta_len, "RCVM0200", "* ",0,
"*ANY ", msgkey, wait_time,"*SAME ", &errcode);
これによってrcvm0200.Msg にはエラー・メッセージの文体が保管されることになる。
次に取得したメッセージをまたこのプログラムの呼出し元に送信しなければならない。
それはこのジョブが対話式であるのか、またはバッチ・ジョブであるかによって
異なってくる。
バッチ・ジョブであれば *SYSOPR (QSYSOPR) にメッセージを送信しないと
操作員の目に触れることはない。
もうひとつ重要なことはバッチ・ジョブの場合は *SYSOPR にメッセージを送信するだけで
よいかというと、それだけではない。
もしこのジョブがさらに上位のプログラム(CLP)などに呼び出されている場合を想定すると
if(type[0] == '0'){/* バッチ・ジョブ */
/* *SYSOPR にメッセージを送る */
QMHSNDPM(msgid, "QCPFMSG QSYS ", msgdta, msg_dta_len, "*ESCAPE ",
"*SYSOPR ", pgm_stk_cnt, " ", &errcode);
}/* バッチ・ジョブ */
として *SYSOPR に送信するだけでは済まない。
というのは上位のプログラムは、このプログラムがエラーとして異常終了していないかと
監視している可能性がある。
史実、エラーとなったのであるから *SYSOPR だけに送信したのでは上位の呼び出し元の
プログラムのエラー監視に引っかからなくなってしまう。
従って上位にも送信が必要なのであるが *ESCAPE メッセージを二つ続けて送信することは
できない。
最初の *ESCAPE の送信でこのプログラム自身がエラー終了してしまうからである。
そこで
if(type[0] == '0'){/* バッチ・ジョブ */
/* *SYSOPR にメッセージを送る */
QMHSNDPM(msgid, "QCPFMSG QSYS ", msgdta, msg_dta_len, "*DIA
"*SYSOPR ", pgm_stk_cnt, " ", &errcode);
/* この *PGMBDY にもメッセージを送る */
QMHSNDPM("CPF9897", "QCPFMSG QSYS ", msgdta, msg_dta_len,
"*ESCAPE ", "*PGMBDY ", pgm_stk_cnt, " ", &errcode);
}/* バッチ・ジョブ */
のようにして最初には *SYSOPR で *DIAG でメッセージして
次に呼出し元にも *ESCAPE で送信する、という二つの送信が必要となる。
これらを統合化して記述すると
if(type[0] == '0'){/* バッチ・ジョブ */
/* *SYSOPR にメッセージを送る */
QMHSNDPM(msgid, "QCPFMSG QSYS ", msgdta, msg_dta_len, "*DIAG ",
"*SYSOPR ", pgm_stk_cnt, " ", &errcode);
}/* バッチ・ジョブ */
/* この *PGMBDY にメッセージを送る */
QMHSNDPM("CPF9897", "QCPFMSG QSYS ", msgdta, msg_dta_len,
"*ESCAPE ", "*PGMBDY ", pgm_stk_cnt, " ", &errcode);
という記述になる。
現在のジョブがバッチ・ジョブなのか、それとも対話式のジョブであるかを
調べるには RTVJOBA コマンドによる調査が必要である。
RTVJOBA によって受け取るジョブのタイプは 0 がバッチ・ジョブであることを示し
1 が対話式ジョブであることを示している。
以上の説明をひとつのサンプル・ソースにまとめたサンプル・ソースを
以下に示す。
【サンプル・ソース : TESTCHK3】
0001.00 #include
0002.00 #include
0003.00 #include
0004.00 #include
0005.00 #include
0006.00 #include
0007.00 #include
0008.00 #include
0009.00 #include
0010.00 #include "asnet.src/h(common)" /* POST_LEN, REP_LEN, MAX_USL,ID_LEN */
0011.00 #include /* triml */
0012.00
0013.00 #define TRUE 0
0014.00 #define FALSE -1
0015.00 volatile _INTRPT_Hndlr_Parms_T ca;
0016.00 void PRDCHK(char AutoWeb[1], char Tonakai[1], char eStudio[1],
0017.00 char DTAARA[20]);
0018.00 #pragma map(PRDCHK, "ASNET.COM/PRDCHKCL")
0019.00 #pragma linkage(PRDCHK, OS)
0020.00 void MonitorMSG(_INTRPT_Hndlr_Parms_T ca, char* ref);
0021.00 void RtvJobA(char[], char[], char[], char[], char[], char[],
0022.00 char[], char[], char[], char[], char[], char[], char[],
0023.00 char[], char[]);
0024.00 #pragma map(RtvJobA, "ASNET.COM/RTVJOBA")
0025.00 #pragma linkage(RtvJobA, OS)
0026.00 typedef struct {
0027.00 int BYTESPRO;
0028.00 int BYTESAVL;
0029.00 char MSGID[7];
0030.00 char RESRVD;
0031.00 char EXCPDATA[100];
0032.00 } ERRSTRUCTURE; /* Define the error return structure */
0033.00 ERRSTRUCTURE errcode;/* Error Code Structure for RCVMSG */
0034.00
0035.00 char ref[132];
0036.00 char job[10], user[10], jobnbr[6], outq[10], outqlib[10], date[6];
0037.00 char type[1], prtdev[10], langid[3], cntryid[2], ccsid[5];
0038.00 char dftccsid[5], cymddate[7], sbmmsgq[10], sbmmsgqlib[10];
0039.00 char jobid[ID_LEN], ascnbr[6];
0040.00 decimal(5, 0) dftCCSID;
0041.00 void main(void){
0042.00 #pragma exception_handler(MONMSG, ca, 0, _C2_MH_ESCAPE | _C2_ALL,�
0043.00 _CTLA_HANDLE)
0044.00
0045.00 printf("** TESTCHK: PRDCHK のテスト **�n");
0046.00 getchar();
0047.00
0048.00 /*( ライセンスの検査 )*/
0049.00 strcpy(ref, "LICENSE");
0050.00 PRDCHK("1", "1", " ", "CLIENT TEST.COM ");
0051.00 printf("NORMAL EOJ.�n");
0052.00 getchar();
0053.00 return;
0054.00
0055.00 MONMSG:
0056.00 #pragma disable_handler
0057.00 MonitorMSG(ca, ref);
0058.00 exit(0);
0059.00 }
0060.00 /***************************************************/
0061.00 void MonitorMSG(_INTRPT_Hndlr_Parms_T ca, char* ref)
0062.00 /***************************************************/
0063.00 {
0064.00 char msgid[7], msgkey[4], msg[3000], msgdta[256], reply_msgq[20];
0065.00 int msg_dta_len, wait_time = 0, len, pgm_stk_cnt = 1;
0066.00 typedef struct {
0067.00 Qmh_Rcvpm_RCVM0200_t rcvm0200_t;
0068.00 char Msg[3000];
0069.00 } RCVM0200;
0070.00 RCVM0200 rcvm0200;
0071.00
0072.00 errcode.BYTESPRO = 160;
0073.00 errcode.BYTESAVL = 0;
0074.00 memcpy(msgkey, (void*)&ca.Msg_Ref_Key, 4);
0075.00 msg_dta_len = sizeof(RCVM0200);
0076.00 QMHRCVPM(&rcvm0200, msg_dta_len, "RCVM0200", "* ",0,
0077.00 "*ANY ", msgkey, wait_time,"*SAME ", &errcode);
SOSI if(strncmp(rcvm0200.rcvm0200_t.Message_Type, "15", 2) == 0){/* エラ
0079.00 msg_dta_len = rcvm0200.rcvm0200_t.Length_Message_Available;
0080.00 memcpy(msgdta, rcvm0200.Msg, msg_dta_len);
0081.00 msgdta[msg_dta_len] = 0x00;
0082.00 /*( 現在のこのジョブのタイプを調べる )*/
0083.00 memcpy(job, "* ", 10);
0084.00 RtvJobA(job, user, jobnbr, outq, outqlib, date,
0085.00 type, prtdev, langid, cntryid, ccsid, dftccsid, cymddate,
0086.00 sbmmsgq, sbmmsgqlib);
0087.00 if(type[0] == '0'){/* バッチ・ジョブ */
0088.00 /* *SYSOPR にメッセージを送る */
0089.00 QMHSNDPM(msgid, "QCPFMSG QSYS ", msgdta, msg_dta_len, "*DIA
0090.00 "*SYSOPR ", pgm_stk_cnt, " ", &errcode);
0091.00 }/* バッチ・ジョブ */
0092.00 /* この *PGMBDY にメッセージを送る */
0093.00 QMHSNDPM("CPF9897", "QCPFMSG QSYS ", msgdta, msg_dta_len,
0094.00 "*ESCAPE ", "*PGMBDY ", pgm_stk_cnt, " ", &errcode);
0095.00 }/* エラー・メッセージ */
0096.00 exit(0);
0097.00 }

【解説】
前述の説明の大半は関数: MonitorMSG に記述されている。
RTVJOBA のソースは紹介されていないが簡単なCLPなので容易に想像はつくはずだ。
このサンプルでは
0050.00 PRDCHK("1", "1", " ", "CLIENT TEST.COM ");
が *ESCAPE メッセージ CPF9897 を返すことを想定している。
MonitorMSG関数は PRDCHK 関数が戻したエラー・メッセージを取得して
この呼出し元のブログラムにまた戻す、という処理を行う。
MonitorMSG関数はこれまで様々な方法を試してきたが
この関数が最も最新で安定して動作する方法として実用的な考慮の元に
作成されたものであるといえる。
_INTRPT_Hndlr_Parms_T についても IBMマニュアルには詳しい解説は見られないが
今回の解説でご理解は得られたものと思う。
APIエラーのエラー・コードの構造体は
typedef struct {
int BYTESPRO;
int BYTESAVL;
char MSGID[7];
char RESRVD;
char EXCPDATA[100];
} ERRSTRUCTURE; /* Define the error return structure */
ERRSTRUCTURE errcode;/* Error Code Structure for RCVMSG */
であり、メッセージ・データは char EXCPDATA[100]; という 100バイトの文字であり
_INTRPT_Hndlr_Parms_T の Ex_Data[48]; に比べればマシであるが
それでも足りない。
それではどれくらいの長さが必要かというと MSGF を WRKMSGF で調べればすぐに
わかるようにメッセージ・データに必要な最大長は 132 である。
IBM が必要な長さを 48バイトとしたり 100バイトとしたのかは、理由は不明であるが
132バイトを確保しておいてくれれば、私達も楽にはなっていただろう。
