概要

Windows NT/2000/XP/Vistaは内部でUNICODEを使用している。
Windows 9x系の日本語版ではShift-JISは使っている。
Shift-JISとは、ANSIコードの使っていないコード領域に2バイトのJIS漢字コードを割り当てたものである。MS-DOSの時代から使われている。英数字等は1バイトで、漢字の場合2バイトとなる。Visual C++では普通にchar型である。コードページ(JISやShift-JISやECUなどを指定するためのコード)が違えば文字化けの原因になったりする。また、文字数を数えるのに1文字1文字、先頭から漢字が英数字を判断して数えなければならないので煩雑であるなどの問題点がある。
UNICODEでは、全世界の文字を単一の文字コードで取り込むという壮大?な理想をもとに作成された文字コードである。Windows NT系(NT/2000/XP/Vista/7/8/8.1/10/Server)でサポートされているのは、UTF16(LE)である。
Visual C++ 2005以降のプロジェクト作成時はUNICODEがディフォルトとなっている。UNICODEを使わないようにするためには、プロジェクトのオプションでマルチバイト等を選択する。プロジェクトでUNICODEが指定されている場合は、UNICODEマクロが定義される。
Windows9x系では、APIがMessageBoxぐらいしかUNICODEをサポートしていないため、Shift-JISで作成すべきであろう。TCHAR.Hで用意されているマクロを使えばUNICODE対応版とShift-JIS対応版を1つのソースから作成するのが楽になる。

テスト環境

コンパイラ

Visual C++ 2008 Standard 32/64bit
Visual C++ 2013 Express 32/64bit

実行環境

Windows 8.1 Enterprise 64bit
Windows 7 EnterPrise Service Pack 1 64bit
Windows Vista Ultimate Service Pack 2 32bit
Windows XP Professional Service Pack 3 32bit

UNICODEによる文字列および文字定数の記述方法

文字列

Shift-JISの場合

普通に
char* buf="漢字"; // char* buf = { 0x8a , 0xbf , 0x83 , 0x9a , 0 };
char* buf="0123"; // char* buf = { 0x30 , 0x31 , 0x32 , 0x33 , 0 };

UNICODEの場合

Lを付ける
WCHAR* buf = L"漢字"; // WCHAR* buf={ 0x6f22 , 0x5b57 , 0x0000 };
// byte列で表現すると 0x22 , 0x6f , 0x57 , 0x5b , 0x00 , 0x00 

WCHAR* buf = L"0123"; // WCHAR* buf={ 0x0030 , 0x0031 , 0x0032 , 0x0033 , 0 };
// byte列で表現すると 0x30 , 0x00 , 0x31 , 0x00 , 0x32 , 0x00 , 0x33 , 0x00 , 0x00 ,0x00

Shift-JISとUNICODEをサポート

TCHAR* buf=_TEXT( "漢字" );
WCHARはshort intとして定義されている。TCHAR型は、ヘッダーファイルtchar.hで定義されている。UNICODEマクロが定義されている場合はWCHAR、定義されていない場合はcharに展開される。
_TEXT("x")マクロは、UNICODEマクロが定義されている場合はL"x"、定義されていない場合は"x"に展開される。

文字定数

文字列と同じように _T( 'x' ) マクロを使う

UNICODEと他のエンコーディングとの変換

UNICODE文字列を他のエンコーディングに変換する

WideCharToMultiByte APIを使用するとUNICODE文字列を他のエンコーディングに変換できます。
cchMultiByteに0を代入してAPIを呼び出すと変換後のバイト数が返されますので動的にバッファを確保する場合に使用することができます。

プロトタイプ

int WideCharToMultiByte(UINT CodePage , DWORD dwFlags , LPCWSTR lpWideCharStr , int cchWideChar , LPSTR lpMultiByteStr , int cchMultiByte , LPCSTR lpDefaultChar , LPBOOL lpUsedDefaultChar);
CodePage:コードページ
dwFlags:処理速度とマッピング方法を決定するフラグ
lpWideCharStr:ワイド文字列のアドレス
cchWideChar:ワイド文字列の文字数
lpMultiByteStr:新しい文字列を受け取るバッファのアドレス
cchMultiByte:新しい文字列を受け取るバッファのサイズ
lpDefaultChar:マップできない文字の既定値のアドレス
lpUsedDefaultChar:既定の文字を使ったときにセットするフラグのアドレス コードページ
定数意味
CP_ACP ANSI コードページ
CP_MACCP Macintosh コードページ
CP_OEMCP OEM コードページ
CP_SYMBOL シンボルコードページ(42)
CP_THREAD_ACP 現在のスレッドの ANSI コードページ
CP_UTF7 UTF-7 を使った変換
CP_UTF8 UTF-8 を使った変換
932 SHIFT-JIS
51932 EUC-JP
50220 JIS.ISO-2022-JP

