日別アーカイブ: 2007年10月15日

_T() と TEXT() の違いやソースの文字コード

非常にいまさらな感じで、とっくに常識かもしれませんが、文字列
リテラルに用いる _T() と TEXT() はどこが違うのか気になったので
調べてみました。

Windows で文字や文字列を扱う場合、TCHAR (_TCHAR) 型を良く用います。
これはコンパイル時のオプションによって char と wchar_t に定義内容
が変わります。
TCHAR 型を使用しておくと同一ソースコードでも比較的容易に文字列の
扱いを切り替えることができます。

特に NT 系の API では、文字列として両方受け付けるよう関数が2セット
用意されていて、混在することが可能です。
9x 系の時代は MBCS による表現が用いられていたので、Local かつ
当時から引き継いでいるプログラムなどはそのまま char 型として扱う
ことができます。

逆に WindowsCE は wchar_t しか受け付けないので、CE とのコード共有を
考えると TCHAR 型への対応は重要でした。
ゲーム機でも Dreamcast では WindowsCE を選択できたので、
WindowsCE を使う場合は WCHAR 等の wide 型が用いられました。

TCHAR と同じように文字列定数や文字定数も切り替える必要があります。
その場合に用いられるのが _T() や TEXT() マクロです。この両者は同じ
意味だといわれています。

例えば _T(“initdata.dds”) とか TEXT(“maze.fx”) など、どちらで
記述しても特に差はありません。

_T() の実際の宣言は VC/include の tchar.h にありました。
定義部分だけ抽出すると下記のようになります。

// tchar.h
#ifdef  _UNICODE
#define __T(x)      L ## x
#else
#define __T(x)      x
#endif

#define _T(x)       __T(x)
#define _TEXT(x)    __T(x)

シンボル _UNICODE の定義によって __T() を切り替え、それを _T() と
_TEXT() に定義しなおしています。2段階定義になっているのは、おそらく
マクロ展開のためにプリプロセッサのパスを最低2回通すためでしょう。

 例えば #define DDSFILE “initdata.dds” と定義した場合
 _T(DDSFILE) だと L”initdata.dds” になりますが
 __T(DDSFILE) の場合は LDDSFILE となります。

TEXT() の定義は VC/PlatformSDK/include の winnt.h の中にありました。

// winnt.h
ifdef UNICODE
#define __TEXT(quote) L##quote      // r_winnt
#else
#define __TEXT(quote) quote         // r_winnt
#endif

#define TEXT(quote) __TEXT(quote)   // r_winnt

定義されている意味は同じです。こちらはシンボル UNICODE の定義に
よって __TEXT() の内容が変わり、それをさらに TEXT() に定義
しなおしています。

_TEXT() は tchar.h だけど TEXT() と __TEXT() は winnt.h など、
双方シンボルが衝突しないように微妙に使い分けられているようです。

_T() と TEXT() はそれぞれ定義している場所が違うので、include して
いるヘッダによっては必ずしも両方使えるとは限らないようです。
また UNICODE、_UNICODE の両方を必ず定義するか、または定義しない
ようにしないと定義内容が異なってしまいます。

ヘッダを調べると、一部のヘッダ (OleDlg.h, MSAcm.h, pdh.h) では
UNICODE と _UNICODE の同期を取るコードが含まれていました。

OleDlg.h
// syncronize UNICODE options
#if defined(_UNICODE) && !defined(UNICODE)
        #define UNICODE
#endif
#if defined(UNICODE) && !defined(_UNICODE)
        #define _UNICODE
#endif

ただしこれらのヘッダは必ず include されるとは限らないので、
結局は VisualStudio が挿入するコマンド行シンボル
/D “UNICODE” /D “_UNICODE” に依存しているようです。

ちなみに Windows の wide character は UNICODE の 16bit エンコー
ディングで、MBCS では ShiftJIS が使われていました。
UTF-8 など、UNICODE でも MBCS の可能性があります。
文字まわりはいつも苦労するところですが、個人的にはプログラム中
でも UTF-8 を使うことが結構あります。

プログラム中で扱う文字コードの他に、ソースコードの文字をどうしようか、
といった悩みもあります。以前は OS の言語によってソースを ShiftJIS
とみなしてしまう仕様があって、0x5c(\) の処理でよく問題が
出ていました。

マニュアルを見ると VisualStudio2005 では、BOM さえあれば UTF-8
でも大丈夫そうです。
MSDN コンパイラおよびリンカでの Unicode のサポート

試してみました。テキストエディタでソースを ShiftJIS で開いて

// 表
 Error

とか書いて保存します。UTF-8 で開くと

// [95]\
 Error

となります。文字としては不正ですがとりあえずやってみます。
bom つきで保存するとコンパイルエラーにはならず、行末の \ が
有効となっていることがわかります。(vim で :se bomb )
bom 無しで保存すると ShiftJIS とみなされ、行末の \ が無視
されてコンパイルエラーになります。(vim で :se nobomb )
きちんと UTF-8 を認識しています。

さらに、UTF-8 かつ BOM 付きで実際にコンパイルされた文字コードを
見てみます。

wchar_t* wstr= L"あい";
char*    str= "あい";

ソースコードは 0xe3 0x81 0x82 0xe3 0x81 0x84 で UTF-8 です。

wstr の表示出力は 0x3042 0x3044 0x0000
str の表示出力は 0x82 0xa0 0x82 0xa2 0x00

結局 UTF-16(UCS-2) と ShiftJIS に変換されていました。
UTF-8 そのままは通らないようです。