【问题标题】:Accept non ASCII characters接受非 ASCII 字符
【发布时间】:2015-08-30 05:11:03
【问题描述】:

考虑这个程序:

#include <stdio.h>

int main(int argc, char* argv[]) {
   printf("%s\n", argv[1]);  
   return 0;
}

我是这样编译的:

x86_64-w64-mingw32-gcc -o alpha alpha.c

问题是如果我给它一个非 ASCII 参数:

$ ./alpha róisín
r�is�n

如何编写和/或编译该程序以使其接受非 ASCII 人物?回复alk:不,程序打印错误。看 这个例子:

$ echo Ω | od -t x1c
0000000  ce  a9  0a
        316 251  \n
0000003

$ ./alpha Ω | od -t x1c
0000000  4f  0d  0a
          O  \r  \n
0000003

【问题讨论】:

  • 这取决于 MinGW 为创建 argv 数组所做的工作。它是否使用 UTF-8 或 ANSI 对命令行进行编码?如果是ANSI,那么你应该检查MinGW是否支持wmain使用wchar_t *参数。否则,只需忽略陈旧的 ANSI 字符串(IMO,现在整个 ANSI API 都是毫无价值的垃圾,因此经常导致 mojibake)并调用 CommandLineToArgvW 并通过 WideCharToMultiByte 手动编码为 UTF-8,如果您需要 char * 字符串。
  • 您的更新证明 MinGW 正在调用 GetCommandLineA 以获取 ANSI 编码的命令行副本,因此您得到 mojibake "Ω" => "O",因为这是您的最接近的映射ANSI 字符集(可能是 1252)用于希腊语 Omega 字符。这是毫无价值的。使用 GetCommandLineWCommandLineToArgvWWideCharToMultibyte 获取 UTF-8 编码的命令行参数。

标签: c windows argv non-ascii-characters


【解决方案1】:

最简单的方法是使用wmain

#include <fcntl.h>
#include <stdio.h>

int wmain (int argc, wchar_t** argv) {
  _setmode(_fileno(stdout), _O_WTEXT);
  wprintf(L"%s\n", argv[1]);
  return 0;
}

也可以用GetCommandLineW完成;这是代码的简单版本 在HandBrake repo找到:

#include <stdio.h>
#include <windows.h>

int get_argv_utf8(int* argc_ptr, char*** argv_ptr) {
  int argc;
  char** argv;
  wchar_t** argv_utf16 = CommandLineToArgvW(GetCommandLineW(), &argc);
  int i;
  int offset = (argc + 1) * sizeof(char*);
  int size = offset;
  for (i = 0; i < argc; i++)
    size += WideCharToMultiByte(CP_UTF8, 0, argv_utf16[i], -1, 0, 0, 0, 0);
  argv = malloc(size);
  for (i = 0; i < argc; i++) {
    argv[i] = (char*) argv + offset;
    offset += WideCharToMultiByte(CP_UTF8, 0, argv_utf16[i], -1,
      argv[i], size-offset, 0, 0);
  }
  *argc_ptr = argc;
  *argv_ptr = argv;
  return 0;
}

int main(int argc, char** argv) {
  get_argv_utf8(&argc, &argv);
  printf("%s\n", argv[1]);
  return 0;
}

【讨论】:

  • 由于您使用的是 UTF-8 而不是 Windows 本机 UTF-16,因此无论何时调用 Windows API,您都必须转换回 UTF-16。例如,假设用户在命令行中传递了一个文件名,您必须先调用MultiByteToWideChar 转换为UTF-16,然后才能通过CreateFileW(或_wfopen_wopen 等)打开此文件。
  • fopen 调用 _open,后者调用 ANSI API CreateFileA。这将使用系统的 ANSI 代码页(例如 1252)将文件名解码为本机 UTF-16。因此,如果字符串不是全是 ASCII 字符,则会出现 mojibake 和未找到文件错误。要在 Windows 上解决此问题,您必须改为通过MultiByteToWideChar 进行转换,然后调用_wfopen,后者调用_wopen,后者调用CreateFileW。您可能想要创建一个辅助函数 my_fopen 或类似的东西以避免预处理器地狱。
  • @eryksun 我不明白。为什么我必须转换为 UTF-8 才能将其转换回 UTF-16?这是“我做错了”的例子还是另一个 Windows 很糟糕的例子?
  • 如果您不想在 Windows 上支持完整的 unicode,只需坚持使用 ANSI API。然后,如果用户传递的文件名不能用他们的 ANSI 代码页表示,告诉他们太糟糕了,在 Windows 上支持 unicode 工作量太大。如果您不喜欢给出这个答案,那么使用 C/C++ 以跨平台方式支持 unicode 恐怕真的需要做很多工作。几乎所有其他操作系统都选择使用 UTF-8 来适应早于 unicode 的 char * API。 Windows 是使用 UTF-16 的奇怪鸭子,因为它是 90 年代初 wchar_t * 和 UCS-2 的早期采用者。
  • 当我说 Windows 使用 UTF-16 时,我的意思是一直到内核。例如,CreateFile API 是一个用户模式函数,它在进行系统调用NtCreateFile 之前完成初步工作。在内核中,对象路径使用OBJECT_ATTRIBUTES 记录,该记录将路径本身存储为UNICODE_STRING。这是一个经过计数的宽字符串,最多可包含 32768 个字符。
