RPG

274. ポインターを使わない sprintf

C/400, いわゆる C言語の関数には RPG にはない、わかりやすい関数が
数多く用意されている。
IBM は、ILE-RPG の中から C/400 の関数を利用できるようにするために
バインド・ディレクトリー: QC2LE を提供しており、

H BNDDIR('QC2LE')

のようにして H-仕様書に定義すれば ILE-RPG の中から C/400 の関数を
呼び出すことができる。
( 実際はこの指定がなくてもバインドのときに自動的に C/400 関数は
  インクルードされるのだが仕様によれば、この指定が必要ということなので
  あえて必要である、として紹介した。)

これらは機会があれば C/400 の関数の利用方法をいくつか紹介していく予定であるが
今回は RPG にはない、わかりやすくて使いやすい、

関数: sprintf

について解説しよう。
同時にポインターを使わないでポインター・パラメータに値を指定して渡す方法も
紹介する。
この方法は API を利用するときにかなり役に立つはずである。
多くの API のパラメータはポインターであり、そのためポインターを苦手とする
RPG 開発者にとって朗報である。

関数 sprintf とは

sprintf 関数とは書式を編集する関数である。

sprintf((結果の変数), (書式フォーマット),
           代入する変数1, 代入する変数2, … )

のように定義されており例えば C/400 で記述すると

sprintf(BUFF, "<definitions name="%s" len=%d>", FIELD, LEN);

を実行すると受入れ変数 BUFF には FIELD="CGIPARM", LEN=15
書式:

"<definitions name="%s" len=%d>"

に代入される。 %s には文字列、%d には数値が代入されるという意味である。
わかりやすくはないだろうか ?
これを ILE-RPG で書くと

【RPG ①】
 /FREE
     EVAL BUFF= '<definitions name="' + %TRIMR(FIELD) + '" len=' -
           %TRIML(%CHAR(LEN)) + '>";
 /END-FREE			

となるはずである。
定位置記述のほうがいいという人であれば

