メニューボタン
IBMi海外記事2009.02.16

V6R1でのRPGの機能強化

バーバラ・モリス 著

V6R1ではRPGの機能が大幅に強化されました。データ構造や配列に対する16MBというサイズ制限を除いては、変数のサイズ制限がすべてなくなりました。その他のLIKE定義やLIKEDS定義だけに使用できる変数を定義することができます。H仕様のMAINキーワードを使用してどのサブ・プロシージャがプログラムのエントリ・プロシージャであるかを指定した場合、サブ・プロシージャだけを含むプログラムを作成することができます。

H仕様のキーワードTHREAD(*CONCURRENT)を使用することで、RPGモジュールを複数のスレッドで同時に実行することができるようになりました(マルチ・スレッドのサポートについては本稿で後述します)。ファイル関連の機能強化もいくつかあり、サブ・プロシージャ内でファイルを定義できる機能、MYFILE.MYFMTなどの修飾レコード・フォーマットの使用、結果データ構造とEXFMTを組み合わせた使用、パラメータとしてのパス・ファイル(ファイル・パラメータについては後述します)などが含まれます。また、ファイルや外部記述データ構造に対してコンパイル時にオーバーライドを使用する必要がなくなりました。

種々の制限の緩和

16,773,104バイトまでのサイズのデータ構造を定義することができます。配列は、その全サイズが16,773,104バイトを越えなければ16,773,104個の要素まで定義できます。たとえば、1バイトの配列であれば16,773,104次元までの配列を使用することができます。

変数の体系付けに関するコンパイラによる制限は、これ以外にはありません。サイズが200万バイトで10万バイトの次元を持つ配列からなるデータ構造の定義を図2に示します。

巨大な可変長フィールド

65,535バイトよりも長い可変長のフィールドを使用することができるかと疑問に思っていらっしゃるかもしれません。フィールドの長さはそのフィールドの格納域の最初の部分に符号なし整数のプレフィックスとして保存されています。V6R1より前のリリースでは、この値は常に2バイトの長さでしたので、可変長フィールドの最大長は2バイトの符号なし整数が表すことのできる最大値である65,535バイトとなっていました。

V6R1からは、長さを表すプレフィックスは2バイトまたは4バイトのいずれかとなりました。デフォルトでは変数の長さが1バイト~65,535バイトの間であると定義された場合は2バイトのプレフィックスが使用され、それ以外の場合は4バイトのプレフィックスが使用されます。VARYING(2)またはVARYING(4)というキーワードで使用したいサイズを明示的に指定することができます。ただし、65,535バイト以上の長さで定義されたフィールドに対してはVARYING(2)を使用することはできません(図3)。

可変長フィールドのデータ部分にポインタをセットすると便利な場合があります。RPGのプログラマは通常%ADDR(fld) + 2を使用して2バイトのプレフィックスを読み飛ばしています。プレフィックスのサイズが2バイトでも4バイトでも良くなったので、%ADDRという組み込み関数で2番目のパラメータ*DATAが使用できるようになりました(図4)。%ADDR(varyingField : *DATA)を使用すると可変長フィールドのデータ部分のアドレスが返されますので、長さのプレフィックス部分のサイズがいくつであるかを覚えておく必要はありません。

巨大なフィールド長を指定する

RPGのD仕様のレイアウトを覚えているのであれば、Lengthエントリの長さが7バイトであることをご存知でしょう。つまり、Lengthエントリを使用して指定できる最大の長さは9,999,999バイトということです。これよりも長いサイズを指定する場合はLENキーワードを使用します(LENキーワードを使用してこれよりも短いサイズを指定することもできます)。

LENキーワードはデータ構造と文字、UCS-2、グラフィック文字列変数に対して有効です。データ構造に対して長さを明示的に指定したい場合は、Lengthエントリを使用する代わりにLENキーワードを使用するだけです(図1のD)。文字列変数に対してはTypeエントリ中でタイプA、C、またはGを使用し、LENキーワードを使用して長さを指定します(E)。

可変長フィールドをさまざまなサイズの長さプレフィックスで使用することができます。長さプレフィックスのサイズが重要となる唯一の状況はパラメータを参照渡しで渡すときです。この場合、長さプレフィックスのサイズは渡されたプロトタイプ・パラメータと同じでなければなりません。

テンプレート

変数定義の後で定義するLIKE定義またはLIKEDS定義のみに使用する変数を定義する場合は、使用されないポインタをベースとした変数を使用する代わりにTEMPLATEキーワードを使用することができます(図1のA)。

