RPG

200. iconv によるUnicode 変換

ユニコード (UTF-8) の使用が一般的になってきた現在、IBM の EBCDIC コードを
ユニコードに変換する必要のある機会も多くなってくるはずだ。
コード変換API と言えば QDCXLATE が良く知られているが QDCXLATE
EBCDIC/ASCII 間の変換しかサポートしていない。
すべての言語コードのあいだの変換を行なうAPI は、ここで紹介する iconv である。
iconv 関数は UNIX の API としても良く知られているが IBM System i では
独自の使用方法があるので、それをここで紹介する。

iconv による言語コードの変換には次の3段階のステップを必要とする。

(1) 変換ハンドルの作成

どのCCSID から どのCCSID への変換であることを宣言して変換ハンドルを生成する。

(2) 実際のコード変換

iconv 関数を使って変換ハンドルを指定して実際に変換を実行する。

(3) ハンドルのクローズ

使用済みの変換ハンドルをクローズする。

このステップを経ることが必要であることがIBM API マニュアルには明確に書かれていないので
当初は苦労すると思われる。
また QDCXLATE と iconv のちがいも API マニュアルには解説されていない。
ユニコードへの変換には iconv の利用が必要であるとも書かれていない。
実際はユニコードとの変換には iconv を利用するしかないのである。

RPG による iconv の使用方法は結構、難しいものとなるが、米国サイトを含めて RPG による
iconv の使用サンプルはどこにも紹介されていないのでここで初めて紹介することにした。
日本語を扱う我々にとってはこれから Unicode への変換は必須となってくる時代がすぐそこに
やってきていると言える。
恐らくここで紹介するサンプル・ソースがないと独力で iconv を利用するRPG を作成するのには
骨が折れるはずだ。
というのは iconv のパラメータはポインターを多用しており、かつAPI の仕様のとおりに
記述してもなかなか正常には動作してくれない。
しかも iconv のパラメータはポインターのポインターというRPG のプログラマーにとっては
ややこしくて理解できないような構造を必要としている。
RPG でもポインターを使う機会は少ないはずなので、それも含めて解説していくことになる。

■ QtqIconvOpen : コード変換の割り振り API

構文
iconv_t QtqIconvOpen(QtqCode_T* tocode,  QtqCode_T* fromcode)

QtqIconvOpen() 関数は fromcode で定義されているCCSID から tocode として定義されている
CCSID へ文字コードを変換するための初期化を行なって変換識別子 iconv_t を戻す。
API には iconv_open という同じ機能の API も用意されていますが iconv_open 関数は正しく動作しない。
この QtqIconvOpen 関数を使用すること。

QtqCode_T の形式

オフセットタイプフィールド
10進数16進数
00BINARY(4)CCSID
44BINARY(4)変換代替
88BINARY(4)代用代替
12CBINARY(4)シフト状態代替
1610BINARY(4)入力の長さオプション
2014BINARY(4)混合データのエラー・オプション
2418CHAR(8)予約済み

■ iconv() : コード変換 API

構文
size_t  iconv(iconv_t cd,  char **inbuf,  size_t *inbytesleft,
char** outbuf,  size_t  *outbytesleft)

iconv() 関数は入力文字列 : inbuf の値を cd として与えられた変換識別子に基づいて
ある CCSID から別の CCSID へ変換して変換結果の文字列を outbuf に入れて
変換した文字数を size_t として戻す。
ただし 入力inbuf, 出力 outbuf ともにポインターのポインターを定義する仕様になっている
ことに注意すること。

■ iconv_close(): コード変換割り振り解放 API

構文
int  iconv_close(iconv_t cd)

iconv_close() 関数は 変換識別子 cd をクローズする。

