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

モダンなCLプログラミング技法を試す パート2

Bruce Vining 著

かつてないほど高速なCLベース・アプリケーションの作成と保守

このシリーズのパート 1 では、*DEFINED ストレージを使用して構造化データ・セット内でサブフィールドを記述する方法を学びました。パート 1では、フォーマット SBSI0100 を使用している場合に Retrieve Subsystem Information (QWDRSBSD) API で返される受信側変数の最初の 80 バイトを不連続の CL 変数に再定義する方法も確認しました。*DEFINED ストレージを使用すると、比較的分かりやすい Change Variable (CHGVAR) コマンドを使用して、受信側変数から情報を抽出できます (図 1)。*DEFINED ストレージを利用する前にこれを実現するには、CHGVAR コマンドを %sst 組み込み関数などの CL 関数とともに使用する必要がありました (図 2)。図 1 図 2 を比べてみると、*DEFINED ストレージのアプローチの方がはるかに作成しやすく、読みやすいことがわかります。このアプローチの方が実行速度がやや速いのも付加価値となっています。本号では、CL: *BASED ストレージでサポートされている別の種類のストレージを見てゆきましょう。

フォーマット SBSI0100 を使用する場合に受信側変数で返された情報に加えて、QWDRSBSD API もサブシステムに定義された各ストレージ・プールに関する情報を返します。それは 図 3 の Offset 列の「Offsets vary. These five fields repeat, in the order listed, for each pool defined for the subsystem.」というテキストでわかります。

パート 1 で行った同じアプローチを採用することで、*DEFINED ストレージを利用する前に、まずこのサブシステム・ストレージ・プール情報をどのように表示していたかを確認します。それから *DEFINED ストレージを使用していかに生産性を向上させたか、また *DEFINED と *BASED ストレージを組み合わせて、いかに実質的にプログラミングを簡略化できたかを見てみましょう。

ストレージ・プールを表示する

API 文書 atpublib.boulder.ibm.com/infocenter/iseries/v7r1m0/topic/apis/qwdrsbsd.htm は、QWDRSBSD API が 図 3 に示す繰り返しデータの 1 から 10 というオカレンス数をどのように返すことができるかを説明しています。サブシステムに返されるオカレンス数は、次のいずれか小さいほうになります。

  • 最初の 80 バイトのオフセット 76 にあるフィールド値 (定義されたストレージ・プールの数) または
  • 提供された受信側変数に適合するストレージ・プール・オカレンスの数

サンプル・プログラムでは、最大 10 個のサブシステム・プールに相当する情報を収容できるサイズの受信側変数を使用します。今後のリリースでは、i OS は 1 つのサブシステム内で 10 個を超えるストレージ・プールをサポートする可能性があるため、ユーザーには、現在のプログラムでアクセスできるよりも多くのプールが表示に使用できるかどうかが通知されます。(今後の記事では、10 個を超えるストレージ・プールを 1 つのサブシステムで定義できるようにする変更など、システム変更に自動的に対応できる方法での CL プログラムの作成方法を確認します。)

API は現在、最大 10 個のストレージ・プールの情報を返すことができるため、結果の表示にサブファイル・アプローチを採用するのは理にかなっています。しかし、標準 CL は、サブファイル・サポートに関しては多くをサポートしません。また、User Interface Manager (UIM) やオプション製品などインターフェースの導入については、本記事の対象外です。そのため、各ストレージ・プールを一度に表示するだけです。使用しているディスプレイ・ファイルのソース (図 4) の名前は DSPPLSDSPF (Display Pools Display File) です。次のコマンドで作成できます。

CRTDSPF FILE(DSPPLSDSPF) SRCFILE(QDDSSRC)