使用例

以下のソースは、UNICODE文字列へのポインタtをShift-JISに変換し配列sjisに格納します。
WCHAR* t=L"テスト漢字123";
char sjis[128];
WideCharToMultiByte(932, 0, t, -1, sjis, sizeof(sjis), NULL, NULL);

他のエンコーディングをUNICODE文字列に変換する

MultiByteToWideChar APIを使用すると他のエンコーディングをUNICODEに変換できます。
cchWideCharに0を代入してAPIを呼び出すと変換後の文字数が返されますので動的にバッファを確保する場合に使用することができます。

プロトタイプ

int MultiByteToWideChar(UINT CodePage , DWORD dwFlags , LPCSTR lpMultiByteStr , int cchMultiByte, LPWSTR lpWideCharStr , int cchWideChar);
CodePage:コードページ(WideCharToMultiByte参照)
dwFlags:文字の種類を指定するフラグ
lpMultiByteStr:マップ元文字列のアドレス
cchMultiByte:マップ元文字列のバイト数
lpWideCharStr:マップ先ワイド文字列を入れるバッファのアドレス
cchWideChar:バッファのサイズ

使用例

以下のソースは、Shift-JIS文字列へのポインタstrをUNICODE文字列に変換し配列utfに格納します。
char str="テスト漢字123";
WCHAR utf[16];
MultiByteToWideChar(932, 0, str, -1, utf, sizeof(utf) / sizeof(TCHAR));

UNICODEによるファイル入出力

書き込み

何も指定せずに_tfopen_sを使用すると、エンコーディングがANSIになっているので、Shift-JISで書き込まれる。
_tfopen_sの第3引数でエンコーディングを指定するとUNICODEで保存することができます。
エンコーディングにはUNICODEの他、UTF-8またはUTF-16LEを指定することができます。

UNICODE文字列がShift-JISで書き込まれる例

エンコーディングがANSIになっているので、Shift-JISで書き込まれます。
UNICODE・マルチバイトどちらでコンパイルしてもエンコーディングに依存するためShift-JISで書き込まれます。
#include <stdio.h>
#include <tchar.h>
#include <locale.h>

void _tmain(void){
	//	UNICODE文字を標準出力に正しく表示させるためにロケールを設定
	_tsetlocale(LC_ALL, _TEXT(""));

	FILE* fp;
	if(_tfopen_s(&fp, _TEXT("test.txt"), _TEXT("w"))){
		_tprintf(_TEXT("ファイルが開けませんでした。"));
		return;
	}
	_ftprintf(fp, _TEXT("テスト漢字123\n"));
	fclose(fp);
}
書き込まれたファイルを16進ダンプした結果(ファイルサイズ15バイト)
0000	83 65 83 58 83 67 8a bf  8e 9a 31 32 33 0d 0a      テスト漢字123\n

UNICODE文字列がUNICODEで書き込まれる例

UNICODEでコンパイルした場合、_tfopen_sでccs=UNICODEを指定しているのでファイルにUNICODEで書き込まれます。
マルチバイトでコンパイルした場合、ANSI版の関数が使用されます。_ftprintfのANSI版でfprintfが使用されますが、UNICODEに変換して書き込む機能をもたないので実行時にエラーが発生します。 マルチバイトで正常にUNICODEで保存するには、MultiByteToWideChar APIでUNICODEに変換してからファイルに書き込みます。
UNICODEでコンパイルした場合とマルチバイトでコンパイルした場合のコードを切り替えるためにプロジェクトがUNICODEの時定義されるUNICODEマクロと#ifを使用します。
UNICODEとマルチバイト両方のコンパイルをサポートする必要がない場合はもっとシンプルに記述できます。
//	UNICODE文字列をファイルにUNICODE文字列として書き込みプログラム
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#include <locale.h>