【サンプル : TESTICONV】
0001.00 H DATEDIT(*YMD/)
0002.00 F********** TESTICONV : iconv による Unicode 変換テスト ***************
0003.00 F*
0004.00 F*   CRTRPGMOD QTEMP/TESTICONV SRCFILE(MYSRCLIB/QRPGLESRC)
0005.00 F*        AUT(*ALL)
0006.00 F*   CRTPGM MYLIB/TESTICONV MODULE(QTEMP/TESTICONV)
0007.00 F*        BNDSRVPGM(QSYS/QTQICONV) ACRGRP(*NEW)
0008.00 F*
0009.00 F**********************************************************************
0010.00
0011.00 D QTQ_ICONV_OPEN  PR            52A   EXTPROC('QtqIconvOpen')
0012.00 D  TOCODE                         *   VALUE
0013.00 D  FROMCODE                       *   VALUE
0014.00
0015.00 D ICONV           PR             4B 0 EXTPROC('iconv')
0016.00 D  CD                           52A   VALUE
0017.00 D  INPUT_P                        *   VALUE
0018.00 D  INPLEN                         *   VALUE
0019.00 D  OUTPUT_P                       *   VALUE
0020.00 D  OUTLEN                         *   VALUE
0021.00
0022.00 D ICONV_CLOSE     PR                  EXTPROC('iconv_close')
0023.00 D  CD                           52A   VALUE
0024.00
0025.00 DICONV_T          DS                  QUALIFIED
0026.00 D ICORV                   1      4B 0 INZ(0)
0027.00 D ICOC                    5     52B 0 DIM(00012)
0028.00
0029.00 DQTQ_CODE_T       DS                  QUALIFIED
0030.00 D CCSID                   1      4B 0
0031.00 D CONV                    5      8B 0 INZ(0)
0032.00 D SUBSTI                  9     12B 0 INZ(0)
0033.00 D SUBSTAT                13     16B 0 INZ(0)
0034.00 D INPLENOPT              17     20B 0 INZ(0)
0035.00 D ERROPT                 21     24B 0 INZ(0)
0036.00 D RESERV                 25     32A
0037.00
0038.00 D INPUT           S            512A
0039.00 D INPUT_P         S               *   INZ(%ADDR(INPUT))
0040.00 D INPUT_PP        S               *
0041.00 D INPLEN          S             10I 0
0042.00 D INPL_P          S               *   INZ(%ADDR(INPLEN))
0043.00 D OUTPUT          S            512A
0044.00 D OUTPUT_P        S               *   INZ(%ADDR(OUTPUT))
0045.00 D OUTPUT_PP       S               *
0046.00 D OUTLEN          S             10I 0
0047.00 D OUTL_P          S               *   INZ(%ADDR(OUTLEN))
0048.00 D CD              DS                  LIKEDS(ICONV_T)
0049.00 D CD_P            S               *   INZ(%ADDR(CD))
0050.00 D RES             S              4B 0
0051.00 D FROMCODE        DS                  QUALIFIED
0052.00 D  CCSID                  1      4B 0
0053.00 D  OTHER                  5     32B 0 DIM(00007)
0054.00 D FROMCODE_P      S               *   INZ(%ADDR(FROMCODE))
0055.00
0056.00 D TOCODE          DS                  QUALIFIED
0057.00 D  CCSID                  1      4B 0
0058.00 D  OTHER                  5     32B 0 DIM(00007)
0059.00 D TOCODE_P        S               *   INZ(%ADDR(TOCODE))
0060.00
0061.00 D TRUE            S              4B 0 INZ(0)
0062.00 D FALSE           S              4B 0 INZ(-1)
0063.00
0064.00 C*(1) 変換ハンドル CD を作成 */
0065.00 C                   CLEAR                   FROMCODE
0066.00 C                   CLEAR                   TOCODE
0067.00 C                   EVAL      FROMCODE.CCSID = 5026
0068.00 C                   EVAL      TOCODE.CCSID = 1208
0069.00 C*----------------------------------------------------+
0070.00 C                   EVAL      CD = QTQ_ICONV_OPEN(TOCODE_P:FROMCODE_P)
0071.00 C                   IF        CD.ICORV = FALSE
0072.00 C     'OPEN ERROR'  DSPLY                   ANS               1
0073.00 C                   SETON                                        LR
0074.00 C                   RETURN
0075.00 C                   ELSE
0076.00 C     'OPEN OK'     DSPLY                   ANS               1
0077.00 C                   ENDIF
0078.00 C*----------------------------------------------------+
0079.00
0080.00 C*(2) 変換を実行
0081.00 C                   MOVEL(P)  ' A '        INPUT
0082.00 C                   Z-ADD     6             INPLEN
0083.00 C                   EVAL      OUTLEN = %SIZE(OUTPUT)
0084.00 C                   EVAL      INPUT_PP = %ADDR(INPUT_P)
0085.00 C                   EVAL      OUTPUT_PP = %ADDR(OUTPUT_P)
0086.00 C*----------------------------------------------------+
0087.00 C                   EVAL      RES = ICONV(CD:INPUT_PP:INPL_P:OUTPUT_PP:
0088.00 C                             OUTL_P)
0089.00 C                   IF        RES = FALSE
0090.00 C     'CONV ERROR'  DSPLY                   ANS               1
0091.00 C                   SETON                                        LR
0092.00 C                   RETURN
0093.00 C                   ELSE
0094.00 C     'CVT  OK'     DSPLY                   ANS               1
0095.00 C                   ENDIF
0096.00 C*----------------------------------------------------+
0097.00
0098.00 C*(3) 変換ハンドルをクローズ */
0099.00 C*----------------------------------------------------+
0100.00 C                   CALLP     ICONV_CLOSE(CD)
0101.00 C*----------------------------------------------------+
0102.00 C     'CLOSE OK'    DSPLY                   ANS               1
0103.00 C                   SETON                                        LR
0104.00 C                   RETURN
【解説】

このサンプルは文字「」(CCSID=5026)を UNICODE(CCSID=1208) に iconv を使って変換する
方法を示している。
([注意] CCSID=1399 は UNICODE ではない。UNICODE の漢字は3バイトによって
漢字の1文字を表現する。
CCSID=1399 は EBCDIC なので、やはり漢字は2バイトで表現する。
従ってCCSID=1399 は UNICODE とは何の関係もない。)

最初に変換ハンドル CD