i 5.4 以前に使用できたレベルの CL サポートを使用した場合、図 5 で見られるようなプログラムを使用してストレージ・プール情報を表示していたかもしれません。プログラムは次の内容を宣言しています。

  • パラメーターが API に 1 つ渡される。パラメーターは 20 バイト長で、最初10 バイトはサブシステムの記述名、次の 10 バイトはサブシステムの記述を含ライブラリーです。
  • レコード・フォーマットを 2 つ含むスプレイ・ファイル DSPPLSDSPF (図 4 のソースを使用して以前作成):
    • ストレージ・プールの属性を表示する SBSPOOLS
    • 表示されているより多くのストレージ・プールが存在することをユーザーに通知する MOREPOOLS
  • QWDRSBSD API に渡される受信側変数の長さを表す CL 変数 &LenSBSI。
    この変数には初期値 360 が割り当てられています。この値は、サブシステムに関する 80バイトの固定情報と 280 バイトのストレージ情報 (最大 10 個のプール、各プールには28 バイトの情報を割当) を含むのに十分なサイズです。
  • QWDRBSBD API に渡される受信側変数になる CL 変数 &SBSI0100。
    この変数は360 バイトの長さを持つものとして宣言されます (CL 変数 &LenSBSI に割り当てられた値)。
  • 定義されたストレージ・プールそれぞれに関連付けられた情報の長さを表す CL 変数&LenPoolInf。
    この変数には初期値 28 が割り当てられています (繰り返し情報を文書化した長さ)。
  • CL 変数 &PoolIDLocn、&PoolNmLocn、および &PoolSzLocn はそれぞれ、サブシステムに定義された最初のストレージ・プールのプール ID、プール番号、プール・サイズの最初の開始位置を表します。
  • CL 変数 &EC_BytPrv、&PoolNbr、&MaxPlsSpt、および &More
    これらの変数は次を表します。
    • &EC_BytPrv: 標準の API エラー・コード・バイト提供フィールド。このフィールドは、0 (ゼロ) に初期化されており、API で検出されたエラーはエスケープ・メッセージとして返されます。
    • &PoolNbr: DOFOR ループ内のストレージ・プールを処理する際に後で使用される作業変数。&MaxPlsSpt は、アプリケーションでサポートされているストレージ・プールの最大数を表します。10 (プール情報の受信側変数に割り当てられているスペース量) に初期化されています。
    • &More: オンに設定されている場合、10 個を超えるストレージ・プールがサブシステムにより定義されていることを示す論理標識。論理変数を CL で明示的に定義できない OS リリースを実行している場合、この変数を 1 バイト長の *Char 変数と置き換え、同じ目標 (より多くのストレージ・プールが存在することを通知するなど) を達成できます。

QWDRSBSD API を呼び出した後、サンプル・プログラムは Change Variable (CHGVAR) コマンドを使用して、ディスプレイ・ファイル・フィールド &SbsName および &SbsLib を受信側変数 &SBSI0100 のそれぞれ開始位置 9 と 19 で見つかったサブストリング化値に設定します。プログラムは、割り当てられた受信側変数内に収まるより多くのストレージ・プールがサブシステムにより定義されているかどうかを判断します。これは、サブシステムで定義されたプールの数 (受信側変数内の固定開始位置 77 で返される 4 バイトのバイナリー値) を、プログラムでサポートされているストレージ・プールの最大数 (&MaxPlsSpt) と比較して行います。定義されたプールの数が &MaxPlsSpt より多い場合、論理変数 &More が true に設定されます。定義されたプールの数が &MaxPlsSpt 以下の場合、変数 &MaxPlsSpt は、サブシステムにより実際に定義されたストレージ・プール数に設定されます。

ストレージ・プールを処理する

サンプル・プログラムは DOFOR ループに入り、サブシステムのストレージ・プールをすべて処理します。DOFOR 処理は、返された最初のストレージ・プール・エントリーから開始し、次を実行します。

  • プール ID、プール名、およびプール・サイズを表示フォーマット SBSPOOLS の対応するフィールドにサブストリング化します。
  • 表示フォーマット SBSPOOLS の書き込みおよび読み取りを行います。
  • F3 キーを押すと、DOFOR ループから退出します。
  • F3 キーを押さないと、1 つのストレージ・プール情報エントリーのサイズを、現在表示されているプール ID、プール名、およびプール・サイズへのアクセスに使用する開始位置へ追加し、受信側変数内の次のストレージ・プール情報セットにアクセスします。
  • 表示するすべてのストレージ・プールが処理されているわけではない場合、このプロセスのステップ 1 に戻ります。

DOFOR の終了時、次の条件下でレコード・フォーマット MOREPOOLS が表示されます。

  • 表示されているより多くのストレージ・プールがサブシステムで定義されている。
  • 定義されたすべてのストレージ・プールを表示する前に、F3 キーで DOFOR を終了しなかった。

プログラムが終了します。

プログラムの動作をデモンストレーションする場合、次のコマンドを使用してサブシステムを作成できます。

CrtSbsD SbsD(VINING/SAMPLESBS) Pools((1 1000 5) (3 *Base) (4 *ShrPool5))