TEMPLATEキーワードを使用すると、このタイプの定義にBASEDキーワードを使用することを上回る利点がいくつか得られます。一番の利点はINZキーワードが使用できることで、これによりテンプレート・データ構造のLIKEDSで定義したデータ構造に対してINZ(*LIKEDS)をより一層便利に使用することができます。こうすることでこの変数が計算やデバッグ時に使用することを意図したものでないということが他のプログラマにより明確になります。もう一つの利点はテンプレートの定義に余計な記憶域を使用しなくて済むということです。BASEDキーワードを使用すると、使用されないベースのポインタに必要な記憶域が無駄になるからです。

サイクルのないRPGプログラム

RPGのメイン・プロシージャは2つのタイプになりました。1つはcycle-mainでもう1つはliner-mainです。cycle-mainプロシージャについてはよくご存知でしょう。cycle-mainプロシージャはRPGのサイクルのすべてのフェーズを使用します。まず*INIT (initialization)から始まり、*GETIN (get input)、*TOTC (total calculations)、*TOTL (total output)、*DETC (detail calculations)、*DETL (detail output)を繰り返し、最後に*TERM (termination)で終わります。cycle-mainプロシージャは、メイン・プロシージャへの呼び出し1回につき*INIT、*DETC、*TERMだけを使用し、繰り返しの部分は*DETCだけを実行するものがほとんどです。

linear-mainプロシージャは、H仕様でMAINキーワードを使用することで識別します。linear-mainプロシージャは通常のサブ・プロシージャと良く似ていますが、プロトタイプにEXTPROCキーワードではなくEXTPGMキーワードを使用する点が異なります。

linear-mainプロシージャは、RPGのサイクルを使用しないのでこのような名前が付けられています。他のサブ・プロシージャと同様で、計算を1度だけ実行します。linear-mainプロシージャのあるモジュールはRPGのサイクルの*INIT部分だけを使用します。linear-mainモジュールとNOMAINモジュールはサイクルの*TERM部分さえも使用しません。

linear-mainプロシージャをエキスポートすることはできず、プログラム呼び出しにより呼び出すことしかできません。linear-mainプロシージャが自分自身を再帰的に呼び出す場合は、EXTPGMキーワードで識別されたプログラムに対して呼び出しが実行されます。つまり呼び出しはメイン・プロシージャに対して直接実行されるわけではありません。プログラムが*NEWアクティベーション・グループを使用している場合、メイン・プロシージャやそのサブ・プロシージャからの呼び出しは本来の意味での再帰呼び出しとはなりません。プログラムに対する呼び出しが新しいアクティベーション・グループを開始するからです。

linear-mainプロシージャ用のプロトタイプはEXTPGMキーワードを使用するので、DFTACTGRP(*YES)を使用していてもlinear-mainプロシージャを定義することができます。linear-mainプロシージャのあるプログラムを図5に示します。LRはlinear-mainプロシージャのあるモジュールとは関係がないので、*INLRの設定がない点に注意してください。メイン・プロシージャはDSPLY命令コードを実行して戻ってくるだけです。

ファイルの機能強化

V6R1でのファイルの機能強化は、RPG IVがファイル処理に関して初めて大きな機能強化が行ったV5R2以来の傾向を引き継いでいます。V5R2では、RPGのプログラマはI/Oの結果データ構造を外部記述レコード形式に対して使用することができました。V5R3ではこの機能が拡張され、I/Oの結果データ構造を外部記述ファイルに対して使用することができるようになりました。V5R2とV5R3で追加された機能は、サブ・プロシージャでファイルを使用するときにとても重要な機能になりました。

コンパイル時のオーバーライドを避ける

EXTNAMEというD仕様キーワードが更新されて、引用符号中にファイル名を指定してライブラリで修飾することができるようになりました(図1のA)。新しいF仕様キーワードであるEXTDESCを使用すると、コンパイラに対して実行時に外部記述ファイル用にどのファイルを使用するのかを指定することができます。またEXTFILEキーワードに対して同じファイルを指定したい場合は、EXTFILE(*EXTDESC)という新しい特別なパラメータを使用することができます。

EXTDESCを使用するとRPGプログラム中で実際のファイル名とは異なる名前を簡単に使用することができます。図1のCの例では、実際のファイルはPARTSと呼ばれていますが、RPG名はinFileです。実際のファイル名とそのファイルのレコード形式が同じ名前の場合、RPGプログラム内でファイルに対して異なる名前を付けることで、レコード形式の名前を変更して名前の重複を避ける必要がありません。図1のCの例では、PARTSという名前のファイルを使用しており、そのレコード形式はPARTSという名前になっていますが、ファイルはinFileと呼ばれていますので名前の競合はありません。ファイルが修飾されている場合は名前の競合はありませんが、レコード形式の内部名は奇妙なことにPARTS.PARTSとなります。

Fparts   if  e   k disk  qualified

修飾されたレコード形式