【解决方案2】:

由于您使用的是 MinGW(实际上是 MinGW-w64,但在这种情况下这无关紧要),您可以访问 Windows API,因此以下内容应该适合您。它可能更干净并且经过正确测试,但至少应该提供一个好主意:

#define _WIN32_WINNT 0x0600
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>

#include <windows.h>

int main (void)
{
    int       argc;
    int       i;
    LPWSTR    *argv;

    argv = CommandLineToArgvW(GetCommandLineW(), &argc);
    if (argv == NULL)
    {
        FormatMessageA(
            (
                FORMAT_MESSAGE_ALLOCATE_BUFFER |
                FORMAT_MESSAGE_FROM_SYSTEM |
                FORMAT_MESSAGE_IGNORE_INSERTS),
            NULL,
            GetLastError(),
            0,
            (LPWSTR)&error, 0,
            NULL);

        fprintf(stderr, error);
        fprintf(stderr, "\n");
        LocalFree(error);
        return EXIT_FAILURE;
    }

    for (i = 0; i < argc; ++i)
        wprintf(L"argv[%d]: %ls\n", i, argv[i]);

    // You must free argv using LocalFree!
    LocalFree(argv);

    return 0;
}

请记住这一点:Windows 不会为您编写字符串。我使用自己的 Windows 键盘布局,它使用组合字符(我很奇怪),所以当我输入

example -o àlf

在我的 Windows 命令提示符中,我得到以下输出:

argv[0]: example
argv[1]: -o
argv[2]: a\u0300lf

a\u0300U+0061 (LATIN SMALL LETTER A),后跟 Unicode 代码点 U+0300 (COMBINING GRAVE ACCENT) 的表示形式。如果我改为使用

example -o àlf

使用预组合字符U+00E0 (LATIN SMALL LETTER A WITH GRAVE),输出会有所不同:

argv[0]: example
argv[1]: -o
argv[2]: \u00E0lf

其中\u00E0 是由Unicode 代码点U+00E0 表示的预组合字符à 的表示。然而,虽然我可能是一个奇怪的人这样做,Vietnamese code page 1258 实际上包括组合字符。这通常不会影响文件名处理,但可能会遇到一些困难。

对于只是字符串的参数,您可能需要使用NormalizeString 函数来研究规范化。其中链接的文档和示例应该可以帮助您了解该功能的工作原理。规范化和 Unicode 中的其他一些事情可能是一段漫长的旅程,但如果这类事情让您兴奋,这也是一段有趣的旅程。

【讨论】:

  • 我无法使用 wmain,因为我的编译器不知道存在此类东西,但您的解决方案对我有用。我不得不修改它,所以我像你一样使用空的 main ,然后 CommandLineToArgvW 来读取参数,这样它们就不会被修改为我的程序的垃圾然后我将程序中的 chcp 设置为 852,然后将语言环境设置为斯洛伐克语,现在字符就像一个魅力 ;) 最后我可以在我们公司的服务器上读取带有特殊字符的文件/文件夹。
【解决方案3】:

尝试编译并运行以下程序:

#include <stdio.h>

int main()
{
    int i = 0;

        for( i=0; i<256; i++){
            printf("\nASCII Character #%d:%c ", i, i);
        }

        printf("\n");

    return 0;
}

在您的输出中,您应该会看到从 128 号开始的小问号。仅供参考,我使用的是 Ubuntu,当我编译和运行这个程序(使用 GNOME 终端)时,这也发生在我身上。

但是,如果我转到 Terminal > Set character encoding... 并选择 Western (WINDOWS-1252) 而不是 Unicode (UTF-8),然后重新运行程序,扩展的 ASCII 字符会正确显示。

我不知道 Windows/MinGW 的确切步骤,但简而言之,更改字符编码应该可以解决您的问题

【讨论】:

  • 更新:刚刚尝试自己运行您的程序,结果证明,它适用于 UTF-8,并使用 WINDOS-1252 打印错误字符。诡异的。好吧,无论如何,您仍然应该尝试我上面的建议,看看会发生什么。如果有经验更丰富的人能够对这些平台差异提供更深入的了解,那就太好了。
  • @Steve Penny 我的回答怎么没有解决这个问题?我建议在尝试在命令行参数中的非 ASCII 字符和程序输出之间建立连接时更改字符编码
  • @Steve Penny 我只是想帮忙。即使我在不​​同的平台上,相同的步骤也可以解决 OP 的问题。如果他们不这样做,那么其他人或者您可能会提供更好的帮助?编辑:完全错过了你是 OP 的事实,对不起
  • @Steven Penny 大声笑看到我之前评论中的编辑 :) 无论如何,你真的尝试过我的解决方案(更改字符编码)吗?让我知道结果。如果它不起作用,让我们希望其他人可以回答这个问题。祝你好运
  • @Frank 不幸的是,Windows 仍然依赖于它的旧字符集。您可以更改“代码页”,但所做的只是更改您可以使用的字符。 UTF-8 不是那些“代码页”之一。这是假设您可以将代码页更改为所需的代码页。不幸的是,Windows 在这方面仍然停留在过去。
猜你喜欢
  • 1970-01-01
  • 2011-06-20
  • 1970-01-01
  • 1970-01-01
  • 2011-06-05
  • 1970-01-01
  • 1970-01-01
  • 2016-12-21
  • 1970-01-01
相关资源
最近更新 更多