おい、若いの。少し手を止めて、この茶でも飲みながら聞いてくれ。
お前が今まさに直面している、あるいは将来必ず踏み抜く地雷原……それがCICSオンライン環境における `EXEC CICS LINK` や `XCTL` での `COMMAREA`(共通領域)の受け渡し、そして「送信側と受信側でのデータ構造の不一致による突然のASRA(S0C4)アベンド」だ。
PL/IはCOBOLと違って、「予約語」という概念が極めて薄い言語だ。`IF` や `READ` でさえも文脈によっては変数名として使えてしまう(もちろんそんな狂ったコーディングは御法度だがな)。この「何でも定義できてしまう自由度の高さ」が、CICSの領域境界を跨ぐ通信においては、時として凶器に変わる。
今日は、メインフレームの現場で30年飯を食ってきた私が、構造体のメモリレイアウトの罠と、それを綺麗にいなすための実践的なコーディング標準を叩き込んでやる。心して聞け。
—
1. なぜCOMMAREAの不一致はアベンド(S0C4)を招くのか
CICSの `COMMAREA` は、OSが管理する領域から切り出された、ただの「生きたバイト列の塊」に過ぎない。送信側のプログラムがどう解釈しようが、受信側のプログラムがどう定義しようが、CICS自身は「渡されたアドレスから指定されたバイト数をコピーする」ことしかしない。
ここでPL/Iの出番だ。PL/Iの構造体(`DECLARE`文)は、コンパイラがよしなにメモリ上のアライメント(境界調整)やパディング(隙間の埋め合わせ)を行う。
特に気を付けなければならないのが以下の点だ。
- 半端な長さの文字項目 (`CHAR`) や数値項目 (`FIXED BINARY`) の境界調整
- 構造体メンバーの偶数・4バイト・8バイト境界への自動パディング
- 送信側と受信側で `PICTURE` 句や `BINARY` の桁数が微妙にズレていることによるオフセットの崩壊
これらが原因で、受信側プログラムがレイアウトのズレたデータを無理やり参照した瞬間、存在しないメモリアドレスを叩いて ASRA(システム異常終了コード:S0C4) がお迎えに来るというわけだ。
—
2. 予約語を持たないPL/Iの強みと、あえて「厳格なコーディング標準」を敷く理由
冒頭で言った通り、PL/Iには厳密な意味での予約語がほとんどない。例えば、以下のようなコードも文法エラーにはならない。
1
DECLARE IF FIXED BINARY;
IF = 10;
コンパイラは文脈からこれを解釈できるが、人間の目はそうはいかない。特にCOMMAREAのマップ定義において、送信側と受信側で場当たり的な変数名をつけていたら、大規模改修の際に地獄を見る。
だからこそ、「外部インターフェースとなるCOMMAREAの構造体定義は、インクルードメンバー(COPYブック的な位置づけ、PL/Iでは `%INCLUDE`)として完全に対称に保ち、一字一句たりともズラさない」 という鉄の掟が必要なのだ。
—
3. 実践コード:安全なCOMMAREA定義と送受信プログラム
百聞は一見に如かずだ。実際にオンラインの受注データを処理するトランザクションを想定し、送信側(親)と受信側(子)のサンプルコードを見せよう。すべて大文字、そして現場の流儀に沿った丁寧なコメント付きだ。
【共通インクルード定義:ORDERMAP.inc】
まずは、送信・受信の両方でインクルードするデータレイアウトだ。すべてのアライメントのズレを防ぐため、可能な限り `FIXED BINARY(31)` や `CHAR` を用い、パディングが発生しにくい明示的な構造にする。
1
//
/ 共通COMMAREA定義: 注文処理データ構造(ORDERMAP) /
//
DECLARE 1 ORDER_COMMAREA SHARED,
3 C_RETURN_CODE CHAR(2), 処理結果コード(OK/NG)
3 C_TRAN_ID CHAR(4), 発信元トランザクションID
3 C_ORDER_DATA,
5 C_CUST_ID CHAR(8), 顧客ID
5 C_ITEM_CODE CHAR(10), 商品コード
5 C_QUANTITY FIXED BIN(31), 数量(4バイト整数)
5 C_PRICE FIXED DEC(7,2); 金額(パック十進数)
—
【送信側プログラム:SNDPROG.pl1】
親プログラム側で `EXEC CICS LINK` を使い、COMMAREAを渡す処理だ。
1
SNDPROG: PROC OPTIONS(MAIN);
DCL EIBTRNID CHAR(4) EXTERNAL;
/ 共通COMMAREAの取り込み /
%INCLUDE ORDERMAP;
/ ワーク変数の定義 /
DCL WK_LENGTH FIXED BIN(31) INIT(30); / 領域の総バイト数 /
/ 1. COMMAREAに値を詰める /
C_RETURN_CODE = ’00’;
C_TRAN_ID = EIBTRNID;
C_CUST_ID = ‘CUST1234’;
C_ITEM_CODE = ‘ITEM9999’;
C_QUANTITY = 150; / BUILTIN関数で安全に処理も可能 /
C_PRICE = 12500.50;
/ 2. CICS LINKによるプログラム呼び出し /
EXEC CICS LINK PROGRAM(‘RCVPROG’)
COMMAREA(ORDER_COMMAREA)
LENGTH(WK_LENGTH);
/ 3. 呼び出し後の結果判定 /
IF C_RETURN_CODE ^= ’00’ THEN
GOTO ERROR_RTN;
RETURN;
ERROR_RTN:
/ エラー処理ロジック /
PUT SKIP LIST(‘CICS LINK ERROR OCCURRED.’);
RETURN;
END SNDPROG;
—
【受信側プログラム:RCVPROG.pl1】
子プログラム側だ。CICSから渡されたアドレスを、先ほどと同じ `%INCLUDE ORDERMAP` で受け受ける。ここで `DFHCOMMAREA` の概念が重要になる。
1
RCVPROG: PROC OPTIONS(MAIN);
/ CICS通信領域(DFHCOMMAREA)のポインタ定義 /
DCL DFHCOMMAREA CHAR(30) BASED(P_COMMAREA);
DCL P_COMMAREA POINTER;
/ 共通COMMAREAの構造体ベース定義 /
DCL 1 RCV_ORDER_MAP BASED(P_ORDER_PTR),
%INCLUDE ORDERMAP;
DCL EIBRESP FIXED BIN(31) EXTERNAL;
/ 1. COMMAREAのアドレスを取得して構造体にオーバーレイする /
P_COMMAREA = ADDRESS(DFHCOMMAREA);
P_ORDER_PTR = P_COMMAREA;
/ 2. 受信データのバリデーション(ONユニットやビルトイン関数活用) /
ON CONVERSION BEGIN;
/ 数値変換エラー等の異常系をキャッチするONユニット /
C_RETURN_CODE = ‘E9’;
GOTO EXIT_RCV;
END;
IF C_CUST_ID = ” THEN DO;
C_RETURN_CODE = ‘E1′;
GOTO EXIT_RCV;
END;
/ 3. ビジネスロジックの実行(例:数量のチェック) /
IF C_QUANTITY > 1000 THEN
C_QUANTITY = 1000; / 上限クランプ /
C_RETURN_CODE = ’00’;
EXIT_RCV:
EXEC CICS RETURN;
END RCVPROG;
—
4. ベテランからの現場の教訓:デバッグとトラブルシューティングの極意
いいか、もしお前が本番稼働後に `ASRA` を踏んだり、あるいはデータが化ける現象に遭遇したら、以下の手順で即座に原因を特定しろ。
1. DUMPの解析(IPCSの活用)
- CICSのトランザクションダンプ(またはスナップダンプ)を採取し、異常終了時の `EIBRESP` と、問題の `COMMAREA` の先頭アドレスを突き止めろ。
2. オフセットの目視確認
- 送信側のコンパイルリスト(Cross-Reference / Attribute LIST)出力にあるオフセット位置と、受信側のそれが完全一致しているか、ヘキサ(16進数)ダンプ上でバイト単位で比較しろ。大抵の原因は、片方だけに意図しない `ALIGN` 属性がついているか、`FIXED DEC` の桁数の定義ミスだ。
3. コンパイラオプションの統一
- プロジェクト全体で `RULES(NOLAXITM)` や `AGGREGATE` などのコンパイラオプションを統一し、アライメントの挙動がプログラム間でズレないようにビルドJCLを厳格に管理しろ。
PL/Iは奥が深い。しかし、その分、機械の内部構造までをプログラマの意のままに操れる最高にエキサイティングな言語だ。
CICSの領域を跨ぐデータ構造の同期は、いわば異国間の通訳のようなもの。単語の定義を少しでも違えれば、会話は一瞬で崩壊する。
今日の話を胸に刻み、明日からのコーディングに活かしてくれ。期待しているぞ、若いの。
