1
1

Windows でも main 関数の引数を UTF-8 に

Posted at

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)

を設定しないと文字化けします。

main_utf8.cpp
#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;
}
引数「🈳の.txt」の実行結果
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) を使います。

main_utf8.cppとの差分
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]);
引数「🈳の.txt」の実行結果
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 にします。

Visual Studio プロパティ.png

このオプションを使わない方法として

#define main _main_utf8

などとして main の名称を変更すると、コンパイル結果としての main 関数は存在しないので wmain から開始されます。

wmain.cpp
// ----------------------------------------------------------------------------
// 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;
}
引数「🈳の.txt」の実行結果
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
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1