WinAPIのWNDCLASSA・WNDCLASSW・WNDCLASSEXA・WNDCLASSEXWの違いってなんだ?
Win32 APIで登場するWNDCLASSA・WNDCLASSW・WNDCLASSEXA・WNDCLASSEXWの違いを、EXの有無とA/Wの文字列方式の2軸で整理します。Visual Studioの文字セット設定、UNICODEマクロ、TEXTマクロ、UTF-8保存設定との違いまで解説します。
Win32 APIでウィンドウを作ろうとすると、RegisterClassやRegisterClassExという関数を使って、ウィンドウの属性や動作をあらかじめWindowsへ登録する場面が出てきます。
このとき設定するのが、ウィンドウクラスです。
ここでいうウィンドウクラスは、C++のclassとは別物です。ウィンドウのアイコン、マウスカーソル、背景色、ウィンドウプロシージャなどをまとめた、いわばウィンドウの設計情報です。
ところが、このウィンドウクラスを設定しようとすると、いきなり次の4種類が登場します。
WNDCLASSA
WNDCLASSW
WNDCLASSEXA
WNDCLASSEXW
名前がほとんど同じなので、初めて見ると次のような疑問が出てきます。
WNDCLASSとWNDCLASSEXは何が違うのか- 末尾の
AとWには何の意味があるのか - なぜ文字列に
Lを付けるコードがあるのか WNDCLASSEXとだけ書かれているサンプルは何なのか- Visual Studioの「Unicode文字セットを使用する」と関係があるのか
この記事では、この4種類の違いを順番に整理します。
結論から言うと、違いは次の2つだけです。
WNDCLASS / WNDCLASSEX
基本版か拡張版か
A / W
扱う文字列の種類
4種類を個別に暗記する必要はありません。EXの意味と、A・Wの意味を理解すれば整理できます。
4種類の違いを先に整理する
| 構造体 | 基本・拡張 | 使用する文字列 | クラス名の型 |
|---|---|---|---|
WNDCLASSA |
基本版 | char系 |
LPCSTR |
WNDCLASSW |
基本版 | wchar_t系 |
LPCWSTR |
WNDCLASSEXA |
拡張版 | char系 |
LPCSTR |
WNDCLASSEXW |
拡張版 | wchar_t系 |
LPCWSTR |
名前を分解すると、次のようになります。
WNDCLASS + A
WNDCLASS + W
WNDCLASSEX + A
WNDCLASSEX + W
つまり、覚えることは次の2点です。
EXが付く
→ 拡張版
AまたはWが付く
→ 文字列の種類が変わる
まずは、WNDCLASSとWNDCLASSEXの違いから確認します。
1.WNDCLASSとWNDCLASSEXの違い
最初に、末尾のAとWを無視して考えます。
WNDCLASS
WNDCLASSEX
WNDCLASSEXは、WNDCLASSを拡張した構造体です。
WNDCLASSの構造
Unicode版のWNDCLASSWは、概略として次のメンバを持っています。
typedef struct tagWNDCLASSW
{
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCWSTR lpszMenuName;
LPCWSTR lpszClassName;
} WNDCLASSW;
ウィンドウクラスには、次のような設定を登録します。
- ウィンドウの動作を表すスタイル
- メッセージを処理するウィンドウプロシージャ
- アプリケーションのインスタンス
- アイコン
- マウスカーソル
- 背景ブラシ
- メニュー名
- ウィンドウクラス名
ここでいう「ウィンドウクラス」は、C++のclassとは別物です。
Win32 APIにおけるウィンドウクラスは、これから生成するウィンドウの共通設定をまとめたものです。
ウィンドウクラス
ウィンドウの設計情報
ウィンドウ
その設計情報を使って実際に生成されたもの
WNDCLASSEXの構造
拡張版のWNDCLASSEXWは、概略として次のメンバを持っています。
typedef struct tagWNDCLASSEXW
{
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCWSTR lpszMenuName;
LPCWSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEXW;
WNDCLASSWと比較すると、次の2つが追加されています。
UINT cbSize;
HICON hIconSm;
cbSize
cbSizeには、使用する構造体全体のサイズを指定します。
WNDCLASSEXW windowClass{};
windowClass.cbSize =
sizeof(WNDCLASSEXW);
WNDCLASSEXWを使用しているのに、別の構造体のサイズを指定してはいけません。
WNDCLASSEXW windowClass{};
// 間違い
windowClass.cbSize =
sizeof(WNDCLASSW);
使用している構造体自身のサイズを指定します。
windowClass.cbSize =
sizeof(WNDCLASSEXW);
hIconSm
hIconSmには、ウィンドウクラスで使用する小さいアイコンを指定します。
windowClass.hIconSm =
LoadIconW(
nullptr,
IDI_APPLICATION
);
従来からあるhIconには、通常サイズのアイコンを指定します。
windowClass.hIcon =
LoadIconW(
nullptr,
IDI_APPLICATION
);
WNDCLASSEXとWNDCLASSの構造上の違いは、基本的に次の2つです。
| 追加メンバ | 役割 |
|---|---|
cbSize |
構造体全体のサイズを指定する |
hIconSm |
小さいアイコンを指定する |
登録関数も異なる
WNDCLASSとWNDCLASSEXでは、対応する登録関数も異なります。
| 構造体 | 登録関数 |
|---|---|
WNDCLASSA |
RegisterClassA |
WNDCLASSW |
RegisterClassW |
WNDCLASSEXA |
RegisterClassExA |
WNDCLASSEXW |
RegisterClassExW |
例えば、WNDCLASSEXWを使う場合はRegisterClassExWで登録します。
WNDCLASSEXW windowClass{};
windowClass.cbSize =
sizeof(WNDCLASSEXW);
RegisterClassExW(
&windowClass
);
次の組み合わせは間違いです。
WNDCLASSEXW windowClass{};
// WNDCLASSW用の関数なので型が合わない
RegisterClassW(
&windowClass
);
WNDCLASSとWNDCLASSEXの比較
| 項目 | WNDCLASS |
WNDCLASSEX |
|---|---|---|
| 基本的なウィンドウクラス設定 | できる | できる |
| 構造体サイズの指定 | なし | cbSize |
| 小さいアイコンの指定 | なし | hIconSm |
| 対応する登録関数 | RegisterClass |
RegisterClassEx |
| 新規コードでの選択 | 使用可能 | 基本的にこちらでよい |
したがって、新しくコードを書く場合は、拡張版のWNDCLASSEXを選べばよいでしょう。
2.末尾のAとWの違い
次に、末尾のAとWを見ていきます。
A
Windowsコードページ版
W
Unicode版・Wide Character版
一般には、次のように説明されることが多いです。
A
ANSI版
W
Wide版
ただし、A版の「ANSI」という呼び方は歴史的なものです。
実際には、A版APIはWindowsのコードページに基づくchar文字列を扱います。必ずしも特定のANSI規格そのものを意味するわけではありません。
A版で使う文字列
A版では、char系の文字列を使用します。
WNDCLASSEXA windowClass{};
windowClass.lpszClassName =
"SampleWindowClass";
WNDCLASSEXA::lpszClassNameの型はLPCSTRです。
LPCSTRは、実質的には次の型です。
const char*
したがって、通常の文字列リテラルを代入できます。
"SampleWindowClass"
登録にはA版の関数を使用します。
RegisterClassExA(
&windowClass
);
ウィンドウ生成にもA版を使用します。
CreateWindowExA(
0,
"SampleWindowClass",
"Sample Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
1280,
720,
nullptr,
nullptr,
hInstance,
nullptr
);
W版で使う文字列
W版では、wchar_t系のワイド文字列を使用します。
WNDCLASSEXW windowClass{};
windowClass.lpszClassName =
L"SampleWindowClass";
WNDCLASSEXW::lpszClassNameの型はLPCWSTRです。
LPCWSTRは、実質的には次の型です。
const wchar_t*
そのため、文字列リテラルの先頭にLを付けます。
L"SampleWindowClass"
登録にはW版の関数を使用します。
RegisterClassExW(
&windowClass
);
ウィンドウ生成にもW版を使用します。
CreateWindowExW(
0,
L"SampleWindowClass",
L"サンプルウィンドウ",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
1280,
720,
nullptr,
nullptr,
hInstance,
nullptr
);
普通の文字列とL付き文字列は型が違う
次の2つは、見た目が似ていますが、C++上では異なる型です。
"SampleWindowClass"
L"SampleWindowClass"
対応を整理すると次のようになります。
| 書き方 | 文字型 | ポインター型 | 対応するWin32 API |
|---|---|---|---|
"ABC" |
char |
const char* |
A版 |
L"ABC" |
wchar_t |
const wchar_t* |
W版 |
文字列の先頭に付いているLは、表示される文字ではありません。
コンパイラに対して、文字列をwchar_tの配列として作るように指定する接頭辞です。
A版とW版の対応
| 項目 | A版 | W版 |
|---|---|---|
| 構造体 | WNDCLASSEXA |
WNDCLASSEXW |
| クラス名の型 | LPCSTR |
LPCWSTR |
| 実質的な型 | const char* |
const wchar_t* |
| 文字列リテラル | "ABC" |
L"ABC" |
| 登録関数 | RegisterClassExA |
RegisterClassExW |
| ウィンドウ生成 | CreateWindowExA |
CreateWindowExW |
| 文字の扱い | Windowsコードページ | UTF-16 |
構造体だけをW版にしても、登録関数や文字列がA版のままでは型が合いません。
WNDCLASSEXW
↓
RegisterClassExW
↓
CreateWindowExW
↓
L"文字列"
このように、構造体、関数、文字列を同じ系統へそろえる必要があります。
3.マルチバイト文字列とは
A版の説明では、char系の文字列とWindowsコードページが登場しました。
では、A版でよく使われる「マルチバイト文字列」とは何でしょうか。
マルチバイト文字列は、charを使いながら、1文字を1バイトまたは複数バイトで表現する方式です。
日本語Windowsで古くから使われてきたコードページでは、英数字などを1バイト、日本語の多くを2バイトで表現します。
A
1バイト
あ
2バイトになることがある
このため、見た目の文字数とバイト数が一致しない場合があります。
const char* text = "日本語";
この文字列をchar単位で調べると、1つの日本語文字が複数の要素に分かれます。
A版APIはWindowsコードページを使う
A版のWin32 APIは、charの文字列をWindowsコードページに従って解釈します。
MessageBoxA(
nullptr,
"メッセージ",
"タイトル",
MB_OK
);
ここで重要なのは、A版が必ずUTF-8を扱うわけではないという点です。
std::string
= 自動的にUTF-8ではない
char文字列
= 自動的にShift_JISでもない
charやstd::stringは、バイトを保存するための型です。
中身がUTF-8なのか、Windowsコードページなのかは、型だけでは判断できません。
A版APIは実行環境のWindowsコードページに依存するため、別の言語環境への移植や、扱えない文字を含む場合に問題が起きやすくなります。
そのため、新規のWindowsアプリケーションではUnicode版の利用が推奨されます。
4.Unicode文字列とは
Unicodeは、世界中の文字に共通の番号を割り当てるための規格です。
Unicodeの文字をコンピューター上に保存する方法には、次のようなものがあります。
Unicode
├─ UTF-8
├─ UTF-16
└─ UTF-32
つまり、UnicodeとUTF-16は同じ意味ではありません。
Unicode
文字を整理する規格
UTF-16
Unicodeの文字を保存する方式の1つ
Win32 APIのW版はUTF-16を使う
WindowsのW版APIは、基本的にUTF-16の文字列を使用します。
C++では主に次の型で扱います。
wchar_t
const wchar_t*
std::wstring
Win32 APIでは、次の型名が使われます。
WCHAR
LPWSTR
LPCWSTR
対応を整理すると次のようになります。
| C++の型 | Win32の型 | 意味 |
|---|---|---|
wchar_t |
WCHAR |
ワイド文字の1コード単位 |
wchar_t* |
LPWSTR |
書き換え可能なワイド文字列 |
const wchar_t* |
LPCWSTR |
読み取り専用のワイド文字列 |
例えば、次のコードはLPCWSTRへ代入できます。
LPCWSTR className =
L"SampleWindowClass";
LPCWSTRは特殊な文字列クラスではありません。
実質的にはconst wchar_t*へ付けられたWindows独自の型名です。
1文字が必ず1個のwchar_tとは限らない
Windowsのwchar_tは、UTF-16の1コード単位を格納します。
多くの日本語や英数字は1個のwchar_tで表現できますが、Unicodeのすべての文字が必ず1個に収まるわけではありません。
絵文字や一部の文字は、2個のUTF-16コード単位で表現されます。
したがって、std::wstring::size()が返す値は、必ずしも人間が見た文字数と一致しません。
この記事では、まず次の対応を押さえておけば十分です。
A版
char
LPCSTR
"文字列"
W版
wchar_t
LPCWSTR
L"文字列"
5.4種類をもう一度整理する
文字列の違いまで含めて、4種類を確認します。
WNDCLASSA
WNDCLASS
基本版
A
char系・Windowsコードページ
WNDCLASSA windowClass{};
windowClass.lpszClassName =
"SampleWindowClass";
RegisterClassA(
&windowClass
);
WNDCLASSW
WNDCLASS
基本版
W
wchar_t系・UTF-16
WNDCLASSW windowClass{};
windowClass.lpszClassName =
L"SampleWindowClass";
RegisterClassW(
&windowClass
);
WNDCLASSEXA
WNDCLASSEX
拡張版
A
char系・Windowsコードページ
WNDCLASSEXA windowClass{};
windowClass.cbSize =
sizeof(WNDCLASSEXA);
windowClass.lpszClassName =
"SampleWindowClass";
RegisterClassExA(
&windowClass
);
WNDCLASSEXW
WNDCLASSEX
拡張版
W
wchar_t系・UTF-16
WNDCLASSEXW windowClass{};
windowClass.cbSize =
sizeof(WNDCLASSEXW);
windowClass.lpszClassName =
L"SampleWindowClass";
RegisterClassExW(
&windowClass
);
新規コードでは、最後のWNDCLASSEXWを使うのが分かりやすいでしょう。
6.AもWも付いていないWNDCLASSEXは何なのか
ここまでで、4種類の違いは説明できました。
WNDCLASS / WNDCLASSEX
基本版か拡張版か
A / W
使用する文字列の種類
ところが、実際のサンプルコードを見ると、新しい疑問が出てきます。
次のように、末尾のAやWが付いていない型が使われていることがあるからです。
WNDCLASSEX windowClass{};
関数名も同様です。
RegisterClassEx(
&windowClass
);
CreateWindowEx(
// 引数
);
これは、第5の構造体や第3の関数が存在するという意味ではありません。
末尾のない名前は、プリプロセッサによってA版またはW版へ置き換えられます。
この切り替えの条件として使われるのが、UNICODEというプリプロセッサマクロです。
UNICODEマクロとは
UNICODEは、末尾のないWin32 APIの型名や関数名を、W版へ切り替えるために使われるマクロです。
プリプロセッサマクロとは、C++のコードを本格的にコンパイルする前に、名前の置き換えや条件分岐を行う仕組みです。
Windowsのヘッダーでは、概念的に次のような切り替えが行われています。
#ifdef UNICODE
#define WNDCLASSEX WNDCLASSEXW
#define RegisterClassEx RegisterClassExW
#define CreateWindowEx CreateWindowExW
#else
#define WNDCLASSEX WNDCLASSEXA
#define RegisterClassEx RegisterClassExA
#define CreateWindowEx CreateWindowExA
#endif
実際のWindowsヘッダーでは、型の別名や関数マクロが個別に定義されていますが、考え方は同じです。
つまり、UNICODEが定義されているかどうかによって、末尾なしの名前がA版・W版のどちらになるかが決まります。
UNICODEが定義されている場合
WNDCLASS
→ WNDCLASSW
WNDCLASSEX
→ WNDCLASSEXW
RegisterClassEx
→ RegisterClassExW
CreateWindowEx
→ CreateWindowExW
UNICODEが定義されていない場合
WNDCLASS
→ WNDCLASSA
WNDCLASSEX
→ WNDCLASSEXA
RegisterClassEx
→ RegisterClassExA
CreateWindowEx
→ CreateWindowExA
では、このUNICODEはどこで定義されるのでしょうか。
ここで初めて、Visual Studioのプロジェクト設定にある次の項目が登場します。
Unicode文字セットを使用する
マルチバイト文字セットを使用する
この設定は、先ほどまで説明していた4種類そのものを直接選択する画面ではありません。
末尾のないWNDCLASSEXやRegisterClassExを、A版とW版のどちらへ切り替えるかを決める設定です。
7.Visual Studioの「文字セット」設定との関係
Visual StudioのC++プロジェクトには、次の設定があります。
文字セット
一般的には、次の手順で確認できます。
ソリューション エクスプローラー
↓
対象のプロジェクトを右クリック
↓
プロパティ
↓
構成プロパティ
↓
詳細設定
↓
文字セット
Visual Studioやプロジェクトの種類によっては、構成プロパティ > 全般に表示される場合もあります。
設定には、主に次の選択肢があります。
Unicode文字セットを使用する
マルチバイト文字セットを使用する
設定なし
この設定は、末尾のないWin32 APIの型名や関数名をA版・W版のどちらとしてコンパイルするかに関係します。
Unicode文字セットを使用する場合
Visual Studioで次を選択します。
Unicode文字セットを使用する
この設定では、プロジェクトに主に次のプリプロセッサマクロが定義されます。
UNICODE
_UNICODE
_MBCSは定義されない構成になります。
Windowsのヘッダーは、主にUNICODEマクロを見てA版とW版を切り替えます。
そのため、次のコードは、
WNDCLASSEX windowClass{};
RegisterClassEx(
&windowClass
);
実質的に次の組み合わせになります。
WNDCLASSEXW windowClass{};
RegisterClassExW(
&windowClass
);
CreateWindowExもW版になります。
CreateWindowEx
→ CreateWindowExW
したがって、文字列にはワイド文字列が必要です。
WNDCLASSEX windowClass{};
windowClass.lpszClassName =
L"SampleWindowClass";
次のコードは型が一致しません。
WNDCLASSEX windowClass{};
windowClass.lpszClassName =
"SampleWindowClass";
Unicode設定では、WNDCLASSEXがWNDCLASSEXWになっているためです。
必要な型
LPCWSTR
const wchar_t*
渡している型
const char*
マルチバイト文字セットを使用する場合
Visual Studioで次を選択します。
マルチバイト文字セットを使用する
この設定では、主に次のマクロが定義されます。
_MBCS
通常、UNICODEと_UNICODEは定義されません。
そのため、末尾のないWin32 APIの名前はA版になります。
WNDCLASS
→ WNDCLASSA
WNDCLASSEX
→ WNDCLASSEXA
RegisterClassEx
→ RegisterClassExA
CreateWindowEx
→ CreateWindowExA
次のコードは、
WNDCLASSEX windowClass{};
RegisterClassEx(
&windowClass
);
実質的に次の組み合わせになります。
WNDCLASSEXA windowClass{};
RegisterClassExA(
&windowClass
);
lpszClassNameはLPCSTRになるため、通常の文字列を使用します。
windowClass.lpszClassName =
"SampleWindowClass";
設定なしの場合
文字セットを設定なしにすると、その設定からはUNICODE、_UNICODE、_MBCSが定義されません。
UNICODEが定義されていないため、末尾のないWin32 API名は基本的にA版になります。
WNDCLASSEX
→ WNDCLASSEXA
ただし、ソースコードや別のプロジェクト設定でUNICODEを手動定義している場合は、その定義が使われます。
文字セット設定による変化
| Visual Studioの設定 | 主なマクロ | WNDCLASSEX |
RegisterClassEx |
CreateWindowEx |
|---|---|---|---|---|
| Unicode文字セット | UNICODE、_UNICODE |
WNDCLASSEXW |
RegisterClassExW |
CreateWindowExW |
| マルチバイト文字セット | _MBCS |
WNDCLASSEXA |
RegisterClassExA |
CreateWindowExA |
| 設定なし | 通常はいずれもなし | WNDCLASSEXA |
RegisterClassExA |
CreateWindowExA |
UNICODEと_UNICODEの違い
「Unicode文字セットを使用する」を選ぶと、通常はUNICODEと_UNICODEの両方が定義されます。
名前は似ていますが、主な役割が異なります。
UNICODE
Windowsヘッダーが使用
Win32 APIのA版・W版を切り替える
_UNICODE
CランタイムやMFCなどが使用
TCHAR系の機能を切り替える
_MBCS
マルチバイト文字セット用の機能に影響する
WNDCLASSEX、RegisterClassEx、CreateWindowExなどの切り替えに直接関係するのはUNICODEです。
Visual Studioの文字セット設定を使うと、UNICODEと_UNICODEが矛盾しないようにまとめて設定されます。
8.TEXTマクロとの関係
末尾のない型名や関数名と一緒に、次のようなコードを見かけることがあります。
TEXT("SampleWindowClass")
または、次の書き方です。
_T("SampleWindowClass")
これらは、プロジェクト設定に応じて文字列の型を切り替えるためのマクロです。
Unicode文字セットの場合
TEXT("SampleWindowClass")
は、概念的には次の文字列になります。
L"SampleWindowClass"
マルチバイト文字セットの場合
TEXT("SampleWindowClass")
は、通常の文字列になります。
"SampleWindowClass"
したがって、末尾のない型と関数を使う場合は、文字列にもTEXTを使うとA/Wをまとめて切り替えられます。
WNDCLASSEX windowClass{};
windowClass.cbSize =
sizeof(WNDCLASSEX);
windowClass.lpszClassName =
TEXT("SampleWindowClass");
RegisterClassEx(
&windowClass
);
CreateWindowEx(
0,
TEXT("SampleWindowClass"),
TEXT("Sample Window"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
1280,
720,
nullptr,
nullptr,
hInstance,
nullptr
);
この書き方は、同じソースコードをUnicode版とマルチバイト版の両方でビルドしたい場合に使われます。
ただし、新しくW版専用のコードを書くなら、次のように明示した方が読みやすい場合もあります。
WNDCLASSEXW windowClass{};
windowClass.lpszClassName =
L"SampleWindowClass";
RegisterClassExW(
&windowClass
);
9.重要:「Unicode文字セット」はソースファイルの保存設定ではない
ここは非常に間違えやすいところです。
Visual Studioの次の設定は、
文字セット
Unicode文字セットを使用する
.cppや.hファイルをUTF-8形式で保存する設定ではありません。
この設定が主に行うことは、次のマクロを定義することです。
UNICODE
_UNICODE
そして、末尾のないWin32 APIの型名や関数名をW版へ切り替えます。
WNDCLASSEX
→ WNDCLASSEXW
RegisterClassEx
→ RegisterClassExW
CreateWindowEx
→ CreateWindowExW
一方、ソースファイルの保存形式は別の設定です。
プロジェクトの文字セット
Win32 APIをA版・W版のどちらにするか
ソースファイルのエンコーディング
.cppや.hをどのバイト列で保存するか
ソースファイルのエンコーディング
ソースコードは、例えば次の形式で保存できます。
UTF-8
UTF-8 with BOM
Shift_JIS系のコードページ
UTF-16
コンパイラにソースファイルをUTF-8として解釈させる設定には、次のオプションなどがあります。
/utf-8
/source-charset:utf-8
これは、Visual Studioの「Unicode文字セットを使用する」とは別の話です。
2つは独立している
例えば、次のような組み合わせも理屈上は存在します。
プロジェクトの文字セット
Unicode文字セット
ソースファイル
Shift_JIS系で保存
逆に、次の組み合わせもあり得ます。
プロジェクトの文字セット
マルチバイト文字セット
ソースファイル
UTF-8で保存
ソースファイルがUTF-8だからといって、CreateWindowExが自動的にCreateWindowExWになるわけではありません。
また、プロジェクトをUnicode文字セットにしたからといって、.cppファイルが自動的にUTF-8へ変換されるわけでもありません。
この2つは分けて考える必要があります。
10.プロジェクト設定を変更するときの注意
Visual Studioのプロジェクトプロパティは、構成とプラットフォームごとに値を持つ場合があります。
設定画面の上部には、次の項目があります。
構成
Debug
Release
すべての構成
プラットフォーム
Win32
x64
すべてのプラットフォーム
例えば、Debug | x64を選んだ状態で文字セットを変更すると、Release側には反映されない場合があります。
プロジェクト全体をUnicodeへ統一するなら、次のように設定します。
構成
すべての構成
プラットフォーム
すべてのプラットフォーム
その上で、文字セットを変更します。
文字セット
Unicode文字セットを使用する
11.よくあるエラー
エラー1:LPCWSTRにconst char*を代入できない
Unicode文字セットのプロジェクトで、次のコードを書いたとします。
WNDCLASSEX windowClass{};
windowClass.lpszClassName =
"SampleWindowClass";
Unicode設定では、WNDCLASSEXはWNDCLASSEXWになります。
そのため、lpszClassNameが要求する型はLPCWSTRです。
要求される型
LPCWSTR
const wchar_t*
渡している型
const char*
修正するには、ワイド文字列を使います。
windowClass.lpszClassName =
L"SampleWindowClass";
末尾なしの書き方を維持するなら、TEXTも使えます。
windowClass.lpszClassName =
TEXT("SampleWindowClass");
エラー2:WNDCLASSEXWとRegisterClassExを混ぜる
次のコードを考えます。
WNDCLASSEXW windowClass{};
RegisterClassEx(
&windowClass
);
プロジェクトがUnicode文字セットなら、RegisterClassExはRegisterClassExWになるため型が一致します。
しかし、マルチバイト文字セットの場合は、次の組み合わせになります。
WNDCLASSEXW windowClass{};
RegisterClassExA(
&windowClass
);
RegisterClassExAが要求するのはWNDCLASSEXA*なので、型が一致しません。
必要
const WNDCLASSEXA*
実際
WNDCLASSEXW*
修正方法は、W版へ統一することです。
WNDCLASSEXW windowClass{};
RegisterClassExW(
&windowClass
);
または、すべて末尾なしへ統一します。
WNDCLASSEX windowClass{};
RegisterClassEx(
&windowClass
);
混ぜないことが重要です。
エラー3:WNDCLASSEXAとRegisterClassExWを混ぜる
逆の組み合わせでも同じ問題が起きます。
WNDCLASSEXA windowClass{};
RegisterClassExW(
&windowClass
);
A版の構造体はA版の関数へ渡します。
WNDCLASSEXA windowClass{};
RegisterClassExA(
&windowClass
);
エラー4:CreateWindowExWへ普通の文字列を渡す
次のコードは型が一致しません。
CreateWindowExW(
0,
"SampleWindowClass",
"Sample Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
1280,
720,
nullptr,
nullptr,
hInstance,
nullptr
);
CreateWindowExWはLPCWSTRを要求します。
文字列をワイド文字列にします。
CreateWindowExW(
0,
L"SampleWindowClass",
L"Sample Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
1280,
720,
nullptr,
nullptr,
hInstance,
nullptr
);
エラー5:キャストで無理やり直す
次のようにキャストすると、コンパイルエラーだけが消える場合があります。
const char* className =
"SampleWindowClass";
windowClass.lpszClassName =
reinterpret_cast<LPCWSTR>(
className
);
しかし、これは文字コード変換ではありません。
charのバイト列を、そのままwchar_tの配列だと思って読み込ませているだけです。
キャスト
型の見方を変える
文字コード変換
データの並びを別の文字表現へ変換する
A版文字列をW版文字列へ変換する必要がある場合は、MultiByteToWideCharなどを使用します。
この変換については、別の記事で詳しく扱います。
12.どの書き方に統一するべきか
主な書き方は2種類あります。
方法1:末尾なしの名前で統一する
プロジェクト設定に応じて、A版とW版を切り替える方法です。
WNDCLASSEX windowClass{};
windowClass.cbSize =
sizeof(WNDCLASSEX);
windowClass.lpszClassName =
TEXT("SampleWindowClass");
RegisterClassEx(
&windowClass
);
利点は、プロジェクト設定を変更するだけでA版とW版を切り替えられることです。
ただし、型が設定によって変わるため、初学者には実際に何を使っているのか分かりにくい場合があります。
方法2:W版を明示して統一する
新規コードでは、W版を明示する方法が分かりやすいでしょう。
WNDCLASSEXW windowClass{};
windowClass.cbSize =
sizeof(WNDCLASSEXW);
windowClass.lpszClassName =
L"SampleWindowClass";
RegisterClassExW(
&windowClass
);
この方法なら、Visual Studioの文字セット設定を見なくても、コードを読んだだけで使用する型が分かります。
WNDCLASSEXW
W版
RegisterClassExW
W版
L"文字列"
wchar_t文字列
既存コードがTCHARや末尾なしのAPIを多く使っている場合は、無理に書き換えず、プロジェクト全体の方針へ合わせます。
新しく学習用・教材用のコードを書くなら、次の方針が特に分かりやすいでしょう。
Visual Studio
Unicode文字セットを使用する
Win32 API
W版を明示する
13.WNDCLASSEXWを使った最小例
最後に、W版へ統一した最小例を示します。
#include <Windows.h>
namespace
{
constexpr wchar_t WINDOW_CLASS_NAME[] =
L"SampleWindowClass";
}
LRESULT CALLBACK WindowProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam
)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(
hWnd,
message,
wParam,
lParam
);
}
int WINAPI wWinMain(
HINSTANCE hInstance,
HINSTANCE,
PWSTR,
int nCmdShow
)
{
WNDCLASSEXW windowClass{};
windowClass.cbSize =
sizeof(WNDCLASSEXW);
windowClass.style =
CS_HREDRAW |
CS_VREDRAW;
windowClass.lpfnWndProc =
WindowProc;
windowClass.hInstance =
hInstance;
windowClass.hIcon =
LoadIconW(
nullptr,
IDI_APPLICATION
);
windowClass.hCursor =
LoadCursorW(
nullptr,
IDC_ARROW
);
windowClass.hbrBackground =
reinterpret_cast<HBRUSH>(
COLOR_WINDOW + 1
);
windowClass.lpszClassName =
WINDOW_CLASS_NAME;
windowClass.hIconSm =
LoadIconW(
nullptr,
IDI_APPLICATION
);
if (RegisterClassExW(
&windowClass
) == 0)
{
return -1;
}
HWND hWnd =
CreateWindowExW(
0,
WINDOW_CLASS_NAME,
L"Win32 API Sample",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
1280,
720,
nullptr,
nullptr,
hInstance,
nullptr
);
if (hWnd == nullptr)
{
return -1;
}
ShowWindow(
hWnd,
nCmdShow
);
MSG message{};
while (GetMessageW(
&message,
nullptr,
0,
0
) > 0)
{
TranslateMessage(
&message
);
DispatchMessageW(
&message
);
}
return static_cast<int>(
message.wParam
);
}
このコードでは、次の要素をすべてW版へ統一しています。
WNDCLASSEXW
RegisterClassExW
CreateWindowExW
DefWindowProcW
GetMessageW
DispatchMessageW
文字列もワイド文字列です。
L"SampleWindowClass"
L"Win32 API Sample"
14.まとめ
WNDCLASSA、WNDCLASSW、WNDCLASSEXA、WNDCLASSEXWの違いは、次の2軸で整理できます。
WNDCLASS
基本版
WNDCLASSEX
cbSizeとhIconSmを追加した拡張版
A
char系
Windowsコードページ
W
wchar_t系
UTF-16
4種類を整理すると、次のようになります。
| 構造体 | 基本・拡張 | 文字列 |
|---|---|---|
WNDCLASSA |
基本版 | char・Windowsコードページ |
WNDCLASSW |
基本版 | wchar_t・UTF-16 |
WNDCLASSEXA |
拡張版 | char・Windowsコードページ |
WNDCLASSEXW |
拡張版 | wchar_t・UTF-16 |
対応する登録関数もそろえます。
| 構造体 | 登録関数 |
|---|---|
WNDCLASSA |
RegisterClassA |
WNDCLASSW |
RegisterClassW |
WNDCLASSEXA |
RegisterClassExA |
WNDCLASSEXW |
RegisterClassExW |
末尾のないWNDCLASSEXやRegisterClassExは、Visual Studioの「文字セット」設定とUNICODEマクロによってA版・W版へ切り替わります。
Unicode文字セットを使用する
↓
WNDCLASSEXW
RegisterClassExW
CreateWindowExW
マルチバイト文字セットを使用する
↓
WNDCLASSEXA
RegisterClassExA
CreateWindowExA
また、Visual Studioの「Unicode文字セットを使用する」は、ソースコードをUTF-8で保存する設定ではありません。
プロジェクトの文字セット
Win32 APIのA/Wを切り替える
ソースファイルのエンコーディング
.cppや.hをUTF-8などのどの形式で保存するか
新しくWin32アプリケーションを作る場合は、次の構成へ統一すると分かりやすくなります。
Visual Studio
Unicode文字セットを使用する
WNDCLASSEXW
RegisterClassExW
CreateWindowExW
L"文字列"
エラーが出たときは、次の4点を確認します。
1. WNDCLASSかWNDCLASSEXか
2. A版かW版か
3. Visual Studioの文字セット設定はどちらか
4. 構造体・関数・文字列が同じ系統にそろっているか
std::string、std::wstring、LPSTR、LPCWSTR、UTF-8とUTF-16の変換については、次の記事で詳しく扱います。
次の記事:C++とWin32 APIの文字列型を整理する――string・wstring・LPSTR・LPCWSTR・UTF-8・UTF-16