QUALIFIEDキーワードをF仕様で外部記述ファイルに対して指定した場合、ファイルの形式名はRPGプログラム中で修飾されます。図1のCでは、DEVLIB/PARTFILEというファイルはPARTSというレコード形式なので、RPGプログラム中ではこのレコード形式はinFile.partsという名前で参照されます。

図1に示した部品表の例はファイル構造とデータ構造の両方に対して修飾された名前を使用しています。これによりCHAIN命令とREADE命令がinFileのPARTSレコードをデータ構造inDs中で使用しているのが容易におわかりいただけると思います。CHAIN命令の直後のコードでは、PART_IDフィールド、REQ_IDフィールド、REQ_QTYフィールドがinDsデータ構造から来ていることがよくわかると思います(図1のF)。修飾名と結果データ構造を使用すると、そのファイルとフィールドを使用しているコード中のファイル、レコード形式、データ構造、サブ・フィールドの連携がわかります。また修飾レコード形式を使用すると、そのレコード形式に対するI/O命令と、その後に出てくる%EOF、%FOUNDなどといったファイル名を必要とする組み込み関数との間の連携が明確になります。

chain part_id inFile.parts inDs;
if %found(inFile);
  ...
  totals.quantity(i) += inDs.req_qty;

これとCHAINとPARTSレコードとの連携を比較し、次に%FOUND(inFile)の使用、PART_IDフィールド、REQ_IDフィールド、REQ_QTYフィールドの処理と比較してください。このコードを読む人は、コードを理解するのにファイルとファイルのフィールドに関する高度な知識を必要とするでしょう。

chain part_id parts;
if %found(inFile);
  ...
  totals.quantity(i) += req_qty;

サブ・プロシージャで定義されたファイル

V3R2でサブ・プロシージャが導入されて以来、RPGのプログラマはサブ・プロシージャ内でファイルを定義する機能を望んでいました。サブ・プロシージャ内でローカルなファイルを定義することで、モジュール内のどのプロシージャがファイルを使用できるのかを制御することができます。1つのサブ・プロシージャだけにファイルを使用させたい場合は、そのファイルをそのプロシージャ内で定義し、他のサブ・プロシージャが間違ってそのファイルを使用しないすることのないようにしてください。

ローカル変数は常にサブ・プロシージャ内で利用可能でしたが、V6R1になるまではRPGプログラマは「ローカル」ファイルをグローバルで定義することを余儀なくされていました。

自動と静的の2つの種類のローカル・ファイル

サブ・プロシージャ内でファイルを定義することのもう1つの利点は、そのファイルに関連する内部記憶域のタイプを選択できるということです。ローカル変数は自動または静的な記憶域を使用するように定義できるという点を覚えておいてください。デフォルトは自動記憶域です。ローカル・ファイルに対しても同様の選択ができます。デフォルトは内部記憶に対しては自動記憶域ですが、静的記憶域を使用するように選択することもできます。

自動記憶域中のファイルはサブ・プロシージャに対する1回の呼び出しに対してのみ使用可能です。サブ・プロシージャ中で自動ファイルを使用するのは、実行が終了して戻ってきたときに常にLRをセットするようなプログラム中でファイルを使用する場合と似ています。静的記憶域中のファイルはサブ・プロシージャの呼び出し中は常にその状態を保持しています。静的ファイルを使用するのはLRをセットすることのないプログラム中でファイルを使用する場合と似ています。

自動記憶域中のファイルが持つ便利な機能として、サブ・プロシージャが再帰的に呼び出されたとき、サブ・プロシージャを呼び出すごとにファイルがオープンされる点があります。サブ・プロシージャがファイルをシーケンシャルに読んで現在レコード12を処理しているときに自分自身を再帰的に呼び出すと、再帰呼び出しによりファイルが再びオープンされ、レコード1から処理を開始します。サブ・プロシージャがレコード84でファイルの終端に達して戻ってきたとき、呼び出し側ではまだレコード12を処理している状態で、次のレコード13を読み出します。自動記憶域中でファイルを使用して再帰呼び出しをしている部品表プロシージャのコードを図1に示します。

I仕様やO仕様は?

本稿で紹介している例を読んでいるうちに、CHAIN、READE、WRITE命令はいずれも結果データ構造を使用していることにお気づきになったかもしれません。サブ・プロシージャ中で定義されたファイルのI仕様やO仕様をコンパイラが生成しないので、こうしなければならないのです。つまりすべてのI/Oに対してデータ構造を使用しなければなりません。

