VC++とマルチバイト文字とワイド文字
《 初回公開:2020/02/01 , 最終更新:2020/05/14 》
VC++において、日本語の文字を扱うにあたり、文字コードに対する知識(認識)が必要だったので文字コードに関する情報を調べてみた。
そこでは、以下のように述べた。
一応、マルチバイト文字は可変長の複数バイトの文字列となっているのだが、
VC++等ではユニコード文字列を処理するのにワイド文字を示すwchar_t型が、ユニコード以前のShift_JISやascii文字をマルチバイト文字と呼びこれを処理するのに従来からあるchar型が使われる。
ワイド文字は2バイト(または4バイト)の固定長でユニコードを、それに対してマルチバイト文字はワイド文字以外のシングルバイト文字とダブルバイト文字もマルチバイト文字に含まれるという位置付けになっている。
※紛らわしいのは、Windowsでのダブルバイト文字というのはShift-JISにおける2バイト文字の事を指しているような。
- Unicode と MBCS | Microsoft Docs
マルチバイト文字セットは、2 バイト文字セット (DBCS: Double Byte Character Set) とも呼ばれています。
DBCS 文字は、1 バイトまたは 2 バイトで構成されます。
【 目次 】
ANSIとunicode
大雑把に言えばWindowsではワイド文字はunicodeを指し、マルチバイト文字はShift_JISを指していて、 マルチバイト文字の事をANSIと呼んだりする。
以上の事を整理すると
呼称 | 文字コード | 型 |
---|---|---|
マルチバイト文字 , ANSI | Shift_JIS(CP932) | char |
ワイド文字 , ユニコード | Unicode | wchar_t |
マクロ定義の型
VC++のサンプルコードを見ていると、古い標準のC言語ではみかけない型が定義されていて混乱してしまう。
DWORDとか
TCHARとか_tprintfとかTEXT
これっていったい何だろう?
これらの型や関数はヘッダーファイルにtypedefや#defineを使って定義されていて
typedefや#defineというのは
それを確認するには
- DWORDの実際の型は何でしょうか -VC++.NETの環境です。DOWRD dw1 = 1;i- C言語・C++・C# | 教えて!goo
Visual Studioを使っているのならば、知りたい型の上にマウスポインタを置いて右クリック、ポップアップメニューの「定義へ移動」または「宣言へ移動」で簡単に知ることが出来ます。
xxx_tという型が定義されていて末尾に付けられている_tというのは何かというと「標準typedefとして用意された型名に付けられるサフィックス」。 _tはtypedefで定義されている事を示していて、
- _t ‐ 通信用語の基礎知識
CやC++において、標準typedefとして用意された型名に付けられるサフィックス。_tは、typedefを見分けるために付けられる識別子である。
VC++独自に定義されているものは全文字が大文字
Windowsの場合、かつてのMS-DOSの頃から変わらず、実装独自のtypedefは_tを付けるのではなく全て大文字にする流儀がある。従って_tの一覧には名前が出てこない。
他にどんな型が定義されているかというと
文字の型
マルチバイト文字の型
マルチバイト文字(Shift_JIS)の型には古くからC言語で使われてる文字型を示すchar型が使われるのはすでに述べた。
そして、マルチバイト文字の処理用に文字のポインター型も定義されている。
LPSTR = char*
LPCSTR = const char*
ワイド文字の型
ワイド文字(Unicode)の型にはwchar_t型が使われる。
- ワイド文字 | Programming Place Plus C言語編 第47章
wchar_t型は、環境でサポートされているすべてのロケールの中で、最も大きい文字を表現できる大きさを持つ整数型です。
つまり、規格としては具体的な大きさが決められていません。
VC++ではwchar_t型はunsigned short型すなわち2byteになる。
typedef unsigned short wchar_t;
ワイド文字の処理用に定義されている型は
WCHAR = wchar_t で wchar_tはVC++の場合 unsigned short
LPWSTR = WCHAR*
LPCWSTR = const WCHAR*
ワイド文字・文字列はunsigned short型の配列であり2byteごとに処理される。
TCHAR - マルチバイト文字/ワイド文字両対応の型
マルチバイト文字/ワイド文字の両方に使用できる型がマクロ定義されていて、defineマクロ_UNICODEとUNICODEを定義するだけでマルチバイト文字かワイド文字を切り替えられる。
マルチバイト文字 -「UNICODE」未定義
・TCHAR = char
・LPCTSTR = const char*
・LPTSTR = char*
ワイド文字 - 「UNICODE」定義
・TCHAR = WCHAR
・LPCTSTR = const WCHAR*
・LPTSTR = WCHAR*
以下に参考記事を
- 今さら聞けない、教えてもらえない!! Unicode /マルチバイト文字対応 国際化VC ++ プログラミングの基礎!! | Microsoft Docs
- [char、wchar_t(WCHAR)とTCHAR(dinop.com)] (https://www.dinop.com/vc/char_wchar_tchar.html){: target="_blank"}
- Visual C++雑多メモ ー TCHAR編
- TCHARとかLPCTSTR、LPTSTRって何???(UsefullCode.net)
- 紛らわしいぞ!LPCTSTR、LPTSTR、LPSTR、LPCSTRは全部意味が違う!(UsefullCode.net)
- メモ帳: TCHAR型
- おいふぉりーのぶろぐ 文字列について整理
- 文字と文字列と文字コードのお話 - Qiita
文字列リテラル
マルチバイト文字の文字列リテラル
_T と TEXTとL
通常、c言語では文字列リテラルを以下のように表現する。
const char* str = "日本語";
これはマルチバイト文字の文字列リテラルであり、マルチバイト文字の型LPCTRを使った以下のコードと等価である。
LPCSTR str = "日本語";
ワイド文字の文字列リテラル
ワイド文字の文字列リテラルを表現するには通常の文字列リテラルの前にLを追加する。
const wchar_t* wstr = L"ワイド文字";
あるいは
LPCTSTR str = L"ワイド文字";
マルチバイト文字・ワイド文字両対応の文字列リテラル
マルチバイト文字・ワイド文字両対応の文字列リテラルを表すにはTEXTマクロを用いて
LPCTSTR str = TEXT("マルチバイト文字・ワイド文字両対応");
TEXTマクロには短縮形_Tを使う事もできる。
そしてなぜなのか_TEXTというこれと等価のマクロもあって、
以下の3行のコードはすべて等価。
LPCTSTR tstr = TEXT("マルチバイト文字・ワイド文字両対応"); LPCTSTR tstr = _T("マルチバイト文字・ワイド文字両対応"); LPCTSTR tstr = _TEXT("マルチバイト文字・ワイド文字両対応");
- ホイール欲しい ハンドル欲しい » _T() と TEXT() の違いやソースの文字コード
_TEXT() は tchar.h だけど TEXT() と __TEXT() は winnt.h など、双方シンボルが衝突しないように微妙に使い分けられているようです。
_T() と TEXT() はそれぞれ定義している場所が違うので、include しているヘッダによっては必ずしも両方使えるとは限らないようです。
また UNICODE、_UNICODE の両方を必ず定義するか、または定義しない
ようにしないと定義内容が異なってしまいます。
適当に文字型のコードを記述してみると、
#include <tchar.h> #include <wtypes.h> int main() { const char* str = "日本語"; LPCSTR str = "日本語"; LPCWSTR str = L"ワイド文字"; const wchar_t* wstr = L"ワイド文字"; LPCTSTR tstr = TEXT("マルチバイト文字・ワイド文字両対応"); LPCTSTR tstr = _T("マルチバイト文字・ワイド文字両対応"); LPCTSTR tstr = _TEXT("マルチバイト文字・ワイド文字両対応"); wchar_t wc[] = L"ワイド文字"; const wchar_t* text = L"ワイド文字"; wchar_t* a; a = wc; TCHAR tc;
Cランタイム関数
printf関数の仲間達
printf関数
C言語の最新仕様ではprintf関数にセキュリティが強化された新しいバージョンのprintf_sがあり、
- printf, fprintf, sprintf, snprintf, printf_s, fprintf_s, sprintf_s, snprintf_s - cppreference.com
-
printf関数(C言語) - 超初心者向けプログラミング入門
printf関数は書式指定文字列にNULLが渡された場合にエラーとなりますが、print_s関数は書式指定文字列に不正な形式の文字列が渡された場合にもエラーとなります。
VC++では
_printf_l のように、_l が付いているロケールを引数で指定できる関数も存在する。
そしてそのそれぞれにワイド文字バージョンの関数が存在する。
かつマルチバイト文字・ワイド文字両対応の関数の、先頭に_tの付いたバージョンもある。
- printf、_printf_l、wprintf、_wprintf_l | Microsoft Docs
- printf_s、_printf_s_l、wprintf_s、_wprintf_s_l | Microsoft Docs
sprintfやfprintf,vprintfにも同様のバージョンが
- sprintf_s、_sprintf_s_l、swprintf_s、_swprintf_s_l | Microsoft Docs- fprintf_s、_fprintf_s_l、fwprintf_s、_fwprintf_s_l | Microsoft Docs
- vprintf_s、_vprintf_s_l、vwprintf_s、_vwprintf_s_l | Microsoft Docs
scanf関数も同様に
printf以外にもCランタイムの文字列関数にはマルチバイト文字・ワイド文字および両対応の関数がそれぞれあって使い分けが必要。
ワイド文字を標準出力すると正しく出力されない
通常のprintf関数はマルチバイト文字用。
ワイド文字を標準出力するにはprintfでは無くwprintfを使う。
TCHARのようにマルチバイト文字・ワイド文字両対応にするには_tprintf関数を使う。
_tprintf関数はマクロ定数_UNICODEが定義されているどうかでprintfかwprintfのどちらを使うか切り替えているようで。
そして_tprintfやwprintfでワイド文字列を標準出力するためにはローケールの設定が必要。
wprintfの前にローケールの設定を
#include <locale.h> setlocale( LC_ALL, "Japanese");
setlocale関数というのは
サンプルコード
#include <iostream> #include <tchar.h> int main() { // マルチバイト文字の出力 std::cout << "1.日本語\n"; printf_s(("2.日本語\n")); // ワイド文字の出力の前にロケールを設定 setlocale(LC_ALL, "Japanese"); // あるいは_wsetlocale(LC_ALL, L"Japanese")か_tsetlocale( LC_ALL, _T("Japanese")) // ワイド文字の出力 std::wcout << L"3.日本語\n"; wprintf_s((L"4.日本語\n")); // マルチバイト文字・ワイド文字両対応の出力 _tprintf(_T("5.日本語\n")); }
setlocaleの替わりに_wsetlocaleや_tsetlocaleのどちらを使っても良くて、
第二引数の文字の型に注意。
実行結果
1.日本語 2.日本語 3.日本語 4.日本語 5.日本語
マルチバイト文字,ワイド文字変換関数
VC++のランタイム関数にはマルチバイト文字とワイド文字の変換をおこなう関数が用意されていて
関数 wcstombs_s,_wcstombs_s_l -> ワイド文字のシーケンスを、対応するマルチバイト文字のシーケンスに変換
関数 mbtowc、_mbtowc_l -> マルチバイト文字を対応するワイド文字に変換
Windows APIにはANSI用の関数とunicode用の関数が
Windows APIにもANSIすなわちマルチバイト文字用の関数とワイド文字(unicode)用の関数の2つが存在する。
例えば、GetUserName APIにはANSI用のGetUserNameAとunicodeのGetLastNameWのように関数名の最後に A または W の文字が付けられて区別されている。
マクロ定数UNICODE
を定義する事によりマルチバイトとワイド文字が切り替えられるようになっている。
winbase.h
#ifdef UNICODE #define GetUserName GetUserNameW #else #define GetUserName GetUserNameA #endif // !UNICODE
尚、GetUserNameはAdvapi32.dllに定義されていて、
ビルド時にインポートライブラリAdvapi32.libをリンクする必要がある。
C ランタイムライブラリや kernel32.lib に含まれる関数はデフォルトでリンクされるので明示的に指定する必要はないようだが。
次のコードはGetSystemDirectory APIを使ってWindows のシステムディレクトリのパスを取得する例である。
#include <windows.h> #include <tchar.h> #include <cstdio> int main() { TCHAR lpBuffer[MAX_PATH + 1]; UINT uSize = MAX_PATH; if (!GetSystemDirectory((LPTSTR)lpBuffer, uSize)) { _tprintf(_T("Error: %u"), GetLastError()); return 1; } _tprintf(_T("%s\n"), lpBuffer); return 0; }
windows api 一覧
- API 技術関連 - VBでのWin32APIのサンプル
- 標準 Windows API
- Win32API 一覧
- WIN32 API
- 目的別 Win32API のサンプル集 - プログラミングのメモ帳(C/C++/HSP)
Cプログラムのエントリーポイント
main関数
Visual Studio 2019でコンソールアプリケーションのプロジェクトを作成した場合、Cプログラムのエントリーポイントであるmain関数が以下のように自動生成される。
int main()
マルチバイト文字のmain関数
しかし、コマンドライン引数を受け取る場合は通常、C言語では以下のような引数を持ったmain関数になる。
int main(int argc, char *argv[])
このmain関数の2番目の引数はchar型であり、コマンドライン引数をマルチバイト文字列として受け取る事になる。
ワイド文字のmain関数
VC++にはワイド文字用のmain関数が用意されている。
int wmain(int argc, wchar_t *argv[])
マルチバイト文字・ワイド文字両対応のmain関数
マルチバイト文字・ワイド文字両対応のmain関数も用意されていて
int _tmain(int argc, _TCHAR* argv[])
- Visual Studio 2015で作成したC++プロジェクトにて、端折られているmain関数のコマンドライン引数を復活させる - Chronoir.net
- _tmain関数の代わりに main関数を使う方法 - Visual C++ - to_dk notebook
マルチバイト文字・ワイド文字両対応のmain関数を使用してコマンドライン引数を受け取る例を以下に示す。
#include <iostream> #include <tchar.h> int _tmain(int argc, _TCHAR* argv[]) { _tsetlocale(LC_ALL, _T("Japanese")); for (int i = 0; i < argc; i++) { _tprintf_s(_T("%d番目の引数 = %s\n"), i, argv[i]); } }
ちなみにC++Builderでもwmainと_tmain関数が存在していて
WinMain関数
windows GUIプログラムのエントリーポイントWinMain関数
- ゼロからはじめるWindows API - WinMain 関数 すべての始まり編 | マイナビニュース
- プログラムはどこから始まるの? ~ WinMain とは? - Web/DB プログラミング徹底解説
- WinMain関数
マルチバイト文字のWinMain関数
よくみかけるWinMain関数のシグネチャは
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
このlpCmdLine引数もLPSTR型として定義されているのでマルチバイト文字用の関数という事になる。
ワイド文字のWinMain関数
Visual Studio 2019の新しいプロジェクトの作成で、ディスクトップアプリケーションを選択した場合に自動生成されるWinMain関数は以下のようになる
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
このwWinMain関数のlpCmdLine引数の型はLPWSTRであり、これがワイド文字用のWinMain関数という事になる。
つまりVisual Studio 2019のディスクトップアプリケーション・プロジェクトではデフォルトでワイド文字用のWinMain関数が自動生成される事になる。
APIENTRYはminwindef.hに以下のように定義されていて
#define APIENTRY WINAPI
WINAPIは
#define WINAPI __stdcall
マルチバイト文字・ワイド文字両対応のWinMain関数
更に、マルチバイト文字・ワイド文字両対応のWinMain関数があって
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, int nShowCmd);
ビルド時の文字セットの指定
マルチバイト文字・ワイド文字両対応のプログラムをワイド文字として(ユニコード用に)ビルドするには UNICODE と _UNICODE の二つの定数を指定する必要がある。
Visual Studio IDE(Visual Studio Community 2019)での文字セットの指定
Visual Studio IDE(Visual Studio Community 2019)で文字セットを指定するには、リソリューションエクスプローラよりプロジェクトを選択してマウスの右クリックメニューよりプロパティを選択、下記画面のように詳細より文字セットにて設定。
コマンドラインからのコンパイルでの文字セットの指定
clコマンドの/DオプションでUNICODE と _UNICODE の二つの定数を指定する事ができる。
cl /DUNICODE /D_UNICODE /c test.cpp
なぜUNICODEと_UNICODEの両方の指定が必要なのか
VC++のUnicode対応のプログラムをビルドするのになぜUNICODE と _UNICODE の二つの定数を指定するのだろうか。
-
TEXT vs. _TEXT vs. _T, and UNICODE vs. _UNICODE | The Old New Thing
アンダースコアのないプレーンバージョンは、Windowsヘッダーファイルがデフォルトとして扱う文字セットに影響します。
したがって、UNICODEを定義すると、GetWindowTextは、たとえばGetWindowTextAではなくGetWindowTextWにマップされます。
同様に、TEXTマクロは「…」ではなく「L」…」にマッピングされます。
下線付きのバージョンは、Cランタイムヘッダーファイルがデフォルトとして扱う文字セットに影響します。
したがって、_UNICODEを定義すると、_tcslenは、たとえばstrlenではなくwcslenにマップされます。
同様に、_TEXTマクロは、「…」ではなくL「…」にマッピングされます。 -
ソースコード中の識別子 MessageBox は,UNICODE が定義されていれば MessageBoxW に,未定義であれば MessageBoxA に置換されます。
...
このように,文字列を扱う Windows API 関数の多くは UNICODE の定義の有無に応じて関数名の置換を行っています。
また,Visual C++ のランタイムライブラリも,同様にして _MBCS, _UNICODE の定義の有無によって,関数名の置換を行っています。
つまりWindows APIの切り替えにUNICODEが、そしてC++ のランタイムライブラリの切り替えに_UNICODEが必要なようだ。
コマンドラインから日本語を含むソースをコンパイルする時の注意。
何故なのか、BOMなしUTF-8で保存した日本語を含むソースをコンパイルしようとした時に「warning C4819」が出るので注意。
ソースファイルをBOM付きUTF-8やShift-JIS形式で保存し直す事で解決。
char8_t型 char16_t型 char32_t型
標準のC/C++ではchar8_t型 char16_t型 char32_t型が定義されている。
- 文字列リテラル | C++11 and C++14 additional features handbook.
char16_t, char32_tは、それぞれUCS2の範囲の文字、UCS4の範囲の文字を扱うことができる。
UCSとUnicodeはほぼ互換であるため、UTF-16, UTF-32にそれぞれ対応すると考えれば良い。
なお、UTF-16の場合はサロゲートペアを用いる場合がある。
よってchar16_t型の文字列の場合、サロゲートペア形式を用いて複数の値(例えば2文字分)を1文字として表現する可能性もある。(UCS2は固定幅文字のみであり、UTF-16はマルチバイト文字も扱う。)
また、char16_t型文字列、char32_t型文字列は、Unicodeとの互換性を保つために、0x0 - 0x10FFFFまでのコード値のみで構成されなければならない。 - char16_tとchar32_t - cpprefjp C++日本語リファレンス
ただし、C++11時点で、標準ライブラリではchar16_tとchar32_tの入出力はサポートしない。そのため、それらの文字・文字列はシステムの文字コードに変換して入出力する必要がある。
...
uプレフィックスが付く文字リテラルの型はchar16_tであり、
...
Uプレフィックスが付く文字リテラルの型はchar32_tであり、 - UTF-8エンコーディングされた文字の型として
char8_t
を追加 - cpprefjp C++日本語リファレンス - ワイド文字 - Wikipedia
VC++でもchar8_t型 char16_t型 char32_t型は使えるみたい。
参考までに、Visual C++ 2010ということであれば... 扱うことができる文字の表現方法は少なくとも5種類あります。
- ナロー文字。ASCIIなど、1バイトで表現出来る文字
- 多バイト文字。シフトJISなど、2バイト以上で1文字を表す。
- ワイド文字。wchar_t型を使用し、UTF-16で表現
- char16_t型を使用し、UCS-2で表現
- char32_t型を使用し、UCS-4で表現
C++0xを部分的にサポートしたので、この辺はかなり複雑になりました。
複数の表現の文字列リテラルがいろいろあって
蛇足
Delphiの文字型
参考までにDelphiの文字型についてのリンクを