void _tmain(void){
	//	UNICODE文字を標準出力に正しく表示させるためにロケールを設定
	_tsetlocale(LC_ALL, _TEXT(""));

	FILE* fp;
#ifdef UNICODE
	if (_tfopen_s(&fp, _TEXT("test.txt"), _TEXT("w,ccs=UNICODE"))){
#else
	if (_tfopen_s(&fp, _TEXT("test.txt"), _TEXT("w"))){
#endif
	_tprintf(_TEXT("ファイルが開けませんでした。"));
		return;
	}
	TCHAR* str=_TEXT("テスト漢字123\n");
#ifdef UNICODE
	_ftprintf(fp, str);
#else
	WCHAR utf[16];
	MultiByteToWideChar(932, 0, str, -1, utf, sizeof(utf) / sizeof(TCHAR));
	fwprintf(fp,utf);
#endif
	fclose(fp);
}
書き込まれたファイルを16進ダンプした結果(ファイルサイズ22バイト)
0000	ff fe c6 30 b9 30 c8 30  22 6f 57 5b 31 00 32 00   テスト漢字123\n
0010	33 00 0d 00 0a 00                                  
ファイルの先頭のff feは、UNICODEテキストを表すBOMです。

読み込み

何も指定せずに_tfopen_sを使用すると、コードページがANSIになっているので、UNICODEファイルは正常に読み込まれない。
_tfopen_sの第3引数でエンコーディングを指定するとUNICODEファイルとして認識されUNICODEとして読み込むことができます。
ただし、正常に読み込めるのはUNICODEでコンパイルした場合で、マルチバイトの場合、正常に読み込まれません。
マルチバイトの場合は、fgetwsでUNICODEとして読み込みWideCharToMultiByte APIでUNICODE文字列をShift-JISに変換します。
//	UNICODEファイルをUNICODE文字列として読み込むプログラム
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#include <locale.h>

void _tmain(void){
	//	UNICODE文字を標準出力に正しく表示させるためにロケールを設定
	_tsetlocale(LC_ALL, _TEXT(""));

	FILE* fp;
	if (_tfopen_s(&fp, _TEXT("test.txt"), _TEXT("r,ccs=UNICODE"))){
		_tprintf(_TEXT("ファイルが開けませんでした。"));
		return;
	}
#ifdef UNICODE
	TCHAR buf[32];
	_fgetts(buf, sizeof(buf) / sizeof(TCHAR), fp);
	_tprintf(_TEXT("%s"),buf);
#else
	WCHAR buf[32];
	char sjis[32];
	fgetws(buf, sizeof(buf) / sizeof(WCHAR), fp);
	WideCharToMultiByte(932, 0, buf, -1, sjis, sizeof(sjis), NULL, NULL);
	printf("%s", sjis);
#endif
	fclose(fp);
}

標準出力へ書き込む

コンソールのコードページはANSIとなっているため、UNICODEの出力は正常にできない。
コードページはコマンドプロンプトでchcpコマンドを実行することで確認できる。
C:¥>chcp
現在のコード ページ: 932

正常に動作しない例

ソースコード
#include <windows.h>
#include <stdio.h>
#include <tchar.h>

void _tmain(void){
	_tprintf(_TEXT("_tprintf関数:漢字出力テスト\n"));
}
実行結果
_tprintf

ロケールを指定

_tsetlocale関数によりロケールを設定するとUNICODE文字列が正常に表示できる。
ソースコード
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <locale.h>

void _tmain(void){
//	UNICODE文字を標準出力に正しく表示させるためにロケールを設定
	_tsetlocale(LC_ALL, _TEXT(""));

	_tprintf(_TEXT("_tprintf関数:漢字出力テスト\n"));
}
実行結果
_tprintf関数:漢字出力テスト

Shift-JISに変換して出力

ソースコード
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
//#include <locale.h>

void _tmain(void){
	TCHAR* t = _TEXT("UNICODEをマルチバイト文字列に変換してからprintf:漢字出力テスト\n");
#ifdef UNICODE
	char sjis[128];
	WideCharToMultiByte(932, 0, t, -1, sjis, sizeof(sjis), NULL, NULL);
	printf("%s", sjis);
#else
	printf("%s", t);
#endif

}
実行結果
UNICODEをマルチバイト文字列に変換してからprintf:漢字出力テスト

UNICODEおよびマルチバイト両方で使える標準関数

UNICODEおよびマルチバイト両方で使える関数の例(実態はUNICODEマクロ等の定義により#defineで別の関数に変換される)
マルチバイト(_MBCSが定義されているとき) UNICODE/マルチバイト両方で使える関数 UNICODEが定義されているとき
main _tmain wmain
fopen _tfopen _wfopen
printf _tprintf wprintf
sprintf _stprintf _swprintf
fputc _fputtc fputwc
fputs _fputts fputws
fgetchar _fgettchar fgetwchar
fgets _fgetts fgetws
strcmp _tcscmp wcscmp
strcpy _tcscpy wcscpy
atoi _ttoi _wtoi
strtod(atofの代用) _tcstod wcstod
isdigit _istdigit iswdigit

UNICODEとマルチバイトでソースの記述を使い分ける方法

プロジェクトの指定がUNICODEの場合、マクロUNICODEが定義されるので、マクロUNICODEが定義されているかを確認してソースを使い分ける。
#ifdef UNICODE
	//	UNICODEの処理を記述します。
#else
	//	マルチバイトの処理を記述します。
#endif