プログラミング/小ネタ集/Unicode対応コーディング の変更点


Win32 SDKにある @code{TCHAR}; 型や @code{_T}; マクロを積極的に使いましょうというお話。

#contents

*概要 [#about]

このコンテンツは、C/C++言語でWindowsプログラミングをしていて、かつMFCやATLにある @code{CString}; クラスを使っていない人くらいにしか実益はないかもしれません。~
が、内容的に知っておいて損はないことなので書いておきます。

概要としては、 ''@code{LPTSTR}; 型や @code{TCHAR}; 型について知り、NT系(Unicode環境)と9x系(非Unicode環境)のどちらにも最適化できるソースコードを書こう''というお話です。

@code{TCHAR}; 型を見たことがなくても、 @code{LPTSTR}; 型なら見たことがある人も結構いるでしょう。~
初心〜中級のWindowsプログラマは、大抵は @code{LPTSTR}; 型と @code{LPSTR}; 型の違いを特に意識せずにコードを書いています。~
しかし、この二つの型を混同するのは非常に危険なことです。

まずはこれらの型の定義を説明し、 @code{TCHAR}; 型を用いることでUnicode対応プログラミングができることを示します。~
そして実際にプログラミングをしていく中で疑問になりそうな点について解説していきます。

ちなみに、開発環境はVisual Studio .netを想定しています。~
VS.netで @code{UNICODE}; の定義(詳細は後述の [[@code{LPSTR}; と @code{LPTSTR};>#lpstr]] セクションを参照)を有効にするには、プロジェクトのプロパティ内にある「文字セット」で''「Unicode 文字セットを使用する」''を選択します。

*@code{LPSTR}; と @code{LPTSTR}; [#lpstr]

Windowsプログラミングをしているならよく目にする @code{LPSTR}; 型や @code{LPCSTR}; 型は、Win32ヘッダ内で次のように定義されています。~
実際に次のように書かれてあるわけではありませんが、意味的には同じです。

#code(c){{
typedef char* LPSTR;
typedef const char* LPCSTR;
}}

つまり、 @code{LPSTR}; と書かれている部分を @code{char*}; と読み替えても同じということです。~
まぁ、これくらいはさすがに常識ですよね。

では、初心〜中級のWindowsプログラマが @code{LPSTR}; 型と同義であるかのように扱っている @code{LPTSTR}; 型や @code{LPCTSTR}; 型はどのように定義されているかというと、次のようになります。

#code(c){{
typedef TCHAR* LPTSTR;
typedef const TCHAR* LPCTSTR;
}}

さて、突如現れた @code{TCHAR}; 型ですが、もちろんこいつにも基となる定義があります。~
それは次のようになっています。

#code(c){{
#ifdef UNICODE
    typedef WCHAR TCHAR;
#else
    typedef char TCHAR;
#endif
}}

つまり @code{TCHAR}; 型とは、''コンパイル時にプリプロセッサで @code{UNICODE}; が定義されていれば @code{WCHAR}; 型、定義されていなければ @code{char}; 型と同じ''ということです。

今度は @code{WCHAR}; 型なんてのが出てきたのですが、お察しの通り、こいつは''Unicode文字型''です。~
''ワイド文字型''と言った方がMSDNライブラリ等で見覚えがあるでしょうか。~
定義は次のような感じです。~
なお、 @code{wchar_t}; 型は @code{short}; 型と同じ2バイトの整数型です。

#code(c){{
typedef short WCHAR;
// または
typedef wchar_t WCHAR;
}}

つまり、 ''@code{UNICODE}; が定義されている場合、 @code{LPSTR}; 型と @code{LPTSTR}; 型は全くの別モノ''ということです。~
その場合、 @code{LPTSTR}; 型は @code{LPWSTR}; 型と同義になります。

「じゃあ @code{UNICODE}; を定義しなければいいじゃないか」と言われると、それは確かにそうで、実際にそういう前提で組まれているプログラムも多く出回っています。~
というか多分、そういうプログラムの方が多いでしょう。~
別にUnicodeにしなくてもどのWindowsでも動きますし、むしろUnicodeにすると9x系ではバグが出る可能性すらあります。

しかし、Windows 2000やWindows XPといったNT系は、Unicodeをベースにしています。~
@code{UNICODE}; の定義されていないプログラムをNT系で実行した場合、システムがわざわざ文字コードを内部でUnicodeに変換しています。~
NT系向けに最適化したプログラムを提供したい場合、Unicodeへの対応は必須といえるでしょう。

