CL

78. 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 を利用するしかないのである。

■ 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              PGM
0002.00 /*---------------------------------------------------------*/
0003.00 /*   TESTICONV : iconv によるユニコード変換                */
0004.00 /*                                                         */
0005.00 /*   CRTCLMOD QTEMP/TESTICONV SRCFILE(MYSRCLIB/QCLLESRC)   */
0006.00 /*            AUT(*ALL)                                    */
0007.00 /*   CRTPGM   MYLIB/TESTICONV MODULE(QTEMP/TESTICONV)      */
0008.00 /*            BNDSRVPGM(QSYS/QTQICONV) ACRGRP(*NEW)        */
0009.00 /*            AUT(*ALL)                                    */
0010.00 /*---------------------------------------------------------*/
0011.00              DCL        VAR(&MSG) TYPE(*CHAR) LEN(132)
0012.00              DCL        VAR(&MSGID) TYPE(*CHAR) LEN(7)
0013.00              DCL        VAR(&MSGF) TYPE(*CHAR) LEN(10)
0014.00              DCL        VAR(&MSGFLIB) TYPE(*CHAR) LEN(10)
0015.00              DCL        VAR(&MSGDTA) TYPE(*CHAR) LEN(132)
0016.00              DCL        VAR(&FROMCODE) TYPE(*CHAR) LEN(32)
0017.00              DCL        VAR(&TOCODE) TYPE(*CHAR) LEN(32)
0018.00              DCL        VAR(&BIN4) TYPE(*CHAR) LEN(4)
0019.00              DCL        VAR(&RTN4) TYPE(*INT)
0020.00              DCL        VAR(&CD) TYPE(*CHAR) LEN(52)
0021.00              DCL        VAR(&CD_P) TYPE(*PTR)
0022.00              DCL        VAR(&INPUT) TYPE(*CHAR) LEN(512)
0023.00              DCL        VAR(&INPUT_P) TYPE(*PTR)
0024.00              DCL        VAR(&OUTPUT) TYPE(*CHAR) LEN(512)
0025.00              DCL        VAR(&OUTPUT_P) TYPE(*PTR)
0026.00              DCL        VAR(&IN_BYTE) TYPE(*INT)
0027.00              DCL        VAR(&IN_BYTE_P) TYPE(*PTR)
0028.00              DCL        VAR(&OUT_BYTE) TYPE(*INT)
0029.00              DCL        VAR(&RTN_BYTE) TYPE(*INT)
0030.00              DCL        VAR(&TRUE) TYPE(*INT) VALUE(0)
0031.00              DCL        VAR(&FALSE) TYPE(*INT) VALUE(-1)
0032.00              DCL        VAR(&NULL28) TYPE(*CHAR) LEN(28) +
0033.00                           VALUE(X'00000000000000000000000000000000000+
0034.00                           000000000000000000000')
0035.00              DCL        VAR(&NULL) TYPE(*CHAR) LEN(1) VALUE(X'00')
0036.00              MONMSG     MSGID(CPF0000) EXEC(GOTO CMDLBL(ERROR))
0037.00
0038.00 /*(1) 変換ハンドルを作成 */
0039.00              CHGVAR     VAR(%BIN(&BIN4)) VALUE(5026)
0040.00              CHGVAR     VAR(&FROMCODE) VALUE(&BIN4 *CAT &NULL28)
0041.00              CHGVAR     VAR(%BIN(&BIN4)) VALUE(1208)
0042.00              CHGVAR     VAR(&TOCODE) VALUE(&BIN4 *CAT &NULL28)
0043.00              CHGVAR     VAR(&CD_P) VALUE(%ADDR(&CD))
0044.00              CALLPRC    PRC('QtqIconvOpen') PARM((&TOCODE) +
0045.00                           (&FROMCODE)) RTNVAL(&CD)
0046.00              CHGVAR     VAR(&BIN4) VALUE(%SST(&CD 1 4))
0047.00              CHGVAR     VAR(&RTN4) VALUE(%BIN(&BIN4))
0048.00              IF         COND(&RTN4 *EQ &FALSE) THEN(DO)
0049.00              CHGVAR     VAR(&MSG) VALUE('iconv open error')
0050.00              GOTO       SNDMSG
0051.00              ENDDO
0052.00              SNDPGMMSG  MSG('iconv OPEN.') MSGTYPE(*DIAG)
0053.00
0054.00 /*(2) 変換を実行 */
0055.00              CHGVAR     VAR(&CD_P) VALUE(%ADDR(&CD))
0056.00              CHGVAR     VAR(&INPUT) VALUE('A' *CAT &NULL)
0057.00              CHGVAR     VAR(&INPUT_P) VALUE(%ADDR(&INPUT))
0058.00              CHGVAR     VAR(&IN_BYTE) VALUE(6)
0059.00              CHGVAR     VAR(&IN_BYTE_P) VALUE(%ADDR(&IN_BYTE))
0060.00              CHGVAR     VAR(&OUTPUT_P) VALUE(%ADDR(&OUTPUT))
0061.00              CHGVAR     VAR(&OUT_BYTE) VALUE(512)
0062.00              CALLPRC    PRC('iconv') PARM((&CD *BYVAL) (&INPUT_P) +
0063.00                           (&IN_BYTE) (&OUTPUT_P) (&OUT_BYTE)) +
0064.00                           RTNVAL(&RTN_BYTE)
0065.00              IF         COND(&RTN_BYTE *EQ &FALSE) THEN(DO)
0066.00              CHGVAR     VAR(&MSG) VALUE('iconv cvt error')
0067.00              CALLPRC    PRC('iconv_close') PARM((&CD))
0068.00              GOTO       SNDMSG
0069.00              ENDDO
0070.00              SNDPGMMSG  MSG('iconv CONVERT  SUCCESS') MSGTYPE(*DIAG)
0071.00
0072.00 /*(3) 変換ハンドルをクローズ */
0073.00 CLOSE:
0074.00              CALLPRC    PRC('iconv_close') PARM((&CD *BYVAL))
0075.00              SNDPGMMSG  MSG('iconv API CLOSED.') MSGTYPE(*DIAG)
0076.00              RETURN
0077.00
0078.00  ERROR:      RCVMSG     MSGTYPE(*LAST) RMV(*NO) MSG(&MSG) +
0079.00                           MSGDTA(&MSGDTA) MSGID(&MSGID) MSGF(&MSGF) +
0080.00                           MSGFLIB(&MSGFLIB)
0081.00  SNDMSG:
0082.00              IF         COND(&MSGID *NE ' ') THEN(DO)
0083.00              SNDPGMMSG  MSGID(&MSGID) MSGF(&MSGFLIB/&MSGF) +
0084.00                           MSGDTA(&MSGDTA) MSGTYPE(*ESCAPE)
0085.00              ENDDO
0086.00              ELSE       CMD(DO)
0087.00              SNDPGMMSG  MSG(&MSG) TOMSGQ(*TOPGMQ) MSGTYPE(*DIAG)
0088.00              ENDDO
0089.00              ENDPGM
【解説】

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

最初に「」を EBCDIC で

0056.00              CHGVAR     VAR(&INPUT) VALUE('A' *CAT &NULL) 

としてセットしている。

0039.00              CHGVAR     VAR(%BIN(&BIN4)) VALUE(5026)                           
0040.00              CHGVAR     VAR(&FROMCODE) VALUE(&BIN4 *CAT &NULL28)               
0041.00              CHGVAR     VAR(%BIN(&BIN4)) VALUE(1208)                           
0042.00              CHGVAR     VAR(&TOCODE) VALUE(&BIN4 *CAT &NULL28)  

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

0044.00              CALLPRC    PRC('QtqIconvOpen') PARM((&TOCODE) +                   
0045.00                           (&FROMCODE)) RTNVAL(&CD) 

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

0062.00              CALLPRC    PRC('iconv') PARM((&CD *BYVAL) (&INPUT_P) +              
0063.00                           (&IN_BYTE) (&OUTPUT_P) (&OUT_BYTE)) +                  
0064.00                           RTNVAL(&RTN_BYTE) 

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

0074.00              CALLPRC    PRC('iconv_close') PARM((&CD *BYVAL)) 

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

【まとめ】

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

0020.00              DCL        VAR(&CD) TYPE(*CHAR) LEN(52) 

の記述にあるように変換識別子 &CD が 52バイトであることである。
( 米国サイトのサンプルでは &CD を 32 バイトとして定義していたので、それでは実行時の
エラーとなってしまう。)
これは C言語を使って変換識別子の長さを調べてみて初めて 52バイトであることが
わかるのである。 IBM API 解説書だけでは CL で動作させることはできない。

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

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