Unicode が普及した現在では、macOS や Linux などでは UTF-8 を前提としても概ね問題ないでしょう。すると Windows の main も UTF-8 にしたくなります。
main の引数を UTF-8 に設定する
main の引数を UTF-8 にするため、以下を設定します。
ソース コードも UTF-8 が想定されるので、コンパイル オプション /utf-8 も設定しておきます。
コンソールのコードページ
日本語版 Windows の既定ではコードページが 932 なので
- 入力:
SetConsoleCP(CP_UTF8)
- 出力:
SetConsoleOutputCP(CP_UTF8)
を設定しないと文字化けします。
#include <stdio.h>
static int utf8len(unsigned char c)
{
// if (c < 0x80) return 1;
if (c < 0xc0) return 1; // error
if (c < 0xe0) return 2;
if (c < 0xf0) return 3;
if (c < 0xf8) return 4;
return 1; // error
}
static unsigned toucs(const unsigned char* p)
{
switch (utf8len(p[0]))
{
default:
return p[0];
case 2:
return (((p[0] & 0x1f) << 6) |
((p[1] & 0x3f) << 0));
case 3:
return (((p[0] & 0x0f) << 12) |
((p[1] & 0x3f) << 6) |
((p[2] & 0x3f) << 0));
case 4:
return (((p[0] & 0x07) << 18) |
((p[1] & 0x3f) << 12) |
((p[2] & 0x3f) << 6) |
((p[3] & 0x3f) << 0));
}
}
static void print_argv_utf8(int i, const char* p)
{
const unsigned char* q = (const unsigned char*)p;
printf("argv[%d]: %s\n", i, p);
while (*q)
{
size_t nc = utf8len(*q);
unsigned ucs = toucs(q);
printf(" char[%d] =", (int)((const char*)q - p));
switch (nc)
{
case 1:
printf("'%.1s' : %#04x\n", q, q[0]);
break;
case 2:
printf("'%.2s' : %#04x, %#04x -> %#05x\n", q, q[0], q[1], ucs);
break;
case 3:
printf("'%.3s' : %#04x, %#04x, %#04x -> %#06x\n", q, q[0], q[1], q[2], ucs);
break;
case 4:
printf("'%.4s' : %#04x, %#04x, %#04x, %#04x -> %#08x\n", q, q[0], q[1], q[2], q[3], ucs);
break;
}
q += nc;
}
}
int main(int argc, char** argv)
{
int i;
printf("argc: %d\n", argc);
for (i = 1; i < argc; i++)
print_argv_utf8(i, argv[i]);
return 0;
}
argc: 2
argv[1]: 圧縺ョ.txt
char[0] ='圧' : 0xf0, 0x9f, 0x88, 0xb3 -> 0x01f233
char[4] ='縺ョ' : 0xe3, 0x81, 0xae -> 0x306e
char[7] ='.' : 0x2e
char[8] ='t' : 0x74
char[9] ='x' : 0x78
char[10] ='t' : 0x74
見事に化けました。SetConsoleOutputCP(CP_UTF8) を使います。
diff -u main_utf8.cpp main_utf8a.cpp
--- main_utf8.cpp 2024-05-23 20:15:44
+++ main_utf8a.cpp 2024-05-23 20:16:44
@@ -1,4 +1,5 @@
#include <stdio.h>
+#include <Windows.h>
static int utf8len(unsigned char c)
{
@@ -65,6 +66,7 @@
{
int i;
+ SetConsoleOutputCP(CP_UTF8);
printf("argc: %d\n", argc);
for (i = 1; i < argc; i++)
print_argv_utf8(i, argv[i]);
argc: 2
argv[1]: 🈳の.txt
char[0] ='🈳' : 0xf0, 0x9f, 0x88, 0xb3 -> 0x01f233
char[4] ='の' : 0xe3, 0x81, 0xae -> 0x306e
char[7] ='.' : 0x2e
char[8] ='t' : 0x74
char[9] ='x' : 0x78
char[10] ='t' : 0x74
Windows バージョン 1903 より前での実行
ドキュメントにあるように、上記の方法で作ったプログラムは Windows バージョン 1903 より前で実行すると UTF-8 ではなくなるので、古めの Windows で実行するとなると wmain を使用した方がいいようです。
wmain を使う場合、wmain の引数を wchar_t から UTF-8 に変換して main を呼び出す必要があります。すると、main 関数と wmain 関数がリンク時に両方存在することになるので、Visual Studio の既定動作では
LINK : warning LNK4067: エントリ ポイントがあいまいです。'mainCRTStartup' が選択されます。
となって、wmain が呼び出されません。wmain から開始するには、プロジェクトのプロパティで、エントリ ポイントを wmainCRTStartup にします。
このオプションを使わない方法として
#define main _main_utf8
などとして main の名称を変更すると、コンパイル結果としての main 関数は存在しないので wmain から開始されます。
// ----------------------------------------------------------------------------
// Windows のみ CRT と main の間に wmain が介在する形式になる
// ----------------------------------------------------------------------------
#ifdef _MSC_VER
#include <Windows.h>
#include <string>
// オプション wmainCRTStartup を不要にする措置.
#define main _main_utf8
int main(int argc, char** argv, char** envp);
// サロゲートペア第1文字?
static bool issurrogate1(wchar_t c)
{
return (0xd800 <= c && c < 0xdc00);
}
// サロゲートペア第2文字?
static bool issurrogate2(wchar_t c)
{
return (0xdc00 <= c && c < 0xe000);
}
/*
* 変換に必要な情報を得る
*/
struct arginfo
{
size_t size; // 変換後のサイズ.
size_t count; // 文字列の数.
wchar_t **pp;
};
static size_t utf8slen(const wchar_t* s)
{
size_t r = 1;
unsigned c;
while ((c = *s++))
{
++r;
if (c < 0x80)
continue;
++r;
if (c < 0x800)
continue;
++r;
if (!issurrogate1(c))
continue;
if (!issurrogate2(*s))
continue;
++s;
++r;
}
return r;
}
static arginfo argslen(wchar_t** pp)
{
arginfo r = { sizeof(char*) + 1, 0, pp };
while (pp[r.count])
r.size += utf8slen(pp[r.count++]);
r.size += r.count * sizeof(char*);
return r;
}
/*
* 文字列リストの変換
*/
static size_t cnvstr(char *d, wchar_t *s)
{
unsigned c;
wchar_t c1, c2;
char *p = d;
while ((c1 = *s++))
{
if (c1 < 0x80)
{
*p++ = char(c1);
continue;
}
if (c1 < 0x800)
{
*p++ = char(0xc0 | ((c1 >> 6) /* & 0x1f */));
*p++ = char(0x80 | ((c1 >> 0) & 0x3f));
continue;
}
if (issurrogate1(c1))
{
c2 = *s;
if (issurrogate2(c2))
{
++s;
c = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff);
*p++ = char(0xf0 | ((c >> 18) /* & 0x07 */));
*p++ = char(0x80 | ((c >> 12) & 0x3f));
*p++ = char(0x80 | ((c >> +6) & 0x3f));
*p++ = char(0x80 | ((c >> +0) & 0x3f));
continue;
}
}
*p++ = char(0xe0 | ((c1 >> 12) /* & 0x0f */));
*p++ = char(0x80 | ((c1 >> +6) & 0x3f));
*p++ = char(0x80 | ((c1 >> +0) & 0x3f));
// continue;
}
*p++ = 0;
return p - d;
}
static char **cnvarg(void *buffer, arginfo &ai)
{
char **pp = (char**)buffer;
char *p = (char*)(pp + ai.count + 1);
for (size_t i = 0; i < ai.count; ++i)
p += cnvstr((*pp++ = p), ai.pp[i]);
*pp = NULL;
*p = 0;
return (char**)buffer;
}
/*
* wmain
*/
int wmain(int argc, wchar_t** argv, wchar_t** envp)
{
// コンソールの入出力を UTF-8 にする.
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
// スタック上に文字列リストを作成する.
arginfo ai = argslen(argv);
arginfo ei = argslen(envp);
char **ap = cnvarg(_alloca(ai.size), ai);
char **ep = cnvarg(_alloca(ei.size), ei);
return main(argc, ap, ep);
}
#endif /* _MSC_VER */
// ----------------------------------------------------------------------------
// 以下、main 関数を含むプログラム
// ----------------------------------------------------------------------------
#include <stdio.h>
static int utf8len(unsigned char c)
{
// if (c < 0x80) return 1;
if (c < 0xc0) return 1; // error
if (c < 0xe0) return 2;
if (c < 0xf0) return 3;
if (c < 0xf8) return 4;
return 1; // error
}
static unsigned toucs(const unsigned char *p)
{
switch (utf8len(p[0]))
{
default:
return p[0];
case 2:
return (((p[0] & 0x1f) << 6) |
((p[1] & 0x3f) << 0));
case 3:
return (((p[0] & 0x0f) << 12) |
((p[1] & 0x3f) << 6) |
((p[2] & 0x3f) << 0));
case 4:
return (((p[0] & 0x07) << 18) |
((p[1] & 0x3f) << 12) |
((p[2] & 0x3f) << 6) |
((p[3] & 0x3f) << 0));
}
}
static void print_argv(int i, const char* p)
{
const unsigned char *q = (const unsigned char *)p;
printf("argv[%d]: \"%s\"\n", i, p);
while (*q)
{
size_t nc = utf8len(*q);
unsigned ucs = toucs(q);
printf(" char[%d] =", (int)((const char*)q - p));
switch (nc)
{
case 1:
printf("'%.1s' : %#04x\n", q, q[0]);
break;
case 2:
printf("'%.2s' : %#04x, %#04x -> %#05x\n", q, q[0], q[1], ucs);
break;
case 3:
printf("'%.3s' : %#04x, %#04x, %#04x -> %#06x\n", q, q[0], q[1], q[2], ucs);
break;
case 4:
printf("'%.4s' : %#04x, %#04x, %#04x, %#04x -> %#08x\n", q, q[0], q[1], q[2], q[3], ucs);
break;
}
q += nc;
}
}
int main(int argc, char** argv, char** envp)
{
int i;
printf("wchar_t: %zd\n", sizeof(wchar_t));
printf("argc: %d\n", argc);
for (i = 1; i < argc; i++)
print_argv(i, argv[i]);
(void)envp;
return 0;
}
argc: 2
argv[1]: "🈳の.txt"
char[0] ='🈳' : 0xf0, 0x9f, 0x88, 0xb3 -> 0x01f233
char[4] ='の' : 0xe3, 0x81, 0xae -> 0x306e
char[7] ='.' : 0x2e
char[8] ='t' : 0x74
char[9] ='x' : 0x78
char[10] ='t' : 0x74