おい、最近あちこちの現場で「2025年の崖」とか「レガシーシステムのモダナイゼーション」なんて言葉が飛び交っているが、実際どうだ?
「COBOLで書かれた基幹バッチの一部を、オブジェクト指向っぽい書き方ができるPL/Iにリプレイスするぞ」とか、あるいはその逆で「既存のPL/I資産から、資産豊富なCOBOLの会計サブシステムを呼び出したい」なんて無茶振りに直面して頭を抱えている若手エンジニアも多いんじゃないかと思う。
今日はな、そんなメインフレームの現場で避けて通れない、「PL/IからCOBOLやアセンブラ(ASM)を呼び出す際の他言語連携」について、ガッツリ語ってやろう。
特に、データ型のマッピングミスやリンケージエディタの罠で夜間バッチが突然S0C4やU4038で墜落した時の冷や汗の味……これを味わいたくないなら、今日の話を骨の髄まで染み込ませておきな。
—
1. なぜ他言語連携でハマるのか?(PL/I特有のデータ制御と仕様)
まず大前提として、PL/Iという言語の懐の深さを思い出してほしい。PL/Iには、C言語のような「厳密な予約語の縛り(キーワードのコンフリクト)」がほとんどない。変数名に `IF` や `READ` といった名前すら(文脈次第で)使えてしまうほどの柔軟性を持っている。
しかし、その「何でも受け入れる柔軟性」が、他言語(COBOLやアセンブラ)とタッグを組むときには諸刃の剣になる。
COBOLやアセンブラは、メモリ上のデータの配置(アライメント)や、引数の渡し方(値渡しなのか、参照渡しなのか、レジスタ経由なのか)に対して非常に潔癖だ。
PL/I側で何気なく定義したデータ構造が、COBOL側から見ると「おいおい、パディング(隙間)のせいでオフセットがズレてるぞ!」という事態を容易に引き起こす。
ENTRY属性と OPTIONS 記述の基本
他言語を呼び出す際、PL/I側では呼び出す外部ルーチンの名前を `ENTRY` 属性で宣言し、`OPTIONS` オプションで相手の言語規約を指定する。
- `OPTIONS(COBOL)`: COBOLの呼び出し規約(OS/VS COBOLやEnterprise COBOL)に合わせる。
- `OPTIONS(ASM)`: アセンブラ(OSリンクage conventions:レジスタ1にパラメータリストのポインタを並べる形式)に合わせる。
これらを正しく使わないと、制御が戻ってきたときにレジスタが破壊されてメインフレーム全体が巻き添えを食らう。心してかかれ。
—
2. 実践!PL/IからCOBOLサブルーチンを呼び出すコード例
百聞は一見に如かずだ。実際に、PL/Iのメインバッチから、金額計算を行うCOBOLの外部サブルーチン(仮に `CBLCALC` とする)を呼び出すコード例を見てみよう。
ここで重要になるのは、「データの型と長さ、そしてポインタの渡し方(BYVALUE と BYADDR)」の完全一致だ。PL/Iのデフォルトは `BYADDR`(アドレス渡し)だが、これを意識していないと地獄を見る。
1
/ ================================================================= /
/ プログラムID: PLIMAIN /
/ 概要: COBOLサブルーチン(CBLCALC)を呼び出すメインPL/Iプログラム /
/ ================================================================= /
PLIMAIN: PROC OPTIONS(MAIN);
/ ————————————————————- /
/ 1. 外部COBOLサブルーチンのENTRY宣言とOPTIONS(COBOL)の指定 /
/ ————————————————————- /
DCL CBLCALC ENTRY(
CHAR(8), / 顧客ID (COBOL側: PIC X(8)) /
FIXED BIN(31), / 単価 (COBOL側: PIC S9(9) COMP) /
FIXED BIN(15), / 数量 (COBOL側: PIC S9(4) COMP) /
DECIMAL FIXED(11,2) / 金額 (COBOL側: PIC S9(9)V99 PACKED) /
) OPTIONS(COBOL);
/ ————————————————————- /
/ 2. 変数定義(COBOL側のデータ定義と1バイトたりともズラさない) /
/ ————————————————————- /
DCL W_CUST_ID CHAR(8) INIT(‘A1234567’);
DCL W_UNIT_PRICE FIXED BIN(31) INIT(1500);
DCL W_QUANTITY FIXED BIN(15) INIT(10);
DCL W_TOTAL_AMT DECIMAL FIXED(11,2) INIT(0);
/ 戻り値受取用・メッセージ用変数 /
DCL W_RET_CODE FIXED BIN(15) INIT(0);
DISPLAY ‘ PLIMAIN: COBOL呼出処理を開始します ‘;
/ ————————————————————- /
/ 3. サブルーチンの呼び出し /
/ PL/Iのデフォルトはアドレス渡し(BYADDR)なので、 /
/ COBOL側の受け側定義(LINKAGE SECTION)と完全一致させること /
/ ————————————————————- /
CALL CBLCALC(W_CUST_ID, W_UNIT_PRICE, W_QUANTITY, W_TOTAL_AMT);
/ ————————————————————- /
/ 4. 結果の確認と出力 /
/ ————————————————————- /
DISPLAY ‘CUSTOMER ID: ‘ || W_CUST_ID;
DISPLAY ‘TOTAL AMOUNT: ‘ || W_TOTAL_AMT;
DISPLAY ‘ PLIMAIN: 正常終了します ‘;
RETURN;
END PLIMAIN;
🧠 コードの解説とベテランからの注意点
1. データ型のマッピング:
- PL/Iの `FIXED BIN(31)` は、COBOLの `PIC S9(9) COMP`(あるいは `COMP-4`)と完全に一致する。これをうっかり `FIXED DEC` にしたりすると、バイナリデータの解釈が狂ってゴミ数値が返ってくる。
- 金額計算などでよく使われる `DECIMAL FIXED(11,2)` は、COBOLのパック十進数(`COMP-3`)に相当する。PL/I側でもゾーン十進やパック十進の桁数・小数点位置を厳密に合わせる必要がある。
2. リンケージエディタのバインド:
- このPLIプログラムと、呼び出し先のCOBOLモジュールは、当然ながらリンケージエディタ(LKED)の段階で正しく結合(INCLUDE)されていなければならない。ダイナミックリンク(`FETCH` / `RELEASE`)を使う場合でも、記述ミスは即座にABEND(多くはOC4やCBダイナミックローダーのエラー)に直結する。
—
3. アセンブラ(ASM)連携と、知っておくべき「落とし穴」
次に、OSの低水準サービスや特殊な制御ブロック(JES2のインターフェースや、独自の暗号化ルーチンなど)を叩くために、アセンブラ(ASM)を呼び出すケースだ。
ここでの `OPTIONS(ASM)` は、さらに緊張感が走る。
アセンブラ側は、汎用レジスタ(R1)に「パラメータ・アドレスのリスト(アドレスの配列の最後尾にオンスイッチが立っているもの)」が入っている前提で動く。
1
/ アセンブラサブルーチン呼び出しの例 /
DCL ASMSRV ENTRY(
POINTER, / パラメータ1: 制御ブロックへのポインタ /
CHAR(256) / パラメータ2: ワークエリア /
) OPTIONS(ASM, INTER); / INTER(インタラプト処理や環境依存の指定) /
現場でよくあるトラブル:データアライメント(境界調整)
PL/Iで構造体を定義するとき、コンパイラはデフォルトで効率的なメモリアクセスのために「境界調整(Aliged)」を行う。
例えば、`FIXED BIN(31)` の前に奇数バイトの `CHAR(3)` なんたりを置くと、コンパイラが勝手に1バイトの「パディング(隙間)」を挿入する。
しかし、呼び出し先のCOBOLやアセンブラ側が「そんなパディングは想定していない、連続した3バイトの次にそのまま4バイトのバイナリがあるはずだ(UNALIGNED)」と解釈していたらどうなるか?
データが盛大ズレを起こし、計算結果がデタラメになるか、致命的なメモリアクセス例外(S0C4)で夜間バッチが即死する。
そのため、他言語とデータをやり取りする大きな構造体を定義する場合は、以下のように必ず `UNALIGNED` 属性を明示するのが、百戦錬磨のメインフレームエンジニアの鉄則だ。
1
/ 他言語連携用の構造体は必ずUNALIGNEDを指定してパディングを防ぐ /
DCL 1 COM_AREA UNALIGNED,
5 HEADER_ID CHAR(4),
5 RECORD_COUNT FIXED BIN(31),
5 FLAG_BYTE BIT(8);
—
4. オンユニット(ON-UNIT)による例外処理の制御
他言語を呼び出す際にもう一つ忘れてはならないのが、エラー発生時の制御フローだ。
PL/Iには強力な例外処理機構である `ON` ユニットがある。もし外部のCOBOLやアセンブラから予期せぬデータが返ってきたり、ゼロ除算やオーバーフローが発生した場合、PL/I側で事前に `ON` ユニットを張っておくことで、突然のジョブ異常終了を防ぎ、安全にログを出力して終了させることができる。
1
/ ゼロ除算や数値変換エラーの捕捉 /
ON FIXEDOVERFLOW
BEGIN;
DISPLAY ‘ 警告: 数値のオーバーフローを検出し処理を継続します ‘;
GOTO ERROR_ROUTINE;
END;
他言語連携の境界付近では、こうした例外処理を適切にサンドイッチしておくことが、ミッションクリティカルなシステムを堅牢に保つ秘訣だ。
—
5. まとめ:レガシー移行期を生き抜くために
PL/Iと他言語(COBOL/ASM)の連携は、一見すると古臭い技術に見えるかもしれない。しかし、何十年も日本の社会インフラを支えてきた基幹システムの心臓部は、今なおこうした言語間の緻密な連携のうえに成り立っている。
- データ型と長さは1バイトたりとも妥協しない。
- 構造体には `UNALIGNED` を使ってアライメントのズレを防ぐ。
- `OPTIONS(COBOL)` と `OPTIONS(ASM)` の違いを正しく理解し、呼び出し規約を守る。
この基本を忠実に守りさえすれば、言語が変わろうとも、モダナイゼーションの波が押し寄せようとも、怖るるに足りない。
さあ、コーヒーでも飲んで、次のコンパイルリストの警告(Warning)でもチェックしに行こうか。シニアエンジニアの腕の見せ所は、いつだってこういう細部へのこだわりにあるんだからな。