0025.00 DICONV_T          DS                  QUALIFIED
0026.00 D ICORV                   1      4B 0 INZ(0)
0027.00 D ICOC                    5     52B 0 DIM(00012)
           :
0048.00 D CD              DS                  LIKEDS(ICONV_T)

として定義されている。

0064.00 C*(1) 変換ハンドル CD を作成 */
0065.00 C                   CLEAR                   FROMCODE
0066.00 C                   CLEAR                   TOCODE
0067.00 C                   EVAL      FROMCODE.CCSID = 5026
0068.00 C                   EVAL      TOCODE.CCSID = 1208

によって CCSID=5026 から UTF-8(CCSID=1208) への変換であることを宣言してから

0069.00 C*----------------------------------------------------+
0070.00 C                   EVAL      CD = QTQ_ICONV_OPEN(TOCODE_P:FROMCODE_P)
0071.00 C                   IF        CD.ICORV = FALSE
0072.00 C     'OPEN ERROR'  DSPLY                   ANS               1
0073.00 C                   SETON                                        LR
0074.00 C                   RETURN
0075.00 C                   ELSE
0076.00 C     'OPEN OK'     DSPLY                   ANS               1
0077.00 C                   ENDIF
0078.00 C*----------------------------------------------------+

によって変換識別子(ハンドル) CDを取得する。
このハンドル CD を使って

0080.00 C*(2) 変換を実行
0081.00 C                   MOVEL(P)  ' A '        INPUT
0082.00 C                   Z-ADD     6             INPLEN
0083.00 C                   EVAL      OUTLEN = %SIZE(OUTPUT)
0084.00 C                   EVAL      INPUT_PP = %ADDR(INPUT_P)
0085.00 C                   EVAL      OUTPUT_PP = %ADDR(OUTPUT_P)
0086.00 C*----------------------------------------------------+
0087.00 C                   EVAL      RES = ICONV(CD:INPUT_PP:INPL_P:OUTPUT_PP:
0088.00 C                             OUTL_P)

によって 変換を実行すれば実行結果は OUTPUT に収められ変換バイト数は RES である。
変換結果の完了後には

0098.00 C*(3) 変換ハンドルをクローズ */
0099.00 C*----------------------------------------------------+
0100.00 C                   CALLP     ICONV_CLOSE(CD)
0101.00 C*----------------------------------------------------+

によって変換ハンドルもクローズして終了する。

【まとめ】

API: iconv のパラメータはポインターのポインターを使用しているため実行サンプルが
ないと実際にどのように記述すれば動作するのかが、なかなかわかりづらい。

ここで RPG プログラマーのためにポインターを簡単に解説すると
ポインターとは変数の番地(場所)を表す数値であり、すべての変数はあるポインター(番地)
から指定された長さの分だけ定義されている。
例えば、プロシージャー : QtqIconvOpen の定義は

0011.00 D QTQ_ICONV_OPEN  PR            52A   EXTPROC('QtqIconvOpen')
0012.00 D  TOCODE                         *   VALUE
0013.00 D  FROMCODE                       *   VALUE

となっており、TOCODEFROMCODE のタイプ * は、これらの変数がポインターであることを表している。

0038.00 D INPUT           S            512A
0039.00 D INPUT_P         S               *   INZ(%ADDR(INPUT))

とは、変数 INPUT に対して INPUT_P とは INPUT の番地を示すポインターである。

IBM API マニュアルによれば QtqIconvOpen の関数は

QtqIconvOpen(QtqCode_T* tocode,  QtqCode_T* fromcode)

として定義されているが QtqCode_T* tocode のように書かれているのは
パラメータ : tocode はタイプ QtqCode_T のポインターであることを * によって示している。

また、iconv のパラメータで char **inbuf** の記述は inbufinbuf の番地を
示すポインターのポインターであることを示している。
従って

0038.00 D INPUT           S            512A
0039.00 D INPUT_P         S               *   INZ(%ADDR(INPUT))
0040.00 D INPUT_PP        S               *
:
0084.00 C                   EVAL      INPUT_PP = %ADDR(INPUT_P)

のようにして INPUT_PP は変数 INPUT のポインター(INPUT_P) のポインターとして
算出してから、これをパラメータとして実行しているのである。
RPG プログラマーはポインターのポインターでは面食らってしまうかも知れないが
iconv を確実に動作させて Unicode に変換できるRPGソースであることは確かである。

さて、それはともかく UNICODE がこれほど急に普及してきた時代背景を考えると EBCDIC/UNICODE の
変換は重要であり必須であると言える。
また国際言語化にとっても iconv の使い方をマスターしておくべきであろう。

最後にすべての CCSID の間を iconv だけですべて変換可能か? というとそういうわけでは
ないことを知ってして欲しい。
異なる CCSID の間の変換テーブルはべて System i に導入されているが、なかには
テーブルが用意されていないものがあり、これは QtqIconvOpen でエラーとなって
変換することはできない。
ただし QDCXLATE に比べて iconv のほうが実行速度も速く、変換精度も高いように思える。