Hello, world の解説 ~ 文字列の扱い方

今回は前節に続いて、以下の単純な Windows プログラムについて説明します。 今回は 文字列の扱い方 を説明します。サンプルプログラムで文字列の扱い方に関連するキーワードは、 LPSTR, TEXT です。

くどいようですが、もう一度検討するソースコードを見直しておきましょう。


#include <windows.h>

int WINAPI WinMain ( 
	HINSTANCE hInstance, 
	HINSTANCE hPrevInstance, 
	LPSTR lpCmdLine, 
	int nCmdShow ) {

	MessageBox( 
		NULL, 
		TEXT("Hello, world!"), 
		TEXT("Hello"), 
		MB_OK | MB_ICONINFORMATION );

	return 0;
}

UNICODE ビルドと ANSI ビルド両方に対応するための仕組み

LPSTR は Windows API の文字列体系の中の一部ですから、まずは全体像を説明します。

文字コード、エンコーディングルール等は入門コースの範囲を大きく越えるテーマです。 ここでは全体像を掴むことを目的に簡単に記載します。

Windows はご存知の通り世界中で広く使われている OS です。このため、 英語圏のみならず日本語や中国語等のアルファベット以外の文字を扱う必要があります。 英語だけなら、いくつかの記号等を含めても1文字、1バイトで表現可能です。 しかし漢字、平仮名、カタカナ、あるいはギリシャ文字等を扱う場合は、1バイトでは 足りません。

このため Unicode が導入されました。Unicode は1文字、2バイトで全ての文字を表現します。 C 言語で普通の文字1文字は char 型で表現され、サイズは1バイトです。 一方、Unicode 文字の1文字は wchar_t 型 (unsigned short) で表現され、サイズは2バイトです。 Unicode 文字はワイドキャラクタ (Wide Character) といいます。

char 型の文字列定数は、ご存知の通り二重引用符で囲み "abc" のように書きます。 一方、wchar_t 型のワイドキャラクタ版の文字列定数は、二重引用符の前に L を付けて L"abc" のように書きます。

さて、文字を扱う API のあり方を考えてみましょう。

例えば「文字」を受け取る架空の関数 Foo 関数を考えてみましょう。

文字を char 型として扱いたい場合は、Foo のプロトタイプは次のようになるでしょう。

void Foo( char* lpszText );

そして、それを呼ぶ場合は次のようになるでしょう。

Foo( "Hello!" );

一方、wchar_t 型として Foo を定義したい場合は、Foo のプロトタイプは次のようになるでしょう。

void Foo( wchar_t* lpwszText );

そして、それを呼ぶ場合は次のようになるでしょう。

Foo( L"Hello!" );

Unicode ビルドの場合は、アルファベット1文字を扱うにも2バイト使いますから、 データのサイズが大きくなります。一方、ANSI ビルドの場合は使うメモリ (データサイズ) は一文字1バイトで良い分だけ 小さくなりますが、Unicode が扱えないためシングルバイトの言語固有の文字セットを利用することになります。 これはいわゆる文字化けの原因になります。

このようにそれぞれ一長一短あります。従って、上記例でいえば Foo 関数は状況によって、 Unicode、あるいは ANSI ビルド両方に対応することが望ましいといえます。

そこで、次のように二種類の関数を用意します。

ひとつは、ANSI バージョンの Foo である、void FooA ( char* lpszText)。もうひとつは Unicode 版の Foo である void FooW( wchar_t* lpwszText) です。

そして、マクロ Foo を用意して、コンパイラオプションとして UNICODE が指定されたときに、 Foo を FooW と解釈し、それが渡されないときに FooA と解釈されるようにします。 さらに、文字列も Unicode とそうでない場合で、文字列先頭の L が付く付かないが別れますから、 TEXT マクロなどを用意して、UNICODE のときに L が付くようにします。

Windows SDK の体系で行っていることはおよそこのようなことなのです。

MessageBox 関数は、実は MessageBox マクロであり、UNICODE ビルドのときは MessageBoxW、 そうでないときは MessageBoxA と解釈されるように定義されています。

LPSTR は常に char* と解釈されます。LPWSTR は常に wchar_t* です。 一方、オプションによって変るのは TCHAR のシリーズです。 LPTSTR はオプションによって、char* に解釈されたり wchar_t* に解釈されたりします。

文字列の取り扱いの整理

この節では文字列の扱い方を整理します。

マクロ影響する箇所
_UNICODEC ランタイムヘッダーファイル
UNICODEWindows ヘッダーファイル

→ ANSI と Unicode ビルドをちぐはぐにビルドすることは稀なので、_UNICODE と UNICODE を定義するときは両方セットに定義する。

TCHAR汎用文字
LPTSTR汎用文字列へのポインタ
LPCTSTR汎用文字列定数

これはコツに近いですが、上記テーブルのTCHAR 系を基本にすると整理しやすいです。 UNICODE (ワイドキャラクタ) のとき、TCHAR、LPTSTR、LPCTSTR はそれぞれ、 (T が W になり) WCHAR、LPWSTR、LPCWSTR となり、 ANSI のときは (T が取れて)、CHAR、LPSTR、LPCSTR となります。

尚、今どきロングポインタやニアポインタなどの区別は不要ですから、LPTSTR は PTSTR と、LPCTSTR は PCTSTR と同義です。

C ランタイム関数を使うときは、Visual C++ では標準の C ランタイム関数 (sprintf 等) に対応する TCHAR 汎用バージョンがあります。 なるべく TCHAR を使って書いておくとよいでしょう。例えば sprintf に対して _stprintf があります。

あるいは C ランタイム関数を使わない windows.h (winbase.h) で定義されている、lstrlen などのシリーズを利用するとよいでしょう。

文字列についてはセキュリティの面から、strsafe.h で定義される StringCbPrint 等の利用が推奨されます。

文字列定数は TEXT マクロで囲む。これで UNICODE、ANSI どちらにも対応できるはずです。

以上、上記のルールを頭に入れつつ、大量にある文字列関数群をみて行けば良いと思います。 説明が長くなりましたが、上記の理由で問題の WinMain では LPSTR (渡されるもの自体が常に char* だから)、TEXT マクロ (MessageBox は、 MessageBoxA や MessageBoxW に変るから) を使っているのでした。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説