また、C#など、完全にUnicodeをベースとした言語も出てきました。~
これからはUnicodeプログラムが主体となっていくのは想像に難くありません。~
しかし未だ9x系も残る現在、どちらの系統にも対応したいと思うのがWindowsプログラマの性でしょう。

そこで @code{TCHAR}; 型が活躍します。~
@code{TCHAR}; 型を適切に使ったソースコードは、 @code{UNICODE}; を定義するかしないかで、NT系向けと9x系向けを簡単に切り替えることができます。~
定義してコンパイルしたプログラムはNT系用に最適化され、定義しないでコンパイルしたプログラムは9x系用に最適化されます。

*@code{_tWinMain}; 関数 [#twinmain]

Win32 APIの関数等は、大抵Unicodeにも非Unicodeにも対応しています。~
例えばメッセージボックスを表示する @code{MessageBox}; 関数は、次のように(ヘルプ等では)定義されています。

#code(c){{
int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
}}

ご覧のように、ちゃんと @code{LPCTSTR}; 型、すなわち @code{const TCHAR*}; 型が使われています。

少々話が逸れますが、実は実際に上のようなプロトタイプが存在するわけではないのです。~
@code{MessageBox}; は、通常は次のようにマクロとして定義されています。

#code(c){{
#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif
}}

この @code{MessageBoxW}; や @code{MessageBoxA}; が実際の関数で、それぞれ次のように定義されています。

#code(c){{
int MessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType);
int MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
}}

なぜこんな面倒な作りになっているかという話は、後述の[[DLL作成時の注意>#dll]]セクションで述べています。

話を戻して、このようにWin32 APIはちゃんとUnicodeに対応してくれているわけですが、ここではたと疑問に思うことがあります。~
Windowsプログラムを一から書いている人ならば必ず書くあの関数…。~
そう、 ''@code{WinMain}; 関数''って、引数に @code{LPSTR}; 型があったのでは?

#code(c){{
int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nShowCmd);
}}

ありますよねぇ。

まぁこれは''キニシナイ!''…なんて都合よくいくはずもなく、Unicode版プログラムでは引数 @code{lpCmdLine}; から内容を読み取れません。~
まぁこの引数を使うことはあまりないのですが、せっかくUnicodeに対応させようというのにしょっぱなから @code{LPSTR}; 型があるのはなんだかシャクですよねぇ。

ではどうするかというと、実はUnicode版の @code{WinMain}; 関数である ''@code{wWinMain}; 関数''があります。

#code(c){{
int WINAPI wWinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInst, LPWSTR lpCmdLine, int nShowCmd);
}}

Unicode版プログラムではこちらを @code{WinMain}; 関数の代わりに記述すればよいわけです。これにて一件落着。

…って、そうじゃないですよね。~
これじゃあNT向けと9x向けでソースコードを書き換えなきゃならなくなります。~
まぁ、自分で次のように書いてもいいんですが、もっとスマートに解決する手段があります。

#code(c){{
// こうやって書いてもいいですが…
#ifdef UNICODE
int WINAPI wWinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInst, LPWSTR lpCmdLine, int nShowCmd)
#else
int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nShowCmd)
#endif
{
    // 内容
}
}}

それが ''@code{_tWinMain}; 関数''(というかマクロ)です。~
ご察しの通り、次のように定義されています。

#code(c){{
#ifdef UNICODE
#define _tWinMain wWinMain
#else
#define _tWinMain WinMain
#endif
}}

このマクロを使うには、''tchar.hをインクルード''する必要があります。~
tchar.hは、 @code{TCHAR}; 型に関連した便利なマクロ群が定義してあるヘッダファイルです。

このマクロを使えば、Windowsプログラムのメインエントリポイントは次のように書けます。

#code(c){{
int WINAPI _tWinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, int nShowCmd)
{
    // 内容
}
}}

これでUnicode版でも非Unicode版でも問題のないメインエントリポイントになりました。

*@code{_T}; マクロ [#t-macro]

Unicodeに対応させようとするとぶち当たる壁の一つが文字リテラル及び文字列リテラルです。~
「リテラルってなんぞや?」という人のために、非Unicode版における文字リテラルと文字列リテラルを次に示します。

#code(c){{
// 'A' が文字リテラル
char ch = 'A';

// "こんにちわ" が文字列リテラル
const char* str = "こんにちわ";
}}

リテラルとはこのように、''左辺になれない値''のことです。~
さてここで、「リテラルの何が問題なの?」という人は、よく考えてみて下さい。~
文字リテラルにも文字列リテラルにも、数値リテラルである @code{100}; などにも、C/C++言語である以上はすべからく「型」が存在します。~
代表的なリテラルの型を以下に示します(というかほぼ全てですが)。