次のコマンドで Display Subsystem Pools の DSPSBSPLS という CL プログラムをコンパイルします。

rtBndCL Pgm(DSPSBSPLS)

次のようなコマンドを使用して、プログラムを実行します。

Call Pgm(DSPSBSPLS) Parm('SAMPLESBS VINING') or
Call Pgm(DSPSBSPLS) Parm('SAMPLESBS *LIBL')

これらのコマンドを実行すると、 図 6 のような内容が表示されるはずです。
図 5 では、%sst および %bin の組み込み演算がプログラムで多数使用されているのがわかると思います。CL の *DEFINED ストレージ機能を使用して、このプログラムを再書き込みすることで、このプログラムの読みやすさと保守容易性を大幅に改善できます (図 7)。
個人的には、図 7 のプログラム・ソースは図 5 のソースよりもかなり読みやすくなっていると思います。図 7 でソースに行った変更内容は次のとおりです。

  • 基本構造 &SBSI0100 の固定位置にある &SBS_Name、&SBS_Lib、および &SBS_Pools 各サブフィールドの定義
  • API によって返された各ストレージ・プールの情報を表す新しい構造 &PoolInf の定義
  • 構造 &PoolInf の固定位置にある &PoolID、&PoolName、および &PoolSize 各サブフィールドの定義
  • &PoolIDLocn、&PoolNmLocn、および &PoolSzLocn 各ロケーション変数の &PoolLocn ロケーション変数との置き換え (&PoolLocn は、サブシステムにより定義された最初のストレージ・プールに関する情報が返される、構造 &SBSI0100 内のロケーションに初期化されます。)
  • &SBSName および &SBSLib のサブストリング演算設定値のそれぞれ &SBS_Name および &SBS_Lib 各定義サブフィールドへの直接参照との置き換え
  • &MaxPlsSpt の値の設定に使用する 2 項演算の &SBS_Pools 定義サブフィールドへの直接参照との置き換え
  • 開始位置 &PoolLocn で見つかった 28 バイトの &PoolInf 構造へのサブストリング化 (28 バイトは、API で返された各ストレージ・プールの情報量)
  • &Pool、&Name、および &Size の各サブフィールドの値を設定するサブストリング化演算のそれぞれ &PoolID、&PoolName、および &PoolSize の定義された &PoolInf サブフィールドへの直接参照との置き換え
  • &PoolIDLocn、&PoolNmLocn、および &PoolSzLocn を増やす 3 つの CHGVAR コマンドの &PoolLocn を次のプール情報エントリーの位置に増やす 1 つの CHGVAR への置き換え

基底付きストレージ・データの転送

ここでは、以前の 5.4 サポートを使用してプログラム作成した方法とはスタイルが大きく変わっています。しかし、読みやすさを改善している変更がある一方で、サブシステムの各ストレージ・プールに関連付けられた情報を表示するプログラムの基本的な設計は変わっていません。基本的に次の変更が行われています。

  • 返される情報のサブフィールドを定義する 7 つの宣言を追加しました。
  • 多数のサブストリングと 2 項組み込み演算を、アクセス中のデータへより意味のある参照へと置き換えました。
  • 各ストレージ・プールのプール関連情報を &PoolInf 構造にサブストリング化するために 1 つの CHGVAR コマンドを追加しました。

&PoolInf の定義サブフィールドを使用するためにプール関連情報を &PoolInf にコピーするというこの最後の変更は、データを &PoolInf 変数に関連付けられたストレージにコピーする場合に必要です。CL 変数の宣言には、STG(*AUTO)、STG(*DEFINED)、STG(*BASED) という 3 つのストレージ・オプションがあります。

STG(*AUTO) は、宣言中の変数に関連付けられたストレージを直接割り当てる必要があることを示しています。これはデフォルトのオプションで、図 5 に示すプログラムにより排他的に使用されます。

STG(*DEFINED) は、宣言中の変数に関連付けられたストレージが以前割り当てられたストレージの再定義であることを示しています。例えば、&PoolID サブフィールドは &PoolInf CL 変数の再定義です (&PoolInf は STG(*AUTO) を使用して宣言されています)。このオプションは 図 7 に示すプログラムで使用されています。