I仕様やO仕様のすばらしい機能の1つが、プログラム・フィールドを複数のファイルが使用する方法です。たとえば、CUSTNAMEおよびCUSTIDを使用する1つのファイルから読み出して、同じフィールドを使用する他のファイルに出力し、入力ファイル中のCUSTNAMEフィールドおよびCUSTIDフィールドから出力ファイル中の同じフィールドにデータを移動させることができます。しかもフィールド名をRPGのソース中で指定する必要はありません。両方のファイル中にフィールドを追加した場合は、プログラムを再コンパイルして入力ファイルの新しいフィールドから読み出して出力ファイルに書き出すようにすればよいのです。ソースコードを変更する必要はありません。結果データ構造を処理するときはプログラム・フィールドを複数のファイルで使用することはできません。I/Oに使用する結果データ構造はI/O命令のレコード形式かファイルに合致するように明示的に定義しなければならないからです。

V5R4で追加されたEvaluate Corresponding命令コード(EVAL-CORR)は異なるデータ構造中の同じ名前のフィールド間でデータを移動させる問題を解決してくれます。EVAL-CORRを使用して同じ名前のフィールド間でデータを移動させる場合は、コンパイラが一方のデータ構造のサブ・フィールドを他方のデータ構造に自動的にデータを移動してくれます。OPTION(*XREF)とコーディングすると、コンパイラは各EVAL-CORR命令に対して詳細なリストを生成し、両方のデータ構造中で各サブ・フィールドをどのように処理するかを指定します。

ファイルはいつオープンされ、いつクローズされるのか

STATICキーワードがコーディングされていてUSROPNキーワードが使用されていない場合は、そのサブ・プロシージャに対する最初の呼び出し時にファイルは暗示的にオープンされます。静的ファイルは暗示的にクローズされることは決してありません。サブ・プロシージャが処理を終了して戻るときに静的ファイルがオープンされていると、以後の呼び出しでCLOSE命令が実行されるか、あるいはアクティベーション・グループが終了するまでその静的ファイルはオープンされたままになります。サブ・プロシージャに対して特別なパラメータ規約を設定して、静的ファイルをクローズするようにサブ・プロシージャを呼び出すことができるようにしておく必要があるかもしれません。サブ・プロシージャが通常パラメータを有している場合、パラメータを指定せずにそのサブ・プロシージャを呼び出してファイルをクローズさせるか、またはサブ・プロシージャが通常パラメータを有していない場合、オプションのパラメータを追加してサブ・プロシージャはファイルをオープンするだけと指定することができます。

STATICキーワードもUSROPNキーワードも使用していない場合、サブ・プロシージャが呼び出されるたびにファイルは暗示的にオープンされます。プロシージャが元に戻るとき、オープンされている自動ファイルは常にクローズされます。

EXFMT用の結果データ構造

I/O命令用に結果データ構造をすでに使用している場合は、EXFMT用の結果データ構造がサポートされていないことをご存知でしょう。V6R1からEXFMT命令でも結果データ構造を使用することができるようになりました。データ構造をLIKEREC(format:*ALL)またはEXTNAME(file:format:*ALL)で定義します。EXFMT命令の前に、出力可能なフィールドをデータ構造中にセットしておくと、EXFMT命令の後で入力可能なフィールドはすべてこの命令によって値が変更されています。図6に示す例ではANSというフィールドは入出力が両方とも可能なフィールドです。EXFMT命令の実行前はある値がセットされていて、ユーザーが正しい日付値に変更することが想定されています。

ローカルかグローバルか、自動か静的か

オープンされている1つのファイルを複数のプロシージャで使用する必要がある場合、そのファイルをグローバル・ファイルとして定義するか、またはプロシージャン間でパラメータとしてそのファイルを渡す必要があります。オープンされたファイルに関する情報を1つのサブ・プロシージャ内だけで必要とする場合は、そのファイルをサブ・プロシージャ内で定義するのがよいでしょう。

プロシージャが処理を終了して戻ってきた後もファイルをオープンのままにしておきたい場合は、STATICキーワードを使用してコンパイラが静的記憶域内のファイルに関する内部情報を保持するようにします。プロシージャを呼び出すたびに新たにファイルをオープンし、確実にクローズしたい場合は、デフォルトの記憶域タイプである自動記憶域を使用してください。ただし、ファイルをオープンする操作はファイルに対する命令のうち最も負荷の高い操作であることを意識してください。プロシージャが頻繁に呼び出されるために、そのプロシージャの性能が目に見えて落ちる場合は、静的ファイルを使用することで性能が持ち直す場合があるので、ファイルを静的ファイルとして定義するよう特別な開発作業をしなければならないでしょう。

続編あり

ファイル・パラメータや新しいマルチ・スレッドのサポートの詳細についてはいずれ説明します。これらの新しい機能を使用して楽しんでいただけることを期待しています。我々の研究所では、こうした新しい機能なしで今までどうしてきたのだろうかとすでに不思議に思っています。

あわせて読みたい記事

PAGE TOP