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

RPGでの安全な16進数処理の実践

Jon Paris 著

この記事では、RPG関連のインターネット リストでたびたび挙げられる質問について取り上げようと思います。すなわち、「文字ストリングを、それに対応する16進値に変換する簡単な方法はありませんか」という質問です。

もちろん、1つの回答としては、ルックアップ テーブルを使用して独自のルーチンを書くことでしょうが、もっと簡単な方法があります。それは、システムの16進数MI APIを利用することです。これらは元々、CおよびC++による使用に向けて登場したものですが、ILEにとって喜ばしいことに、どのILE言語でも使用することができます。それだけではなく、RPGのプロトタイピング サポートにより、それらは非常に使いやすくなっています。実際、文字表現からそれに対応する16進値へ簡単に変換することができるだけでなく、逆方向への変換、つまり16進数ストリングから文字への変換も行えます。さらに、これらのAPIは文字データ限定というわけではなく、どのようなメモリーの「チャンク」でも動作することができます。つまり、画像やPDFなどをエンコード/デコードするのに使用することができるということです。これについては「別のプロトタイプ」の節で詳述します。

たとえば、「ABCDE」という文字ストリングに対応する16進値を作成する必要があるとします。結果として生じる16進数ストリングは「C1C2C3C4C5」となるはずです。X'C1'はEBCDICにおける「A」の16進値であり、X'C2′は「B」の16進値(C3以下も同様)であるからです。同じように、私の名前(Jon Paris)であれば、「D1969540D7819989A2」に変換されることになります。

これらの変換を可能にする2つのプロシージャーは、16進数ペアから対応する文字表現へ変換する cvtchと、個々の文字を対応する16進数ペアへ変換する cvthcです。これらのプロシージャーについては、他の多くの項目とともに、『 ILE C/C++ for AS/400 MI Library Reference』マニュアルに詳細な説明が記されています。

これらのプロシージャーの名前は、多少、誤解を招きかねないものになっており、また、マニュアルの記述も、どちらがどのような処理を行うかを理解するには慎重に読む必要があります。たとえば、 cvtch という名前であれば、名前からすると「文字を16進数へ変換する」という意味だろうと思われるかもしれません。ところが実際は、正反対のことを行います。IBMによるこのAPIの正式名は「Convert Eight Bit Character to Hex Nibbles(8ビット文字を16進ニブルへ変換)」ですが、この正式名から、実際の動作が逆であることが多少は分かりやすくなるはずです。しかし、これではそれほど明らかにはなりませんね。そうであるなら、正式名は「Convert Eight Bit Character Representation of a Hex Digit to the Corresponding Hex Nibble(16進数字の8ビット文字表現を、対応する16進ニブルへ変換)」であるべきでしょう。もちろん、ニブル(nibble。nybbleとも)が4ビット(半バイト)であることを知っていて初めて、それでつじつまが合います。半バイトが、ニブルです。

まずは、 cvthcのプロトタイプから見てみましょう。

dcl-pr CharToHex ExtProc('cvthc');
   hexResult    Char(65534)  Options(*VarSize);
   charInput    Char(32767)  Options(*VarSize);
   charNibbles  Int(10)  Value;
End-Pr;

ここで注意すべき点があります。

1つには、実際のAPI名を使用する代わりに、このプロトタイプに CharToHex という名前を付けました。そうすることにより、いくぶん紛らわしい名前のせいで生じる誤解を避けることができ、このAPIが行う処理が理解しやすくなります。

2つには、入力パラメーター(charInput)と出力パラメーター(hexResult)の両方を、宣言された最大値より小さいフィールドを使用できるように、Options(*VarSize)を指定して定義しています。入力文字ごとに2つの出力文字が生成されるため、出力パラメーターは、必ず入力パラメーターの2倍の長さでなければならないことに注意してください。また、ほぼ好きな長さにすることができる点にも注目してください。

3つ目のパラメーターには、ニブル(ハーフバイト)単位での入力フィールドの長さが格納されます(入力フィールドの長さに2を掛けたもの)。

このプロセスの処理を見てみましょう。

