WinAPIのWNDCLASSA・WNDCLASSW・WNDCLASSEXA・WNDCLASSEXWの違いってなんだ?

Win32 APIで登場するWNDCLASSA・WNDCLASSW・WNDCLASSEXA・WNDCLASSEXWの違いを、EXの有無とA/Wの文字列方式の2軸で整理します。Visual Studioの文字セット設定、UNICODEマクロ、TEXTマクロ、UTF-8保存設定との違いまで解説します。

Share
WinAPIのWNDCLASSA・WNDCLASSW・WNDCLASSEXA・WNDCLASSEXWの違いってなんだ?

Win32 APIでウィンドウを作ろうとすると、RegisterClassRegisterClassExという関数を使って、ウィンドウの属性や動作をあらかじめWindowsへ登録する場面が出てきます。

このとき設定するのが、ウィンドウクラスです。

ここでいうウィンドウクラスは、C++のclassとは別物です。ウィンドウのアイコン、マウスカーソル、背景色、ウィンドウプロシージャなどをまとめた、いわばウィンドウの設計情報です。

ところが、このウィンドウクラスを設定しようとすると、いきなり次の4種類が登場します。

WNDCLASSA
WNDCLASSW
WNDCLASSEXA
WNDCLASSEXW

名前がほとんど同じなので、初めて見ると次のような疑問が出てきます。

  • WNDCLASSWNDCLASSEXは何が違うのか
  • 末尾のAWには何の意味があるのか
  • なぜ文字列にLを付けるコードがあるのか
  • WNDCLASSEXとだけ書かれているサンプルは何なのか
  • Visual Studioの「Unicode文字セットを使用する」と関係があるのか

この記事では、この4種類の違いを順番に整理します。

結論から言うと、違いは次の2つだけです。

WNDCLASS / WNDCLASSEX
    基本版か拡張版か

A / W
    扱う文字列の種類

4種類を個別に暗記する必要はありません。EXの意味と、AWの意味を理解すれば整理できます。

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が付く
    → 文字列の種類が変わる

まずは、WNDCLASSWNDCLASSEXの違いから確認します。


1.WNDCLASSとWNDCLASSEXの違い

最初に、末尾のAWを無視して考えます。

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
    );

WNDCLASSEXWNDCLASSの構造上の違いは、基本的に次の2つです。

追加メンバ 役割
cbSize 構造体全体のサイズを指定する
hIconSm 小さいアイコンを指定する

登録関数も異なる

WNDCLASSWNDCLASSEXでは、対応する登録関数も異なります。

構造体 登録関数
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の違い

次に、末尾のAWを見ていきます。

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でもない

charstd::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
    使用する文字列の種類

ところが、実際のサンプルコードを見ると、新しい疑問が出てきます。

次のように、末尾のAWが付いていない型が使われていることがあるからです。

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種類そのものを直接選択する画面ではありません。

末尾のないWNDCLASSEXRegisterClassExを、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設定では、WNDCLASSEXWNDCLASSEXWになっているためです。

必要な型
    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
);

lpszClassNameLPCSTRになるため、通常の文字列を使用します。

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
    マルチバイト文字セット用の機能に影響する

WNDCLASSEXRegisterClassExCreateWindowExなどの切り替えに直接関係するのは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設定では、WNDCLASSEXWNDCLASSEXWになります。

そのため、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文字セットなら、RegisterClassExRegisterClassExWになるため型が一致します。

しかし、マルチバイト文字セットの場合は、次の組み合わせになります。

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
);

CreateWindowExWLPCWSTRを要求します。

文字列をワイド文字列にします。

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.まとめ

WNDCLASSAWNDCLASSWWNDCLASSEXAWNDCLASSEXWの違いは、次の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

末尾のないWNDCLASSEXRegisterClassExは、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::stringstd::wstringLPSTRLPCWSTR、UTF-8とUTF-16の変換については、次の記事で詳しく扱います。

次の記事:C++とWin32 APIの文字列型を整理する――string・wstring・LPSTR・LPCWSTR・UTF-8・UTF-16

Read more

【パズル No 13】じじいの謎解き No 13 図が表しているものなーんだ

【パズル No 13】じじいの謎解き No 13 図が表しているものなーんだ

【パズル No 13】じじいの謎解き No 13 図が表しているものは? じじいの謎解き No 13です。 問題が、良い感じに韻を踏んでいます♪ 最近はお正月の風物詩みたいなもんだけど、今は予約して買ったりするんですね。 今回は、簡単かもなのでぜひ解いてみてください! コメントや意見などもお待ちしておりますので、よろしくお願いいたします。

【パズル No 12】じじいの謎解き No 12 暗号パズル

【パズル No 12】じじいの謎解き No 12 暗号パズル

*【パズル No 12】じじいの謎解き No 12 暗号パズル じじいの謎解き No 12です。 どうも、全く自己管理ができないじじいです。 よく見たら、No12をTwitterだけにアップしてこちらにあげていなかったようです。 今更ですが、掲載しますのでお願いだから皆さんこぞって解いてください! コメントや意見などもお待ちしておりますので、よろしくお願いいたします。