RPGプロトタイプの基本事項ならびにその有用性については、既に「【第24回】RPGのプロトタイプの威力」という記事でご紹介しました。今回はその続編として、パラメータとしてデータ構造やファイルを渡すといった、更に高度なプロトタイプの活用法についてご紹介します。(編集部)
プロトタイプを使うことにより、どのようにファイルを含むもっと「興味深い」型のパラメータを渡すことが可能になり、またプロトタイプがどのようにバインドされたプロシージャと関数の呼び出しを行うかについても説明します。
2018年5月 ジョン・パリス、スーザン・ガントナー
私達は直近の特別記事で、RPGプロトタイプがどのように開発をもっと楽にできるか説明しました。プロトタイプについて引き続き取り上げると約束しましたので、この記事では一歩進んで、ファイルを含むさらに“興味深い”タイプのパラメータを、プロトタイプを使って渡す方法およびプロトタイプでバインドされたプロシージャや関数を呼び出す方法について説明します。
データ構造をパラメータとして渡す
“普通の”文字、数値または日付パラメータを渡すのはとても簡単ですが、もっと複雑なパラメータはどうでしょうか? たとえば、データ構造やデータ構造配列を渡すのはどうでしょう? データ構造パラメータを定義するには、単純にLIKEDSまたはLIKERECキーワードを使用してコード内の他の場所にあるデータ構造の定義を参照します。データ構造配列の場合、ただDIMキーワードを追加するだけです。なぜなら、LIKEDSは構造体定義を取り込みますが、元々のDIM文の要素数は取り込まないからです。
最大20個の顧客名と住所のデータ構造配列を渡したい場合、次のようにします。
Dcl-DS CustomerDataTemplate Dim(20) Qualified Template; Name Char(20); AddressLine1 Char(30); AddressLine2 Char(30); AddressCity Char(20); AddressStateProv Char(30); AddressPostalCode Char(30); End-Ds; Dcl-Pr GetCustomerData ExtPgm('GETCUSTDTA'); CustDataDS LikeDS(CustomerDataTemplate) Dim(20); CustDataCount Int(5) Const; End-Pr; Dcl-DS CustomerData LikeDS(CustomerDataTemplate) Dim(20); GetCustomerData( CustomerData : CustomerCount );
このデータ構造定義にTEMPLATEキーワードを使用することを訝しく思うかもしれません。TEMPLATEキーワードは、そのデータ構造がLIKEDS内の構造の定義を提供するためだけに使用されることを意味します。それはデータを保持できる実際のデータ構造ではありません。GetCustomerDataの呼び出しで渡したパラメータが実際の構造体名であることに注意してください。LIKEDSと共にテンプレートを使用する必要はありませんが、これは一般的な慣例です。その理由は、プロトタイプは通常、別のソースメンバー内にコーディングされ、その後そのコードを呼び出す必要のあるすべてのプログラムにコピーされるからです。テンプレートをプロトタイプと同じソースメンバー内にコーディングすることは、呼び出し元プログラムの実際のデータ構造名が異なる場合でも、LIKEDSで使用する名前が常に有効であると我々が知っていることを意味します。呼び出されるプログラムに含まれるプロシージャ・インターフェース(PI)は、LIKEDSキーワードを含めここに示されているプロシージャからのパラメータ定義を反映しなければなりません。また、そうすればすべてのサブフィールド定義を自動的に提供することで、コードが単純化されます。
ファイルをパラメータとして渡す
ファイルをパラメータとして渡すのはどうでしょう? これは頻繁には発生しない状況ですが、発生した場合これは非常に巧妙な解決策を提供します。ファイルをパラメータとして渡すとはどういうことでしょう? 文字通り、現在のレコード位置を含めファイルの制御を別のプログラム(またはプロシージャー)に渡すことができます。呼び出されたルーチンは、READ、WRITE、CHAIN、UPDATEなどファイルに対するI / O操作を実行できます。呼び出し元のプログラムに戻った後、ファイルは呼び出されたルーチンで実行された活動に基づいて再度レコード位置を設定し直される可能性があります。
パラメータとして渡されるファイルはデータベース・ファイルであるかもしれませんが、このサポートの最も良いユースケースの1つはプリンターファイルです。異なるセクションを持つレポートがあるとします。場合によってはそのレポートのすべてのセクションを作成する必要があり、他の場合にはセクションのサブセットのみが必要だとしましょう。理想的には、各セクションを独自のプログラムまたはプロシージャにモジュール化したいと思うかもしれません。しかし、複数の独立したプログラムが異なるセクションを作成しているとき、単一のスプールファイル・レポートを作成するには大変な技巧を要する可能性があります。
しかし、最初のプログラムと各セクションを作成するプログラムの間でプリンターファイルの制御を単純に渡すだけであれば、これはずっと単純になります。最初に、同じレポートの異なるセクションを書き込むために別々のプログラムを呼び出す必要のある初期プログラム中のプロトタイプを見てみましょう。制御を渡したいファイルを指定するためにLIKEFILEキーワードを使用していることに注意してください。次に、呼び出し時に単純にファイル名を渡します。
Dcl-F ProdRptf printer oflind(Overflow); Dcl-Pr PrintSection2 ExtPgm('PRTRPT2'); printFile LikeFile(ProdRptf); overFlowInd Ind; End-Pr; PrintSection2(ProdRptf : overFlow);
オーバーフロー標識の値などの詳細はファイル・パラメータと一緒には渡されないので、オーバーフロー標識を2番目のパラメータとして渡す必要がありました。
呼び出されるプログラムには、レポート・プリンターファイルの宣言が含まれますが、この場合はTEMPLATEキーワードを使用します。先ほどデータ構造に同じキーワードを使用したときと同じように、このファイル宣言は、ファイルの詳細(レコード様式、フィールドなど)を使用したいけれども、このファイルはそのプログラムではオープンされないとコンパイラに通知します。このテンプレートを使用すると、LIKERECキーワードを使用して、呼び出されたプログラム(それらのデータ構造についてはすぐに述べます)で使用する各レコード様式に対するデータ構造を作成することができます。
以下に、ファイルテンプレートの宣言と呼び出しに使用されたプロトタイプからのパラメータ定義を反映するプロシージャ・インタフェース(PI)を示します。これは呼び出されるプログラム(PRTRPT2)のコードです。
Dcl-F ProdRptf printer Template; Dcl-PI PrintSection2 ExtPgm('PRTRPT2'); PrintFile LikeFile(ProdRptf); OverFlowInd Ind; End-PI;
入力仕様書および出力仕様書はテンプレートファイル用に作成されないので、WRITE操作でデータ構造I / Oを使用しなければなりません。次のコード例では、各WRITE操作の最後のパラメータとして使用されるデータ構造名を見ることができます。また、WRITE操作でレコード様式名を修飾する必要があることにも気づくでしょう。 これは、LIKEFILEキーワードを使用してファイルを定義するときの要件です。 これらの核心に関連する以下のコードを見てください。もちろん、レポートに書き出されるデータ構造にデータを実際に埋め込むロジック部は省略しました。
Dcl-Ds PageHdrDS LikeRec(PrintFile.Heading:*Output); Dcl-Ds PrtRec2DS LikeRec(PrintFile.PrtFmt2:*Output); Write PrintFile.Heading PageHdrDS; Write PrintFile.PrtFmt2 PrtRec2DS;
プロシージャまたは関数の呼び出しのプロトタイピング
ここまでは、プログラムの呼び出しに焦点を当ててきました。プロシージャと関数(これは実際には特殊なタイプのプロシージャです)には更なる機能があります。ここでは、プロトタイピングがプロシージャを呼び出すときに行うことのできる追加事項を見ていきます。
おそらく、最も明白で一般的に利用されている違いは、プロシージャが戻り値を持てるということであり、これが技術的にプロシージャを関数にします。関数は、コードをより読み易く分かりやすくする傾向があります。簡単な例を見てみましょう。
品目の売上税を計算するプログラムを呼び出したい場合、プロトタイプと呼び出しは次のようになるでしょう。
Dcl-Pr CalcSalesTax ExtPgm('UTIL0045R'); Amount Packed(7:2) Const; State Char(2) Const; County Char(15) Const; Tax Packed(5:2); End-Pr; CalcSalesTax(ItemAmt : StateCode : County: TaxAmt);
値を返すプロシージャとして記述された場合、パラメータの代わりにDcl-Prで提供される戻り値の定義を使用して、コードは次のようになります。
Dcl-Pr CalcSalesTax Packed(5:2); Amount Packed(7:2) Const; State Char(2) Const; County Char(15) Const; End-Pr; TaxAmt = CalcSalesTax(ItemAmt : StateCode : County);
このスタイルの呼び出しでは、CalcSalesTaxを呼び出す効果がさらに明確になります。 すなわち、3つのパラメータを使って計算を制御し、その結果はTaxAmtに格納されます。おそらく最初の例でも意味のあるプロトタイプと変数名を使用したことはかなり明白ですが、名前があまり明らかでない場合の違いを想像してください。
戻り値に加えて、プロシージャは、デフォルトのメソッドの“参照渡し”の代わりに、“値渡し”によってパラメータに値を渡すことができます。デフォルトでは、パラメータが渡されると、呼び出し元のプログラムまたはプロシージャ内の変数へのポインタが、呼び出されるプログラムまたはプロシージャに送信されます。これが呼び出し元のルーチンがパラメータ値の変更結果を見る方法ですが、その理由は呼び出されたルーチンに値が渡り、更新された値が戻ってきたからではなく、呼び出されたルーチンが元の変数の値を直接変更したからです。
対照的に、プロシージャを呼び出すとき、呼び出されたルーチンにパラメータの実際の値を渡すことができます。その場合値の受け渡しは一方通行です。つまり、呼び出されたルーチンが値を変更しても、呼び出し元のルーチンでその変更を見ることはありません。変数の値のコピーは常に作成されるので、値渡しは前回の記事で説明したCONSTキーワードの使用と同様の利点があります。大きな違いは、値渡しでは呼び出されるルーチンがその変数の内容に影響を与えないことが100%保証されることです。値渡しを使った税金計算プロトタイプは次のようになるでしょう。
Dcl-Pr CalcSalesTax Packed(5:2); Amount Packed(7:2) Value; State Char(2) Value; County Char(15) Value; End-Pr;
非RPGプロシージャ及び関数のプロトタイピング
RPGに限らず、任意の言語に対する呼び出しをプロトタイプ化することができます。ですから、プロトタイプを使用してCLまたはCOBOLのプログラムまたはプロシージャを呼び出すことができます。プロトタイプを使用して、あらゆるIBM i上にあるすべての標準C関数や、しばしばCスタイルAPIと呼ばれる多くのシステムAPIを含むC関数を呼び出すこともできます。CスタイルのAPIを含むC関数の呼び出しはほぼ常に戻り値を伴い、一般的に値渡しでパラメータが渡されます。プロシージャや関数の名前は小文字になる傾向がありますが、デフォルトでRPGは名前を大文字に変換するので、EXTPROCを使用して小文字の関数名を指定する必要があります。別法として、V7.1ではプロトタイプ名を適切な文字ケースでコーディングするなら、単にEXTPROC(*DCLCASE)と指定することができます。
非常に簡単なC関数sleepのプロトタイプを見てみましょう。これは指定された秒数だけプログラムを“休止”し、成功の場合は0、失敗の場合は-1を返します。
Dcl-Pr sleep Int(10) ExtProc('sleep'); seconds Uns(10) value; End-Pr; Success = sleep(10); // 10秒間休止する
大抵の場合、標準のC関数やCスタイルのシステムAPIのプロトタイプを書く心配をする必要はありません。これらのプロトタイプのほとんどは、すでにRPGに“変換”されており、インターネット検索で簡単に見つけられます。それは、私達と同様にC言語の専門家ではないあなた方にとって救いのはずです。
それでも、少なくともそれらのプロトタイプで何が起きているのか理解したいと思うかもしれません。そのために、私たちが説明すべきCの呼び出しに関するもう一つの癖があります。CはRPGのような方法で文字変数を扱いません。RPGでは文字変数は通常固定長です。たとえ可変長フィールドであっても、指定された範囲内でのみ変化し、常に実際の長さを持ちます。Cは文字変数を長さが不定の文字列として扱います。文字列の終わりは値X’00 ‘の位置によって決定されます。
C関数は文字列が値渡しのポインタとして渡されることも期待しています。同様に、C関数から返される文字列は、ヌル文字で終了する文字列へのポインタで表されます。C関数に渡したい任意の文字列の最後に必須のX’00 ‘という値があることを常に保証すること、ならびに返された任意の文字フィールドからそれを取り除くことができるとはいえ、幸いなことにその手間は不要です。要求すればRPGコンパイラは私たちのためにその仕事をします。
文字列をC関数に渡すには*STRINGオプションを指定して、それを渡す前に適切に書式を整えるようコンパイラに要求します。文字変数が固定長の場合、通常*TRIMを指定して後ろの空白が最初に除去されるようにします。*STRINGを使用する場合、プロトタイプがパラメータ型としてポインタを指定していたとしても、呼び出しパラメータは単に文字変数として指定することができます。RPGはそれをCが見たいと期待する形に様々に翻訳します。
簡単な例として以下のシステム関数を見てください。この関数はQCMDEXCと同様にCLコマンドを実行します。QCMDEXCとは異なり、コマンドが正常に実行されたかどうかを呼び出し元に通知します。戻り値0は成功を意味し、それ以外の値は失敗を意味します。
Dcl-Pr CallSystem Int(10) ExtProc('system'); CmdString Pointer Value Options(*String : *Trim); End-Pr; Dcl-S Command Char(40) Inz('ここにCLコマンドを書く'); Reply = CallSystem(Command);
エラーが通知された場合、C関数__errno()およびその付属API strerror()を使用して、正確な原因を調べることができます。詳細に立ち入るのはこの記事の範囲を超えていますが、Web上にたくさんの使用例があります。
プロトタイプについてもう一言
プロトタイプに関する2つの記事を書きましたが、我々はまだすべてを網羅していません。私たちは優れた基礎を提供しようとしましたが、利用可能なすべてのオプションや、プロトタイプを使用できる別の方法については取り上げていません。いつプロトタイプが必要とされるかについてV7.1で規則が変更されました。私たちは、以前の特別記事でこの事およびいくつかのベスト・プラクティスに関する私たちの考えを書きました。そこには、一般的にプロトタイプがハードコードされたものではなく、ソースメンバーにコピーされる理由も含まれています。