以前やったように *AUTO 変数で定義されている STG(*AUTO) と STG(*DEFINED) の両方を使用することで、TYPE, LEN などの CL 変数定義は、変数に割り当てられているストレージに直接関連付けられています。変数定義とストレージがこのような関係にあるために、 CHGVAR を使用してプール関連の受信側変数データを両方のプログラムの別個に定義された CL 変数に移動する必要があります。

*DEFINED ストレージを使用して &SBSI0100 受信側変数のストレージの位置を再定義することで、このデータ・コピーは回避できた可能性があることを指摘しておきます。適切なストレージの位置は、&PoolID1、&PoolName1、&PoolSize1、&PoolID2 など 30 個の別個の CL 変数 (10 個のプールそれぞれが 3 つのサブフィールドを持つ) として再定義されます。これは、&SBS_Name、&SBS_Lib などを使用して &SBSI0100 の最初の 80 バイトを再定義した方法に似ています。しかし、これは複数の CHGVAR コマンドを使用して &SBSI0100 プール関連データを適切な CL データ定義に移動する必要がある、面倒でエラーが発生しやすいアプローチだったでしょう。私なら、これは &SBSI0100 データを &PoolInf データ定義にコピーしなくても済ませるためだけに実行します。単に TYPE, LEN などの &PoolInf CL データ定義を &SBSI0100 データに移動する方がずっと生産的と思います。変数ストレージ割り当てに対してデータ定義を移動 (またはマップ) するこの機能は、サード・ストレージ・オプション STG(*BASED) を使用して取得される内容そのものです。

基底付きストレージにより、変数ストレージに動的にアクセスできるようになります。我々のケースでは、サンプル・プログラムの &SBSI0100 CL 変数に関連付けられたストレージです (図 8)。基底付きビュー (サンプル・プログラムの &PoolInf CL 変数に関連付けられたデータ定義) を使用してそのストレージを処理できます。図 7 および 図 8 間のプログラムに行われた変更は明快です。

  • &PoolInfPtr CL ポインター変数を TYPE(*PTR) として宣言し、初期アドレスは&SBSI0100 CL 変数のオフセット 80 に設定されています。このアドレスは、最初に戻されたプール関連情報の構造の開始に対応しています。
  • &PoolInf CL 変数の宣言を STG(*AUTO) のデフォルト値から STG(*BASED) に変更し、基本ポインターは BASPTR(&PoolInfPtr) を使用して以前の変更で宣言されたポインターと指定します。
  • CL 変数 &PoolLocn は必要なくなったため、その定義を削除します。
    CHGVAR コマンドを削除します。%sst(&SBSI0100 &PoolLocn &LenPoolInf) で見つかったデータを以前 &PoolInf に移動していた。このデータ移動は必要なくなったためです。
  • &LenPoolInf から &PoolLocn への追加を変更し、代わりに &LenPoolInf を&PoolInfPtr ポインターのオフセット (%ofs) に追加します。

これらの変更により、%sst および %bin の組み込み演算すべてを使用する必要がなくなり、それらを CL 変数の参照と置き換えます。これによりプログラム・ロジックは、該当データの定義方法ではなく、データの処理内容が中心になります。副効用として、ある変数から別の変数へ移動する必要性が少なくなります。しかし、プール情報をディスプレイ・ファイル・レコード・フォーマットの変数に移動する必要があるため、データ移動は一部継続して行われる点に注意してください。

図 8 に示すプログラムで見落とされやすい 2 つの項目を指摘したいと思います。

まず、&PoolInfPtr ポインターの初期化に使用するオフセット値は 80 である点に注目してください。この値は %sst を使用する場合に、&PoolLocn 変数の初期化に使用する開始位置 81 とはわずかに異なります。ADDRESS(&SBSI0100) を使用して &PoolInfPtr ポインターを初期化する場合、ポインターは &SBSI0100 受信側変数の先頭バイトをポイント (またはアドレス指定) するよう設定されます。サブストリングの観点からは、これが先頭バイトになります。つまり、開始位置 1 です。&PoolInfPtr と &PoolLocn 両方について、80 (SBSI0100 フォーマットの非反復フィールドのサイズ) を追加し、返された最初のストレージ・プールに関連付けられたプール情報の先頭バイトにアクセスします。%sst や %bin などの組み込み関数で行うように、1 つをすべてに追加するというこのプロセスに慣れるには多少時間が掛かります。

