前回の復習
「第十回 サービス・プログラムの作成(2)」では、サービス・プログラム内で定義済の関数のロジックを変更する際の注意点と、プロトタイプ定義の共通化の重要性について解説しました。再作成したサービス・プログラムを有効にするには、RCLACTGRP コマンドで活動化グループの削除が必要でしたね。また、サービス・プログラムと、その関数を使用する *MODULE オブジェクトのコンパイル時に共通のプロトタイプ定義を使用することで、呼び出す関数とその引数、戻り値が正しいことが確認できることも学びました。次に、サービス・プログラムを事前に登録しておくバインド・ディレクトリ・オブジェクトも紹介しました。バインディング・ディレクトリにより、プログラマーは自分が使用する関数を含んだサービス・プログラム名を知らなくても、プログラムを作成(バインド)することが可能になります。
今回は、サービス・プログラムの最後の回です。皆さんに是非知っておいていただきたい事をまとめて紹介します。扱う項目は、引数の様々な種類の渡し方の説明と、それを応用したサンプル・プログラムの作成、既存のサービス・プログラムにユーザー関数を追加する場合の注意点についてです。今まで説明した箇所は省略しているところもありますので、これまでの回を適宜振り返りながら、今回の内容の理解を深めてください。それでははじめましょう!
引数の指定方法
すでに解説している通り、引数とはサブ・プロシージャが受け取るパラメータのことで、プロシージャ・インターフェース定義でその数とそれぞれのタイプを指定します。「第三回 プログラムの基本構造」で getIncludeTax 関数を作成する際、プロシージャ・インターフェース定義内の金額を受け取る引数に value キーワードを指定しました。その際に「value キーワードは引数を値で受け取ることを指定します」と解説しています。value キーワードはその渡し方をコンパイラーに指示するためのものです。実は、引数の渡し方は以下の3通りがあります。
- 参照渡し
- 値渡し
- 読み取り専用参照渡し
関数を呼び出すプログラム側の記述の仕方や、システム API 等を呼び出す上でもこれらの知識は必要になりますので、それぞれをしっかり理解しておきましょう。
参照渡しと値渡し
参照渡しとは、呼び出し元のプログラム内の変数のアドレスを、呼び出し先と共有する渡し方を言います。プロシージャ内の変数は、プログラムが活動化されるとメモリーにその領域が確保されます。参照渡しはこのメモリー上のアドレスを引数として渡します。呼び出された側はそのアドレスでメモリー上の領域を参照し、その値をそのプロシージャ内の変数として共有することで値を参照するのです。それぞれのプロシージャでは別々の変数名で参照していますが、実態はメモリー上の同じ領域を参照していることになります。そのため、呼び出されたプロシージャでこの変数の値を変更すると、呼び出し元の変数の値も変更されます。同じ領域だから当たり前ですね。この特性を利用して、関数の戻り値ではなく引数の一部を、実行結果を戻す方法として使用することも可能です。
呼び出される側の変数定義は参照渡しの場合は共有なので、双方で渡す変数の数とそれぞれのデータ・タイプおよび属性は同じでなければなりません。ちなみに RPG Ⅲ や IV で別のプログラムを呼び出す際に CALL 演算命令を実行しますが、この際のパラメータはこの参照渡しのみとなります。
次に値渡しですが、変数を引数に指定するのではなく値そのもの(文字・数値リテラル、値を返す式)を指定して渡します。呼び出し元は、値そのものを呼び出し先のプロシージャで定義したメモリー上の領域(変数)にコピーします。このため、呼び出し先でその変数の値を変更しても呼び出し元にはなんら影響がありません。また、値のコピーなので、異なるタイプであっても可能なケースがあります(数字変数の異なるタイプなど)。値渡しを行うには、プロトタイプおよびプロシージャ・インターフェース定義で該当の引数に value キーワードを指定します。
最後に読み取り専用参照渡しですが、変数のアドレスを共有する点は最初に説明した参照渡しと同じです。ただし、「読み取り専用」なので、呼び出し先で渡された変数を変更することはできません。これは実行時にエラーになることでそれを実現するのではなく、コンパイル時に、呼び出し先で受け取った変数を変更する命令があった場合にコンパイル・エラーにすることで実現しています。また、呼び出し側で値渡しと同様の指定方法(リテラル、値を返す式)で記述することも可能です。この場合は、内部的にプロトタイプのパラメータと同じデータ・タイプと属性で一時的な変数を作成してその値をコピーし、その一時的な変数のアドレスを渡すことになります。読み取り専用参照渡しを行うには、プロトタイプおよびプロシージャ・インターフェース定義で該当の引数に const キーワードを指定します。
それでは icafe024 から icafe026 まで、Orion でソースを登録してプログラムを作成してみましょう(iCafe 実習メニューのオプション 1)。とくに、icafe026 は、このままではコンパイル・エラーになることを確認し、dsplyCode 関数内で pCode の値をセットしている行を削除すればコンパイルできることを確認してください。
三種類の引数の渡し方を表にまとめておきます。
引数の省略
プロシージャ・インターフェースで定義した引数は、すべて渡すのが基本ですが、場合によっては省略したいケースもあります。ある特殊なケースのみパラメータを渡したい場合などです。以下のキーワードを引数に指定することで引数を省略することが可能です。
- options(*nopass)
- options(*omit)
では、それぞれのキーワードを解説していきましょう。
options(*nopass) は、文字通りパラメータを「渡さない」可能性があることを指定します。このパラメータを指定すると、これ以降のすべての引数にも同じ options(*nopass) を指定しなければなりません。サンプルをみてみましょう。
このプロシージャ・インターフェスでは三つの引数を定義していますが、二番目と三番目に options(*nopass) を指定しています。これは、第一引数のみ必須で、第二および第三引数は省略可能であることを表します。このプロシージャ・インターフェースを含む dsplyCode 関数の呼び出し方は以下の三通りです。
呼び出す側は、省略可能なパラメータが何番目からなのかを知っていれば良いので問題ありませんが、呼び出される側(dsplyCode)はそうはいきません。様々なプロシージャから呼び出されるので、第二および第三パラメータが指定されていたかどうかを判別する必要があります。これを実現するのが %parms 組み込み関数です。この関数は、プロシージャに渡されているパラメータの数を戻します。この戻り値でパラメータが(先頭から)いくつ渡されたかを判断してその後の処理を行うことになります。もし、渡されていないパラメータ変数をプログラム内で参照した場合は MCH3601(ポインターが参照された一に設定されていない)エラーになるので注意しましょう。%parms 関数の使用例は以下の通りです。
次に options(*omit) ですが、これは呼び出す側がそのパラメータを省略するのではなく、値を渡さないことを意味する *omit を指定することでそのパラメータの省略を可能にします。*omit を指定した引数には value キーワードは指定できません。つまり、値渡しの引数には options(*omit) は指定できないということです。
ではサンプルをみてみましょう。
先程と同じく、三つの引数を定義しています。第二および第三引数に options(*omit) を指定しており、第二引数は読み取り専用参照渡し、第三引数は参照渡しで定義しています。このプロシージャ・インターフェースを含む dsplyCode 関数を呼び出す方法は以下の通りです。
特に三番目の例に注目してください。第二引数のみ渡さないことを指定する *omit を指定しており、第一および第三引数は渡しています。このように、複数パラメータで途中のみ渡したくない場合は options(*omit) で実現できます。
*nopass 同様、呼び出される側ではどの引数が omit されたかを判別しなければなりません。この判別には %addr 組み込み関数を使用します。
%addr 関数は、引数に指定した変数のアドレスを戻します。options(*omit) は value を指定できないことは先程触れました。つまり、内部的には必ず変数のアドレスを渡す参照渡しになります。そして引数が omit されるということは、呼び出し元がその変数のアドレスを渡さないということと等しいので、この場合の %addr 関数はアドレスを戻すことができずに *null を戻すのです。呼び出される側は引数のアドレスが *null かどうかで渡されたかどうかを判断するわけです。では実際のコードのサンプルを見てみましょう。
options(*nopass) と options(*omit) の違いをしっかり理解しておきましょう。
日付を渡すと曜日を表示するプログラムの作成
それでは、ここまで解説してきた内容を踏まえたサンプル・プログラムを作成してみましょう。プログラム名は icafe027 です。日付を引数で受け取り、その日付の曜日を計算して dsply で画面に表示します。
第一引数は日付(8桁の文字列 / yyyymmdd 形式)、第二引数は曜日を英字で表示するか日本語で表示するかを ‘1’ か ‘2’ で指定します。両引数とも読み取り専用参照渡しで定義します。第二引数は省略可能とします。
曜日の計算については、基準日(日曜日)をプログラム内で設定し(①)、その基準日と引数との間の日数を計算し(②)、その日数を 7 で割った余りを利用して曜日を算出(③)します。
基準日はグレゴリオ暦開始(1582年10月15日 / 金曜日)以降の最初の日曜日(1582年10月17日)を設定します。因みに日本でグレゴリオ暦を採用したのは1873年1月1日からなので、それ以降の日付に対応します。それではプログラムの骨格をみていきましょう。
日付は originalDate、曜日タイプは convertType でそれぞれ受け取ります。convertType は options(*nopass) なので省略可能です。省略されたかどうかは %parms 組み込み関数で判断します。
第一引数で受けた日付は文字なので、%date 関数を使用して日付形式のフィールドに変換します(calcDate)。①の基準日(baseDate)との間の日数は %diff 関数を使用して算出しています。更に②を 7 で割った余りを %rem 関数で取得しています。対象日付が日曜日の場合は余りはゼロになります。このまま「ゼロ=日曜日」としても良いのですが、今回は曜日を配列に事前にセットして、その要素番号と関連付けを行いたいので、%rem 関数の結果に 1 を足しています(配列の 1 番目の要素を日曜とする)。では、その配列の定義を見てみましょう。
英語表記、日本語表記それぞれのデータ構造を作成し、name 配列はそのサブフィールドを一番目の要素から順にセットしています(pos(1))。配列には qualified キーワードが指定されているので、 name 配列もデータ構造名で修飾して各要素を参照可能です。
プログラム全体は以下の通りです。icafe027.rpgle で登録し、コンパイルして実行してみましょう。
実行結果は、iCafe 実習メニューのオプション 5 で確認しましょう。
このプログラムは基準日が 1582年10月17日なので、それ以前の日付を指定して実行すると基準日との日数がマイナスになってしまい、プログラムはエラーで終了します。実際のプログラムにこのロジックを応用する場合は、エラー処理を入れることを忘れないでください。
サービス・プログラムを変更する場合の考慮点
先程作成した曜日を戻すプログラムも、様々なプログラムから利用することになると思います。できればサービス・プログラムに含めておきたいですね。前回作成した icafesrv サービス・プログラムにこの icafe027 のロジックを関数として追加してみましょう。以下の手順を参考にしてください。
- rpgle の main プロシージャ全体をコピーし、icafesrv.rpgle の最後にペーストする
- コピーしたサブ・プロシージャ名を getDaysOfWeek に変更し、外部から参照できるように export を指定する(④)
- 4桁の文字を戻り値として返すようにプロシージャ・インターフェースを修正する(⑤)
- 曜日を配列から検索した結果を直接 retrun させる
- メニューのオプション 12 で icafesrv モジュールを作成する
- メニューのオプション 13 で icafesrv サービス・プログラムを作成する
それでは作成した getDaysOfWeek を呼び出すプログラム icafe028 を作成してみましょう。8桁の日付(文字)を引数で受け取り、日本語表記の曜日を dsply するプログラムです。getDaysOfWeek のプロトタイプ定義を prototype.rpgtle に記述し、作成する icafe028 で /include するのを忘れないようにしてくださいね。次回サンプル・コーディングを紹介しますが、まずは今までの内容を思い出しながら挑戦してみましょう!
最後にサービス・プログラムを修正する上で気をつけなければならないことをお話します。今回、サービス・プログラムを CRTSRVPGM で作り直しました(メニューのオプション 13)。では、このサービス・プログラム内の関数 getIncludeTax を使っている icafe023 を実行してください。「CALL コマンドでエラーが見つかった。」と表示されると思います。詳細なメッセージは「プログラム記号の違反」です。プログラム記号というのは、サービス・プログラム作成時にシステムが自動的に設定するもので、内部に含んでいる関数によって変わってきます。関数を使用するプログラムは、そのプログラム作成時のプログラム記号を内部に記憶しており、実行するたびに実際の記号と比較して異なる場合は記号違反とみなして実行を中止してしまうのです。正しく実行させるためには、プログラムの再作成が必要になる点に注意してください。
実は、サービス・プログラムを修正しても、既存のプログラムを再作成する必要なく実行する方法もあります。その際に使用するのがバインダー言語です。業務で利用開始したサービス・プログラムを今回のように追加修正する場合は、バインダー言語が必要になるということを覚えておいてください。
終わりに
今回は、サブ・プロシージャの引数の様々な渡し方について解説しました。また、曜日を取得するプログラムを通して、実際の使い方についても理解を深めてもらえたと思います。曜日を取得するプログラムは、サービス・プログラム化およびそれを使うプログラムを作成して、ユーザー関数の便利な点、作成時に注意すべきこと、その他考慮点などじっくり理解を深めてください。
さて、この連載も残すところあと一回となりました。次回はこれまでの連載のまとめと、FFRPG だからこそ可能な今後のシステム開発について、色々な事柄について書いてみたいと思います。次回もお楽しみに!