【RPG ②】
 D BEG_TAG          C            CONST('<definitions name="')
 D MID_TAG          C            CONST('" len=')
 D END_TAG          C            CONST('>")

 C             BEG_TAG     CAT  FIELD:0         BUFF
 C                         CAT  MID_TAG:0       BUFF
 C                         CAT  %CHAR(LEN)      BUFF
 C                         CAT  END_TAG         BUFF

となってしまう。( 恐らく CAT %CHAR(LEN) はコンパイル・エラーかも? )
やはり sprintf を使って

	
sprintf(BUFF, "<definitions name="%s" len=%d>", FIELD, LEN)

と書いたほうがわかりやすいのは目的とする書体がはっきり目の前に
書式: "<definitions name="%s" len=%d>" として現れているからである。
これに対して 【RPG ①】【RPG ②】 の場合は書き手はわかるかも知れないが
読み手にはわかりにくい記述である。
読み手は結果としてできあがる文字列を頭の中で想像しなければならない
からである。
PHP が作り手にとってわかっても読み手にとってわかりにくくなってしまうのと
同じである。
JSP&Servlet も読み手にとって非常にわかりにくい。
さて sprintf を使った C言語記述のほうが見やすくわかりやすいことは
ご理解頂けたと思う。

バインド・ディレクトリー QC2LE をバインドすれば C言語の関数も
RPG の中で使える、ということなので sprintf も使えるということに
なるのだが実は sprintf%s に代入する文字列パラメータは
文字列の最後を NULL (0x00) で終わるポインターのパラメータである。
さらに数値として 整数: INT ( 10I 0) を定義しても値は正しく代入されない。

もちろんこのような処理を行う方法は IBM 解説書のどこを探しても
説明はない。

【 サンプル・ソース: TESTFMT 】
 
0001.00 H BNDDIR('QC2LE')                                                       
0002.00 D SPRINTF         PR            10I 0 EXTPROC('sprintf')                
0003.00 D  BUFF                      32767A   OPTIONS(*VARSIZE)                 
0004.00 D  FORMAT                         *   VALUE OPTIONS(*STRING)            
0005.00 D  VAR                            *   VALUE OPTIONS(*NOPASS:*STRING:*TRIM)
0006.00 D  LEN                          10I 0 VALUE OPTIONS(*NOPASS)            
0007.00                                                                         
0008.00 D FORMAT          C                   CONST('<definitions -             
0009.00 D                                     name="%s" len=%d>')               
0010.00 D BUFF            S             40A                                     
0011.00 D FIELD           S             11A                                     
0012.00 D LEN             S             10I 0 INZ(15)                           
0013.00 D RES             S             10I 0                                   
0014.00 D                                                                       
0015.00 C                   MOVEL(P)  'CGIPARM'     FIELD                       
0016.00  /FREE                                                                  
0017.00    RES = sprintf(BUFF:FORMAT:FIELD:LEN);                                
0018.00  /END-FREE                                                              
0019.00 C                   MOVEL(P)  BUFF          DSP40            40         
0020.00 C     DSP40         DSPLY                   ANS               1         
0021.00      C                   SETON                                        LR
【 解説 】

非常に短いソースであるが是非覚えて欲しい重要なテクニックが収められている。
まず

   H BNDDIR('QC2LE') 

は、C言語の関数を利用するためのバインド・ディレクトリーの記述である。
次に

 
0002.00 D SPRINTF         PR            10I 0 EXTPROC('sprintf')                
0003.00 D  BUFF                      32767A   OPTIONS(*VARSIZE)                 
0004.00 D  FORMAT                         *   VALUE OPTIONS(*STRING)            
0005.00 D  VAR                            *   VALUE OPTIONS(*NOPASS:*STRING:*TRIM)
0006.00 D  LEN                          10I 0 VALUE OPTIONS(*NOPASS)

が非常に重要な C言語の sprintf の関数のプロトタイプの宣言である。
SPRINTF とは、このプログラムで使用する名前だが実際に参照するのは
EXTPROC('sprintf') によって示されている。

受入れバッファー: BUFF は最大のサイズまで利用できるように

 D  BUFF                      32767A   OPTIONS(*VARSIZE)

として、しかしどのような長さにも対応できるように OPTIONS(*VARSIZE)
指定されている。
書式フォーマットは

 D  FORMAT                         *   VALUE OPTIONS(*STRING)

とあるように変数の型は C言語の言うところの 最後に NULL 文字を
ストッパーとしているポインターである。( C言語では char* )
ここで OPTIONS(*STRING) と指定すると sprintf にパラメータとして渡すときに
i5/OS によって文字列の後ろに自動的に NULL 文字を
付加してくれることを意味する。
つまり、

 C                CAT X'00':0         FORMAT

のような演算を代わりにやってくれることを意味する。
この OPTIONS(*STRING) は API を呼び出すときに
char* と指定されているパラメータを
ILE-RPG で使用するときにも使うことができる。

次に sprintf には文字列変数を

 D  VAR                            *   VALUE OPTIONS(*NOPASS:*STRING:*TRIM)

と定義しており *TRIM は変数として渡すときに自動的に前後のブランクの文字列を
トリム(除去)してくれることを意味する。
次に *NOPASS を説明しよう。
*NOPASS とは省略可能なパラメータであることを示す。
関数 sprintf

 sprintf(BUFF, "<definitions name="%s" len=%d>", FIELD, LEN)

のように記述したときは FIELD は文字変数であり、
LEN は数値変数であったはずである。
これは変数は文字列であっても数値であっても、
どちらでも使用可という意味ではなく
sprintf のパラメータは複数のいくつかの文字列変数のパラメータの後に続いて
数値変数のパラメータが連続して定義されている。
つまり FIELD という定義は
FIELD を定義してその後に続くはずの文字列変数パラメータが
省略されているということである。
従って *NOPASS がないと LEN も文字列変数として sprintf に見なされてしまい
LEN の数値が正しく sprintf に渡されないということになる。
LEN が正しく渡されるために VAR には *NOPASS を指定したのである。
また LEN の後続を省略できることを示すために

 D LEN                          10I 0 VALUE OPTIONS(*NOPASS)

として LEN にも *NOPASS の指定が必要である。

このように指定すれば後は簡単であり

  /FREE                                                                  
     RES = sprintf(BUFF:FORMAT:FIELD:LEN);                                
   /END-FREE 

によって sprintf をわずか一行で実行することができる。

書式を

<definitions name="%s" len=%d>

で示しておいて %s に文字列を %d に数値を代入するという考え方は
実にシンプルでわかりやすい。
sprintf を使えばこれまでのように CAT 命令の繰返しでは
読み手は理解しにくいものとなる。
開発言語にも特性や特質がある。
C言語に優れた関数が用意されていて
ILE-RPG はその関数をインクルードすることができるという
他の言語にはない利点があるのだから大いに利用すべきだろう。
これによって洗練されたわかりやすいソースになることは間違いない。