#code(c){{
// 'A' は char 型です
char ch = 'A';

// "こんにちわ" は const char* 型です。
const char* str = "こんにちわ";

// 100 は int 型です。
int n = 100;

// 200U は unsigned int 型です。
unsigned int u = 200U;

// 10.5 は double 型です。
double d = 10.5;

// 5.8F は float 型です。
float f = 5.8F;
}}

では、 @code{TCHAR}; 型や @code{LPTSTR}; 型にリテラルを代入したい時はどうすればよいのでしょうか。~
あるいは、 @code{MessageBox}; 関数などに直接リテラルを渡したい場合もどうすればよいのでしょうか。

実は、Unicode文字やUnicode文字列のリテラル値を記述する方法は存在します。

#code(c){{
// L'あ' は WCHAR 型です
WCHAR wch = L'あ';

// L"さようなら" は const WCHAR* 型です
const WCHAR* wstr = L"さようなら";
}}

このように、文字リテラルや文字列リテラルの頭に @code{L}; を付けることでUnicodeになります。

というわけで、 @code{TCHAR}; 型に代入するリテラルは、この二種類を切り替えたものになるわけですが、毎回次のように書いていたのでは気が遠くなります。

#code(c){{
// ちょっとメッセージを表示したいだけなのに…
#ifdef UNICODE
MessageBox(NULL, L"エラー発生", L"エラー", 0);
#else
MessageBox(NULL, "エラー発生", "エラー", 0);
#endif
}}

そこで、tchar.hに便利なマクロが定義されています。~
それが @code{_T}; マクロ、あるいは @code{TEXT}; マクロです。~
このマクロを用いると、上述のメッセージボックスの例は次のように記述できます。

#code(c){{
MessageBox(NULL, _T("エラー発生"), _T("エラー"), 0);
// または
MessageBox(NULL, TEXT("エラー発生"), TEXT("エラー"), 0);
}}

もちろん次のように代入もできます。

#code(c){{
// _T('C') は TCHAR 型です
TCHAR tch = _T('C');

// _T("ごきげんよう") は const TCHAR* 型です
const TCHAR* tstr = _T("ごきげんよう");
}}

さて、この @code{_T}; マクロですが、実際にtchar.hの中ではどのように定義されているかというと、次のようになっています。

#code(c){{
#ifdef UNICODE
#define _T(x) L ## x
#else
#define _T(x) x
#endif
}}

@code{##}; はトークン連結演算子です。~
これによって、 @code{L}; と、 @code{x}; に指定した非Unicodeの文字リテラルや文字列リテラルがくっつけられ、Unicodeのリテラルになります。

*DLL作成時の注意 [#dll]

さて、このようにとっても便利な @code{TCHAR}; 型、さっそく自作関数の引数にも使いまくりたいところです。~
でもちょっと待って下さい。~
あなたが今作っているプログラムは、もしかしてプログラマ向けに配布する予定のDLLだったりしますか? ~
だとしたら、 ''@code{TCHAR}; 型及びそこから派生した型を使ってはいけません''。

@code{TCHAR}; 型や上述の [[@code{_T}; マクロ>#t-macro]]は、プリプロセッサで @code{UNICODE}; が定義されているかいないかで内容が変わります。~
これは言い換えれば、プリプロセッサ処理を行うコンパイルの時点で、''既に型は決定してしまっている''ということです。~
これがどういう意味かわかりますか?

簡単な例を挙げましょう。~
例えば、次のような関数を持つDLLを配布しようと考えたとします。

#code(c){{
void WINAPI SomeFunction(LPCTSTR lpText)
{
    const TCHAR* s = _T("ABCDE");

    // (以下略)
}
}}

当然ながら、ヘッダファイルを用意して、そこには次のようにプロトタイプ宣言を記述しますね。

#code(c){{
void WINAPI SomeFunction(LPCTSTR lpText);
}}

さて、そしてこのソースコードをいつものように @code{UNICODE}; を定義せずにコンパイルしてDLLを作成しました。~
そしてDLL本体にヘッダファイルを添付して配布し、利用したユーザからの喜びの声にあなたは感無量です。

しかし、その声の中に、「 @code{UNICODE}; を定義した環境で使うとうまく結果が出てこない」という声が混ざります。~
これは当然です。~
だって、 @code{UNICODE}; を定義していない環境でコンパイルしたDLLなんですから。

コンパイラがプリプロセッサ処理を終えた時点で、上述の関数はコンパイラによって次のように解釈されます。

