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
の定義(詳細は後述の LPSTR
と LPTSTR
セクションを参照)を有効にするには、プロジェクトのプロパティ内にある「文字セット」で「Unicode 文字セットを使用する」を選択します。
LPSTR
と LPTSTR
†Windowsプログラミングをしているならよく目にする LPSTR
型や LPCSTR
型は、Win32ヘッダ内で次のように定義されています。
実際に次のように書かれてあるわけではありませんが、意味的には同じです。
1 2 |
|
つまり、 LPSTR
と書かれている部分を char*
と読み替えても同じということです。
まぁ、これくらいはさすがに常識ですよね。
では、初心〜中級のWindowsプログラマが LPSTR
型と同義であるかのように扱っている LPTSTR
型や LPCTSTR
型はどのように定義されているかというと、次のようになります。
1 2 |
|
さて、突如現れた TCHAR
型ですが、もちろんこいつにも基となる定義があります。
それは次のようになっています。
1 2 3 4 5 |
|
つまり TCHAR
型とは、コンパイル時にプリプロセッサで UNICODE
が定義されていれば WCHAR
型、定義されていなければ char
型と同じということです。
今度は WCHAR
型なんてのが出てきたのですが、お察しの通り、こいつはUnicode文字型です。
ワイド文字型と言った方がMSDNライブラリ等で見覚えがあるでしょうか。
定義は次のような感じです。
なお、 wchar_t
型は short
型と同じ2バイトの整数型です。
1
2
3
| - ! |
|
つまり、 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 |
|
ご覧のように、ちゃんと LPCTSTR
型、すなわち const TCHAR*
型が使われています。
少々話が逸れますが、実は実際に上のようなプロトタイプが存在するわけではないのです。
MessageBox
は、通常は次のようにマクロとして定義されています。
1 2 3 4 5 |
|
この MessageBoxW
や MessageBoxA
が実際の関数で、それぞれ次のように定義されています。
1 2 |
|
なぜこんな面倒な作りになっているかという話は、後述のDLL作成時の注意セクションで述べています。
話を戻して、このようにWin32 APIはちゃんとUnicodeに対応してくれているわけですが、ここではたと疑問に思うことがあります。
Windowsプログラムを一から書いている人ならば必ず書くあの関数…。
そう、 WinMain
関数って、引数に LPSTR
型があったのでは?
1 2 |
|
ありますよねぇ。
まぁこれはキニシナイ!…なんて都合よくいくはずもなく、Unicode版プログラムでは引数 lpCmdLine
から内容を読み取れません。
まぁこの引数を使うことはあまりないのですが、せっかくUnicodeに対応させようというのにしょっぱなから LPSTR
型があるのはなんだかシャクですよねぇ。
ではどうするかというと、実はUnicode版の WinMain
関数である wWinMain
関数があります。
1 2 |
|
Unicode版プログラムではこちらを WinMain
関数の代わりに記述すればよいわけです。これにて一件落着。
…って、そうじゃないですよね。
これじゃあNT向けと9x向けでソースコードを書き換えなきゃならなくなります。
まぁ、自分で次のように書いてもいいんですが、もっとスマートに解決する手段があります。
それが _tWinMain
関数(というかマクロ)です。
ご察しの通り、次のように定義されています。
1 2 3 4 5 |
|
このマクロを使うには、tchar.hをインクルードする必要があります。
tchar.hは、 TCHAR
型に関連した便利なマクロ群が定義してあるヘッダファイルです。
このマクロを使えば、Windowsプログラムのメインエントリポイントは次のように書けます。
これでUnicode版でも非Unicode版でも問題のないメインエントリポイントになりました。
_T
マクロ †Unicodeに対応させようとするとぶち当たる壁の一つが文字リテラル及び文字列リテラルです。
「リテラルってなんぞや?」という人のために、非Unicode版における文字リテラルと文字列リテラルを次に示します。
リテラルとはこのように、左辺になれない値のことです。
さてここで、「リテラルの何が問題なの?」という人は、よく考えてみて下さい。
文字リテラルにも文字列リテラルにも、数値リテラルである 100
などにも、C/C++言語である以上はすべからく「型」が存在します。
代表的なリテラルの型を以下に示します(というかほぼ全てですが)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | - ! - ! - ! - ! - ! - ! |
|
では、 TCHAR
型や LPTSTR
型にリテラルを代入したい時はどうすればよいのでしょうか。
あるいは、 MessageBox
関数などに直接リテラルを渡したい場合もどうすればよいのでしょうか。
実は、Unicode文字やUnicode文字列のリテラル値を記述する方法は存在します。
このように、文字リテラルや文字列リテラルの頭に L
を付けることでUnicodeになります。
というわけで、 TCHAR
型に代入するリテラルは、この二種類を切り替えたものになるわけですが、毎回次のように書いていたのでは気が遠くなります。
1
2
3
4
5
6
| - ! |
|
そこで、tchar.hに便利なマクロが定義されています。
それが _T
マクロ、あるいは TEXT
マクロです。
このマクロを用いると、上述のメッセージボックスの例は次のように記述できます。
1
2
3
| - ! |
|
もちろん次のように代入もできます。
さて、この _T
マクロですが、実際にtchar.hの中ではどのように定義されているかというと、次のようになっています。
1 2 3 4 5 |
|
##
はトークン連結演算子です。
これによって、 L
と、 x
に指定した非Unicodeの文字リテラルや文字列リテラルがくっつけられ、Unicodeのリテラルになります。
さて、このようにとっても便利な TCHAR
型、さっそく自作関数の引数にも使いまくりたいところです。
でもちょっと待って下さい。
あなたが今作っているプログラムは、もしかしてプログラマ向けに配布する予定のDLLだったりしますか?
だとしたら、 TCHAR
型及びそこから派生した型を使ってはいけません。
TCHAR
型や上述の _T
マクロは、プリプロセッサで UNICODE
が定義されているかいないかで内容が変わります。
これは言い換えれば、プリプロセッサ処理を行うコンパイルの時点で、既に型は決定してしまっているということです。
これがどういう意味かわかりますか?
簡単な例を挙げましょう。
例えば、次のような関数を持つDLLを配布しようと考えたとします。
当然ながら、ヘッダファイルを用意して、そこには次のようにプロトタイプ宣言を記述しますね。
1 |
|
さて、そしてこのソースコードをいつものように UNICODE
を定義せずにコンパイルしてDLLを作成しました。
そしてDLL本体にヘッダファイルを添付して配布し、利用したユーザからの喜びの声にあなたは感無量です。
しかし、その声の中に、「 UNICODE
を定義した環境で使うとうまく結果が出てこない」という声が混ざります。
これは当然です。
だって、 UNICODE
を定義していない環境でコンパイルしたDLLなんですから。
コンパイラがプリプロセッサ処理を終えた時点で、上述の関数はコンパイラによって次のように解釈されます。
そして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 16 17 18 19 20 21 22 23 | - ! - ! - ! |
|
これでコンパイルすれば、両方の環境に対応することができます。
作成者側は大変になりますが、より完璧なものを目指すのがプログラマってもんですよ。うん。
文字列の操作をする際に、 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といった(テンプレート)クラスライブラリを積極的に利用していくべきだと思います。