Home / プログラミング / 小ネタ集 / Unicode対応コーディング
Unicode対応コーディング

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

概要

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

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

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

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

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

LPSTRLPTSTR

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

すべて開くすべて閉じる
  1
  2
 
 
typedef char* LPSTR;
typedef const char* LPCSTR;

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

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

すべて開くすべて閉じる
  1
  2
 
 
typedef TCHAR* LPTSTR;
typedef const TCHAR* LPCTSTR;

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
 
 
 
 
 
#ifdef UNICODE
    typedef WCHAR TCHAR;
#else
    typedef char TCHAR;
#endif

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

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

すべて開くすべて閉じる
  1
  2
  3
 
-
!
typedef short WCHAR;
// または
typedef wchar_t WCHAR;

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

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

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

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

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

_tWinMain 関数

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

すべて開くすべて閉じる
  1
 
int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);

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

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
 
 
 
 
 
#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif

この MessageBoxWMessageBoxA が実際の関数で、それぞれ次のように定義されています。

すべて開くすべて閉じる
  1
  2
 
 
int MessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType);
int MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

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

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

すべて開くすべて閉じる
  1
  2
 
 
int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nShowCmd);

ありますよねぇ。

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

ではどうするかというと、実はUnicode版の WinMain 関数である wWinMain 関数があります。

すべて開くすべて閉じる
  1
  2
 
 
int WINAPI wWinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInst, LPWSTR lpCmdLine, int nShowCmd);

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

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
-
!
 
 
 
 
 
 
-
-
!
// こうやって書いてもいいですが…
#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
{
    // 内容
}

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
 
 
 
 
 
#ifdef UNICODE
#define _tWinMain wWinMain
#else
#define _tWinMain WinMain
#endif

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

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
 
 
-
-
!
int WINAPI _tWinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, int nShowCmd)
{
    // 内容
}

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

_T マクロ

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
-
!
 
-
!
// 'A' が文字リテラル
char ch = 'A';
 
// "こんにちわ" が文字列リテラル
const char* str = "こんにちわ";

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
-
!
 
-
!
 
-
!
 
-
!
 
-
!
 
-
!
// '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;

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

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
-
!
 
-
!
// L'あ' は WCHAR 型です
WCHAR wch = L'あ';
 
// L"さようなら" は const WCHAR* 型です
const WCHAR* wstr = L"さようなら";

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

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
-
!
 
 
 
 
// ちょっとメッセージを表示したいだけなのに…
#ifdef UNICODE
MessageBox(NULL, L"エラー発生", L"エラー", 0);
#else
MessageBox(NULL, "エラー発生", "エラー", 0);
#endif

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

すべて開くすべて閉じる
  1
  2
  3
 
-
!
MessageBox(NULL, _T("エラー発生"), _T("エラー"), 0);
// または
MessageBox(NULL, TEXT("エラー発生"), TEXT("エラー"), 0);

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
-
!
 
-
!
// _T('C') は TCHAR 型です
TCHAR tch = _T('C');
 
// _T("ごきげんよう") は const TCHAR* 型です
const TCHAR* tstr = _T("ごきげんよう");

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
 
 
 
 
 
#ifdef UNICODE
#define _T(x) L ## x
#else
#define _T(x) x
#endif

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

DLL作成時の注意

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

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

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
 
-
|
|
-
!
void WINAPI SomeFunction(LPCTSTR lpText)
{
    const TCHAR* s = _T("ABCDE");
 
    // (以下略)
}

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

すべて開くすべて閉じる
  1
 
void WINAPI SomeFunction(LPCTSTR lpText);

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

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

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
 
-
|
|
-
!
void __stdcall SomeFunction(const char* lpText)
{
    const char* s = "ABCDE";
 
    // (以下略)
}

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

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

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
-
!
-
|
|
-
!
|
-
!
-
 
 
-
!
// Unicode版
void WINAPI SomeFunctionW(LPCWSTR lpText)
{
    const WCHAR* s = L"ABCDE";
 
    // (以下略)
}
 
// 非Unicode版
void WINAPI SomeFunctionA(LPCSTR lpText)
{
    const char* s = "ABCDE";
 
    // (以下略)
}

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

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
-
!
 
 
 
 
-
!
-
|
|
|
|
|
!
 
 
 
-
!
 
 
 
 
 
 
// プロトタイプ宣言
void WINAPI SomeFunctionW(LPCWSTR lpText);
void WINAPI SomeFunctionA(LPCSTR lpText);
 
#ifdef __cplusplus
 
// C++の場合:インライン関数で定義
inline void SomeFunction(LPCTSTR lpText)
{
#ifdef UNICODE
    SomeFunctionW(lpText);
#else
    SomeFunctionA(lpText);
#endif // UNICODE
}
 
#else
 
// Cの場合:マクロで定義
#ifdef UNICODE
#define SomeFunction SomeFunctionW
#else
#define SomeFunction SomeFunctionA
#endif // UNICODE
 
#endif // __cplusplus

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

その他細かいこと

文字列操作関数群

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

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

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

CString クラス

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

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