dcl-s  textString  Char(24);
dcl-s  hexString   Char(48);
dcl-s  length      int(10);
Dsply  ('Enter Max 24 char string') ' ' textString;
// Calculate the number of hex nibbles in the input
length = %Size(textString) * 2;
CharToHex ( hexString : textString : length );
Dsply 'Hex result is:';
Dsply hexstring;
  • 「dcl-s」で始まる最初の 3 行は、使用される3つの変数を定義します。
  • 「Dsply ('Enter Max 24 char string') ' ' textString; 」は最大24文字の入力ストリングを要求します。
  • 「length = %Size(textString) * 2; 」は入力ストリングのニブル(ハーフバイト)単位での長さを計算します。
  • 「CharToHex ( hexString : textString : length ); 」は続けて CharToHex()を呼び出し、変換を実行して結果を表示します。長さを別に計算するのではなく、長さパラメーターとして%Size(textString) * 2を指定する(lengthフィールドを使用しない)ようにしてもよかったのではと思っておられるとしたら、まったくその通りです(その場合、次のようになります)。
CharToHex ( hexString : textString : %Size(textString) * 2 );

逆のプロシージャーであるHexToChar(cvtch)のプロトタイプは、お察しの通り、基本的には同じです。前述のプロトタイプと同様に、結果は1つ目のパラメーターに入れられ、入力は2つ目のパラメーターから取られます。以下の例で、プロトタイプの下にある呼び出しをご覧ください。入力の長さは、3つ目のパラメーターで渡されますが、これが実際のバイト単位での長さであり、2倍する必要がない点に注目してください。

dcl-pr HexToChar ExtProc('cvtch');
     charResult  Char(32767)  Options(*VarSize);
     hexInput    Char(65534)  Options(*VarSize);
     hexLength   Int(10)  Value;
End-Pr;
...
HexToChar ( textString : hexString : %Size(hexString));

別のプロトタイプ

CスタイルのAPIのプロトタイプを使用したことがある方は、前述のプロトタイプに驚かれたかもしれません。ドキュメントを読んで予期していたのは、次のようなものだったのかもしれません。

dcl-pr cvtch ExtProc('cvtch');     
   charResult  pointer  Value;     
   hexInput    pointer  Value;     
   hexLength   Int(10)  Value;     
End-Pr;                            

確かに、これは技術的に正確なAPI定義の表現です。それでは、どうして私はこれとは異なるプロトタイプを使用したのでしょうか。また、それらが両方とも機能するのはどうしてでしょうか。

メインの例でこれとは異なるプロトタイプを使用することにしたのは、単に、呼び出しがより「RPGっぽく」なると思われたからです。どういうことかと言うと、このバージョンの呼び出しがどのようになるか見てみれば、一目瞭然だと思います。

HexToChar ( %Addr(textString) : %Addr(hexString) : %Size(hexString));

ご覧の通り、単にフィールドを参照するだけではなく、それぞれのパラメーターのアドレスを渡すことが必要になります。それでは、私の最初のバージョンはどうして機能するのでしょうか。簡単に言えば、変数を参照で渡す場合(最初の例でのやり方)は、呼び出されたルーチンにその変数へのポインターが渡されます。それは、プロトタイプがパラメーターを値で渡されるポインターとして定義しているときに%Addr()を渡す際に行っているのとまったく同じことです。つまり、変数を参照で渡すことは、その変数へのポインターを値で渡すのと同じということです。

それでは、この2つ目のバージョンを使用することはあるのでしょうか。はい。2つ目のバージョンは、動的ストレージを使用している場合に使用します。これはそのストレージへのポインターがすでにあるからです。また、16Mbを超えるテラスペース内のフィールドを処理するときにも使用します。そのようなフィールドは直接RPGで定義するには大き過ぎるためです。

終わりに

前述したMIリファレンスを見てみると、RPGプログラマーが利用できる低レベルMI APIが、他にも数多くあることがお分かりだと思います。それらのAPIを使用する必要があり、何か問題が生じた場合は、お知らせください。

あわせて読みたい記事

PAGE TOP