次に、ADDRESS(&SBSI0100) を使用して単純に &PoolInfPtr を初期化し、別途 CHGVAR 操作として、80 をポインター値に後で追加できたかもしれません。これは、後で &LenPoolInf を追加し、QWDRSBSD API で返される各ストレージ・プールを処理する方法と似ています。&PoolInfPtr の正しい値を設定する 2 種類の方法 (さらに他の多くの可能な方法) は、ストレージ・プール情報へアクセスできる点で同じであり、実質的には個人の好みの問題です。

「ポインターを使用するのは危険な場合があると聞いたことがある」と考えている読者がいないか予想しているのですが。この見解は、まったくもって真実です。ですが、これは読者が使用している可能性があるプログラミング・ツールについても、かなり当てはまるのです。しかし、ポインターは時の経過とともに、使用時には通常以上の気配りが必要なことを示唆する風評を得たのです。幸運なことに、この風評は IBM i でアプリケーション開発をする場合には必ずしも当てはまりません。

ポインターの利用に関する恐ろしい話の多くは、実際にポインターを使用することよりも、アプリケーションが動作しているシステムのアーキテクチャーを反映していると言えます。一部のシステムでは、業務アプリケーション内でポインターを処理することは、大ハンマーを使用して本棚を作ることに似ていました。明らかにいくつか釘を打ち付けることはできますが、気を付けないと、本棚自体をかなり傷付けるおそれがあります。大ハンマー・ポインターを使用すると、システムの他のジョブを不用意に傷付けるおそれがあり、場合によってはシステム自体を傷付ける場合があります。この場合、システムをリブートしなくてはならなくなります。

この種の問題は、過去に聞いたことがある多くの警告の原因であり、ポインターの問題が解決されたら、お酒でも飲みながら楽しく会話できることは明らかです。この場合、システム内で対応できる内容という点では、ポインターはシステムによる制限を受けません。ポインターに関するこうした柔軟性は、オペレーティング・システムを作成している場合は素晴らしいと言えますが、業務用アプリケーションを作成しようとしている場合は危険とも言えます。

IBM i では、ポインターは大ハンマーと言うよりは釘抜き付きハンマーにはるかに似ています。それでも本棚に傷を付ける場合がありますが、傷はローカル・ジョブに制限されます。より正確に言えば、損傷は活動化グループ、またはポインターで現在対応しているジョブ内の似たようなストレージ域に制限されます。別のジョブのストレージに対処するあるジョブにポインターを持つことが IBM i では可能ですが、大ハンマーを使用して偶然にできるというものではありません。API を明示的に使用して、システムに許可を求めて (許可され、それを行ったことの追加責任を請け負って) 初めて可能です。IBM i の正しい動作に関してストレージに直接アクセスできるようなプログラミング・インターフェースは提供されません。そのため、ある程度は、一日の予想外の時間にシステムを再 IPL しなければならないことは滅多にありません。

本棚を作る場合、釘抜き付きハンマーがそのジョブに最適です。釘を挿入するのにぴったりのサイズで、必要な場合は、限られた領域内のかなり大きなサイズの釘を取り除きます。ポインターを誤って使用することで、ジョブ内で問題が発生することがありますが、問題の場所は突き止められ、システム全体に壊滅的な損傷を与えることはありません。

例えを広げると、%sst および %bin 組み込み関数とともに使用されている開始位置は、鋲打ちハンマーに似ています。それでも本棚を傷付ける場合がありますが、傷は直接ポインターを処理する変数に限定されます。一方、CL コマンドにより直接参照されていない変数を傷付ける場合があります。しかし、少なくともこれらの変数はジョブに関連付けられます。

ジョブに最適なツール

個人的には、鋲打ちハンマーで本棚を作ろうとは思いません。明らかに、適度な数の本を支えることができそうな本棚とは思えませんからね。釘抜き付きハンマーをツールキットに備えています。

さて、サブストリング演算をなくすためだけにポインターを使用するのは、少しばかり行き過ぎのように思うでしょう。図 8 に示す基底付き構造アプローチの方が好ましいと思う一方、サンプル・プログラムで示しているように、サブストリング演算を止めてポインターを使用する必要はないとも思います。しかしこの記事では、ポインター導入の機会を与えながら、当面のジョブには鋲打ちハンマーで十分であるというシナリオを採用しています。このシリーズの次号では、苦痛に耐えながら鋲打ちハンマーで本棚を作るよりも、ポインターでいかに簡単に本棚を作ることができるかをお見せしたいと思います。

あわせて読みたい記事

PAGE TOP