
|
(*1) 「Unicode」と「UTF-8」は本来は別物です。 というかUTF-8はUnicodeの一種で、Tcl/Tkで使われるのはUTF-8です。 また実際に、JavaのUTF-8とTcl/TkのUTF-8は完全に互換性があります。
●エクスターナルでそのまま返すサンプル
/* サンプル421(共有ライブラリ) */
#include "tcl.h"
#include <time.h>
#include <string.h>
static int greetRawObjCmd(ClientData data, Tcl_Interp* interp,
int objc, Tcl_Obj* CONST objv[]){
time_t clockseconds;
struct tm* tmtm;
char* g;
Tcl_Obj* outobj;
if(objc > 2){
Tcl_WrongNumArgs(interp, 1, objv, "clockseconds");
return TCL_ERROR;
}
else if(objc == 1){
clockseconds = time(NULL);
}
else if(Tcl_GetLongFromObj(interp, objv[1], & clockseconds) == TCL_ERROR){
return TCL_ERROR;
}
tmtm = localtime(& clockseconds);
if(tmtm->tm_hour >= 5 && tmtm->tm_hour < 10){
g = "おはようさん";
} else if(tmtm->tm_hour >= 10 && tmtm->tm_hour < 18){
g = "ちわっす";
} else if(tmtm->tm_hour >= 18){
g = "こんばんは";
} else {
g = "眠くないですか?";
}
#if 0
/* 8.1以降では文字化けしてしまいます。*/
outobj = Tcl_NewStringObj(g, -1);
#else
outobj = Tcl_NewByteArrayObj(g, strlen(g));
#endif
Tcl_SetObjResult(interp, outobj);
return TCL_OK;
}
DLLEXPORT int Greet_Init(Tcl_Interp* interp){
#ifdef USE_TCL_STUBS
Tcl_InitStubs(interp, "8.1", 0);
#endif
Tcl_CreateObjCommand(interp, "greet_raw", greetRawObjCmd, NULL, NULL);
return Tcl_PkgProvide(interp, "greet", "0.10");
}
/* end. */
このコマンドの使い方ですが、 load libgreet[info sharedlibextension] set a [greet_raw] puts "RAW: $a" set a [greet_raw [clock scan "1999-02-06 07:30"]] puts "RAW: $a"このように使うと化け化けになってしまいます。 このプログラムは日本語の「おはようさん」 などを単なる12バイトの並びとして返すので、 Tcl世界のUTF-8の文字列に変更するために
load libgreet[info sharedlibextension]
set a [encoding convertfrom cp932 [greet_raw]]
puts "RAW=>encoding: $a"
set a [encoding convertfrom cp932 \
[greet_raw [clock scan "1999-02-06 07:30"]] \
]
puts "RAW=>encoding: $a"
このようにする必要があります。
さてプログラムに戻って重大な注意ですが、 エクスターナルで書かれた日本語の文字列を絶対にTcl_NewStringObj でTclオブジェクトに変換したり、Tcl_SetResultでそのまま返したりしてはいけません。 これらのAPIを使うと、エクスターナルで書かれているにも関わらず、 Tcl処理系はUTF-8の文字列と解釈するので、 Tcl世界では…ありゃりゃ? RAW=>encoding: こんb?は RAW=>encoding: お?B、さんぎゃははは。このように化け化けになってしまいます。 さらにもうひとつ重大な注意ですが、 Tcl_NewStringObjの第2引数は、 「-1」を指定すれば自動的に文字列の長さを計算してくれますが、 Tcl_NewByteArrayObjの第2引数に-1を渡してはいけません。 エラーにもならずにSIGSEGVで落ちてしまうので、注意が必要です。
●C言語中でUTF-8に変換するサンプル
/* サンプル422(共有ライブラリ) */
#include "tcl.h"
#include <time.h>
#include <string.h>
#define BUFSIZE 1024
static int greetUtf8aObjCmd(ClientData data, Tcl_Interp* interp,
int objc, Tcl_Obj* CONST objv[]){
time_t clockseconds;
struct tm* tmtm;
char* g;
char* thisSourceEncoding = "cp932"; /* このCプログラムで使う日本語 */
Tcl_Encoding e;
Tcl_Obj* outobj;
int flags = (TCL_ENCODING_START | TCL_ENCODING_END |
TCL_ENCODING_STOPONERROR);
Tcl_EncodingState statebuf;
int srcReadCount = 0, destWroteCount = 0, destCharsCount = 0;
int r;
char destbuf[BUFSIZE];
int destlen = BUFSIZE;
if(objc > 2){
Tcl_WrongNumArgs(interp, 1, objv, "clockseconds");
return TCL_ERROR;
}
else if(objc == 1){
clockseconds = time(NULL);
}
else if(Tcl_GetLongFromObj(interp, objv[1], & clockseconds) == TCL_ERROR){
return TCL_ERROR;
}
tmtm = localtime(& clockseconds);
if(tmtm->tm_hour >= 5 && tmtm->tm_hour < 10){
g = "おはようさん";
} else if(tmtm->tm_hour >= 10 && tmtm->tm_hour < 18){
g = "ちわっす";
} else if(tmtm->tm_hour >= 18){
g = "こんばんは";
} else {
g = "眠くないですか?";
}
if((e = Tcl_GetEncoding(interp, thisSourceEncoding)) == NULL){
Tcl_AppendResult(interp, "unrecognizable encoding name:",
thisSourceEncoding, NULL);
return TCL_ERROR;
}
r = Tcl_ExternalToUtf(interp, e, g, strlen(g), flags,
& statebuf, destbuf, destlen,
& srcReadCount, & destWroteCount, & destCharsCount);
if(r != TCL_OK){
Tcl_AppendResult(interp, "error occurred during encoding conversion to ",
thisSourceEncoding, NULL);
return TCL_ERROR;
}
outobj = Tcl_NewStringObj(destbuf, destWroteCount);
Tcl_SetObjResult(interp, outobj);
return TCL_OK;
}
DLLEXPORT int Greet_Init(Tcl_Interp* interp){
#ifdef USE_TCL_STUBS
Tcl_InitStubs(interp, "8.1", 0);
#endif
Tcl_CreateObjCommand(interp, "greet_utf8a", greetUtf8aObjCmd, NULL, NULL);
return Tcl_PkgProvide(interp, "greet", "0.10");
}
/* end. */
今度のコマンド「greet_utf8a」は先ほどの「greet_raw」 と同じ機能ですが、encodingコマンドを使う必要がなくお手軽です。 load libgreet[info sharedlibextension] set a [greet_utf8a] puts "UTF8: $a" set a [greet_utf8a [clock scan "1999-02-06 07:30"]] puts "UTF8: $a"Tcl_GetEncodingは、"shiftjis"などの エンコーディング名(エクスターナル)から対応する内部表現 (Tcl_Encoding型の構造体)を取得するAPIです。 エクスターナルからUTF-8に変換するにはTcl_ExternalToUtf を、UTF-8からエクスターナルに変換するには Tcl_UtfToExternalを使います。 これらのAPIは豪勢にも11個の引数をとりますが、 今回は対象文字列全体を一発で変換する一番簡単な例ということで、 第5引数はプログラムの通り記号定数の論理和で、 第6引数は受け取るだけで何にも使っていません。 第7引数には変換結果を格納するデータ領域をあらかじめ確保しておき、 いくら確保したかを第8引数で渡します。 これは、1文字が2バイトで表される日本語世界では、 変換元の2倍も用意しておけば十分でしょう。 9番目の引数には変換元から読んだバイト数が、 10番目の引数には変換先に書きこんだバイト数が、 最後の引数には変換先のデータが変換先の文字コードで何「文字」 に相当するかがそれぞれ書きこまれます。このうち10番目の引数は大事ですが、 他の2つは無視しても構いません。
|
|
set r [stringsfile "コピー.jpg"]のようなファイル名を指定すると、 あっても存在しないというエラーになってしまいます。 そこで、次のように直します。
/* サンプル423(main関数をもたない共有ライブラリ) */
#include <stdio.h>
#include <ctype.h>
#include <sys/stat.h>
#include "tcl.h"
#define MAXLEN 1024
static char ereason[1024];
#define RERROR \
{ Tcl_SetResult(interp,ereason,TCL_STATIC); return TCL_ERROR; }
static int getsize(char* filename){
struct stat sbuf;
if(stat(filename, & sbuf) < 0) return -1;
return sbuf.st_size;
}
static int stringsfileHandleProc(ClientData clientData, Tcl_Interp* interp,
int argc, char* argv[]){
FILE* fp;
int i, sz, len;
char* p, * q, * filebuf;
char buf[MAXLEN];
Tcl_DString ds, * dsp;
Tcl_DString dsfilename;
char* filename;
dsp = & ds;
if(argc == 1){
sprintf(ereason, "too few arguments, usage: %s filename", argv[0]);
RERROR;
}
#if 0
filename = argv[1];
#else /* (あ) */
filename = Tcl_UtfToExternalDString(NULL, argv[1], -1, & dsfilename);
#endif
if((sz = getsize(filename)) == -1){
sprintf(ereason, "cannot stat file %s", filename);
Tcl_DStringFree(& dsfilename); /* (い) */
RERROR;
}
if((fp = fopen(filename, "rb")) == NULL){
sprintf(ereason, "cannot open %s", filename);
Tcl_DStringFree(& dsfilename); /* (い) */
RERROR;
}
if((filebuf = (char* )Tcl_Alloc(sz)) == NULL){
strcpy(ereason, "memory exhausted"); fclose(fp);
Tcl_DStringFree(& dsfilename); /* (い) */
RERROR;
}
if((len = fread(filebuf, 1, sz, fp)) != sz){
sprintf(ereason, "problems occurred in reading from %s", filename);
Tcl_Free(filebuf); fclose(fp);
Tcl_DStringFree(& dsfilename); /* (い) */
RERROR;
}
fclose(fp);
Tcl_DStringInit(dsp);
for(p=filebuf, i=0; i<len; ){
if(isgraph(*p)){
int clen;
for(clen=0,q=buf; clen<MAXLEN&&isgraph(*p); clen++) *q++ = *p++;
*q = '\0'; p++; i+=clen+1;
if(clen > 5) Tcl_DStringAppendElement(dsp, buf);
}
else{
p++; i++;
}
}
Tcl_DStringFree(& dsfilename); /* (い) */
Tcl_Free(filebuf);
Tcl_DStringResult(interp, dsp);
Tcl_DStringFree(dsp);
return TCL_OK;
}
DLLEXPORT int Stringsfile_Init(Tcl_Interp* interp){
Tcl_CreateCommand(interp, "stringsfile",
stringsfileHandleProc, NULL, NULL);
return TCL_OK;
}
/* end. */
(あ)と(い)の部分が変わっています。
Tcl_UtfToExternalDStringは、
先の引数を11個ももつTcl_UtfToExternal
よりも簡単にUTF-8からエクスターナルに変換する処理をするAPIです。
第1引数はTcl_UtfToExternal
のときと同じく、Tcl_Encoding型の変換先のエンコーディングを表す構造体を渡しますが、
変換先がロケ−ル(言語環境情報)から決められるシステム標準のエンコーディング
(MS-Windows日本語版ならcp932です)の場合はNULLでOKです。
言うまでもないことですが、
MS-Windows日本語版の日本語のファイル名はもちろんシフトJISなので、
ここではNULLを使っています。
第2引数がTclコマンドの引数から渡されるUTF-8の文字列(ここではファイル名)です。
第3引数はその長さをバイト数で渡しますが、-1を渡せばstrlen(文字列)
の値が自動的に入るので、普通は-1でよいでしょう。
最後の引数にはTcl_DString型の変数へのポインタを渡しますが、これは
Tcl_DStringInitで
初期化してある必要はありません。
Tcl_UtfToExternalDStringは、変換した結果をこのDString変数に格納するほか、
それを戻り値としても返すので、普通は戻り値の方を使えばよく、
DStringは特に使う必要はありません。ただし、処理が終わったら忘れずに
Tcl_DStringFreeを使って領域を開放する必要があります。
−UNIXの場合は注意が必要です。 UNIXではアルファベットのファイル名しか使わないのが一般的ですが、 逆にもし作ろうとした場合にはどんな文字コードのファイル名でも作ってしまえるため、対象のファイル名がどの文字コードで書かれているのかを正確に知っておく必要があります。 最後に、この逆に、エクスターナルからUTF-8に変換する API Tcl_ExternalToUtfDStringも使い方はほとんど同じです。
|