#code(c){{
void __stdcall SomeFunction(const char* lpText)
{
    const char* s = "ABCDE";

    // (以下略)
}
}}

そしてDLL内ではこの形で関数が定義されることになります。~
コンパイルをした地点で、 ''@code{TCHAR}; 型という存在はなくなる''のです。~
若干語弊がある気もしますがそういうもんだと思って下さい。

「じゃあNT系用と9x系用のDLLをそれぞれ作って配布すればいい」という結論もありかもしれません。~
ですが、完全にUnicode版と非Unicode版で別れているのであれば、ヘッダファイル内のプロトタイプ宣言に記述した @code{TCHAR}; は完全に浮いた存在となってしまいます。~
@code{TCHAR}; と書かれているにも関わらず、Unicode版DLLでは @code{UNICODE}; を定義しなければならず、非Unicode版DLLでは @code{UNICODE}; を定義してはなりません。

さて、それではUnicodeにも非Unicodeにも対応したDLLを作るにはどうすればよいのかという話ですが、ここでWin32 APIの方法が参考になります。~
すなわち、Unicode版関数と非Unicode版関数の両方を作ってしまえばいいわけです。~
上述の例をこの方法で改善すると、まず関数定義は次のようになります。

#code(c){{
// Unicode版
void WINAPI SomeFunctionW(LPCWSTR lpText)
{
    const WCHAR* s = L"ABCDE";

    // (以下略)
}

// 非Unicode版
void WINAPI SomeFunctionA(LPCSTR lpText)
{
    const char* s = "ABCDE";

    // (以下略)
}
}}

そして、ヘッダファイル内に次のように記述します。

#code(c){{
// プロトタイプ宣言
void WINAPI SomeFunctionW(LPCWSTR lpText);
void WINAPI SomeFunctionA(LPCSTR lpText);

#ifdef __cplusplus

// C++の場合:インライン関数で定義
inline void SomeFunction(LPCTSTR lpText)
{
#ifdef UNICODE
inline void SomeFunction(LPCWSTR lpText) { SomeFunctionW(lpText); }
    SomeFunctionW(lpText);
#else
inline void SomeFunction(LPCSTR lpText) { SomeFunctionA(lpText); }
    SomeFunctionA(lpText);
#endif // UNICODE
}

#else

// Cの場合:マクロで定義
#ifdef UNICODE
#define SomeFunction(t) SomeFunctionW(t)
#define SomeFunction SomeFunctionW
#else
#define SomeFunction(t) SomeFunctionA(t)
#define SomeFunction SomeFunctionA
#endif // UNICODE

#endif // __cplusplus
}}

これでコンパイルすれば、両方の環境に対応することができます。~
作成者側は大変になりますが、より完璧なものを目指すのがプログラマってもんですよ。うん。

*その他細かいこと [#etc]

**文字列操作関数群 [#lstr]

文字列の操作をする際に、 @code{strcpy}; 関数や @code{strcmp}; 関数といった文字列操作関数群は非常に重要です。~
では、これらの関数の @code{TCHAR}; 用マクロはないのかといえばもちろんそんなことはなく、当然ながらtchar.hで定義されています。

@code{TCHAR}; 用の文字列操作マクロは、 @code{strcpy}; 関数に対して @code{_tcscpy}; マクロ、 @code{strcmp}; 関数に対して @code{_tcscmp}; マクロというように、strの代わりに_tcsが頭に付いたような名称で定義されています。~
MSDNライブラリをインストールしてあるならば、「_t」をキーワードとして入力すると、文字列操作に限らず色々と出てくると思います。~
例えば @code{fopen}; 関数に対して @code{_tfopen}; マクロなんかもありますね。

また、Win32 API関数でも、 @code{lstrcpy}; 関数や @code{lstrcmp}; 関数など、頭にlを付け加えた名称のものがいくつか定義されています。

**@code{CString}; クラス [#cstring]

[[冒頭の概要>#about]]でもちょっと話に出しましたが、Windowsプログラマの中には、MFCやATLといったライブラリを用いていて、文字列関連には @code{CString}; クラスを使っているという人も多いと思います。~
もしかすると、このコンテンツの話を読んで、「 @code{CString}; クラスじゃダメかな?」と思われたかもしれません。

しかしご安心を。~
@code{CString}; クラスは、 @code{TCHAR}; 型と同様の方法によってしっかりUnicodeに対応しています。~
せっかくのC++言語なのですから、Windowsプログラムの仕組み(イベントドリブンなど)についてしっかり理解した後は、MFCやATL/WTLといった(テンプレート)クラスライブラリを積極的に利用していくべきだと思います。