Принимать символы, отличные от ASCII

Рассмотрим эту программу:

#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?

Чтобы ответить на спросить: нет, программа печатает неправильно. См. этот пример:

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

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

person Steven Penny    schedule 14.06.2015    source источник
comment
Это зависит от того, что делает MinGW для создания массива argv. Он кодирует командную строку, используя UTF-8 или ANSI? Если это ANSI, вам следует проверить, поддерживает ли MinGW wmain для использования wchar_t * параметров. В противном случае просто игнорируйте ветхие строки ANSI (ИМХО, весь ANSI API в настоящее время является бесполезным мусором, который так часто приводит к моджибаке) и вызовите CommandLineToArgvW и вручную закодировать в UTF-8 через WideCharToMultiByte если вам нужно char * строк.   -  person Eryk Sun    schedule 14.06.2015
comment
Ваше обновление доказывает, что MinGW вызывает GetCommandLineA для получения копии командной строки в кодировке ANSI, и поэтому вы получаете моджибаке Ω => O, поскольку это самое близкое сопоставление вашего набора символов ANSI (вероятно, 1252) для греческого символа Omega. Это бесполезно. Используйте GetCommandLineW, CommandLineToArgvW и WideCharToMultibyte для получения аргументов командной строки в кодировке UTF-8.   -  person Eryk Sun    schedule 14.06.2015


Ответы (3)


Проще всего это сделать с помощью 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 :

#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;
}
person Steven Penny    schedule 15.06.2015
comment
fopen вызывает _open, который вызывает ANSI API CreateFileA. Это приведет к декодированию имени файла в собственный UTF-16 с использованием системной кодовой страницы ANSI, например 1252. Поэтому, если строка состоит не только из символов ASCII, вы получите ошибку mojibake и файл не найден. Чтобы обойти это в Windows, вам придется вместо этого преобразовать через MultiByteToWideChar, а затем вызвать _wfopen, который вызывает _wopen, который вызывает CreateFileW. Вы можете создать вспомогательную функцию my_fopen или что-то в этом роде, чтобы избежать ада препроцессора. - person Eryk Sun; 15.06.2015
comment
@eryksun Я не понимаю. Почему мне нужно преобразовать в UTF-8 только для того, чтобы преобразовать его обратно в UTF-16? Это случай, когда я делаю это неправильно, или еще один пример того, что Windows ужасна? - person Steven Penny; 15.06.2015
comment
Если вы не хотите поддерживать полный юникод в Windows, просто придерживайтесь ANSI API. Затем, если пользователь передает имя файла, которое не может быть представлено его кодовой страницей ANSI, скажите ему, что слишком много работы для поддержки Unicode в Windows. Если вам не нравится давать этот ответ, то я боюсь, что действительно будет много работы по поддержке Unicode кросс-платформенным способом с использованием C/C++. Почти все остальные операционные системы решили адаптировать char * API-интерфейсы, предшествующие Unicode, с помощью UTF-8. Windows — странная утка, использующая UTF-16, потому что она была одной из первых, кто внедрил wchar_t * и UCS-2 в начале 90-х. - person Eryk Sun; 15.06.2015
comment
Когда я говорю, что Windows использует UTF-16, я имею в виду вплоть до ядра. Например, CreateFile API — это функция пользовательского режима, которая выполняет предварительную работу перед системным вызовом NtCreateFile. В ядре пути к объектам используют запись OBJECT_ATTRIBUTES, в которой хранится сам путь. как UNICODE_STRING. Это подсчитываемая строка широких символов, которая может содержать до 32768 символов. - person Eryk Sun; 15.06.2015

Поскольку вы используете 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\u0300 – это U+0061 (LATIN SMALL LETTER A), за которым следует представление кодовой точки Юникода 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. Однако, хотя я могу быть странным человеком для этого, вьетнамская кодовая страница 1258 на самом деле включает в себя комбинирование символов. Обычно это не должно влиять на обработку имен файлов, но могут возникнуть некоторые трудности.

Для аргументов, которые являются просто строками, вы можете изучить нормализацию с помощью NormalizeString. Документация и примеры, связанные с ней, должны помочь вам понять, как работает функция. Нормализация и некоторые другие вещи в Unicode могут оказаться долгим путешествием, но если вас это волнует, это также веселое путешествие.

person Community    schedule 14.06.2015
comment
Я не мог использовать wmain, потому что мой компилятор не знал, что такие вещи существуют, но ваше решение сработало для меня. Мне пришлось изменить его, поэтому я использовал пустой main, как и вы, затем CommandLineToArgvW для чтения аргументов, чтобы они не превращались в мусор для моей программы, затем я установил chcp в своей программе на 852, а затем установил словацкий язык, и теперь символы работают как очарование ;) и, наконец, я могу читать файлы/папки со специальными символами на сервере нашей компании. - person Martin Krajčírovič; 20.01.2021

Попробуйте скомпилировать и запустить следующую программу:

#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), это происходит и со мной.

Однако, если я выберу «Терминал» > «Установить кодировку символов...» и выберу Western (WINDOWS-1252), а не Unicode (UTF-8), и перезапущу программу, расширенные символы ASCII отобразятся правильно.

Я не знаю точных шагов для Windows/MinGW, но, короче говоря, изменение кодировки символов должно решить вашу проблему.

person Frank    schedule 14.06.2015
comment
ОБНОВЛЕНИЕ: только что попробовал запустить вашу программу самостоятельно, и, как оказалось, она хорошо работает с UTF-8 и печатает неправильные символы с WINDOS-1252. Странный. Ну, в любом случае, вы все равно должны попробовать мое предложение выше и посмотреть, что произойдет. Если бы кто-то более опытный мог дать больше информации об этих различиях платформ, это было бы здорово. - person Frank; 14.06.2015
comment
@ Стив Пенни Как мой ответ не решает проблему? Я предлагаю изменить кодировку символов, пытаясь установить связь между символами, отличными от ASCII, в аргументах командной строки и выводе программы. - person Frank; 14.06.2015
comment
@ Стив Пенни, я просто пытаюсь помочь. Несмотря на то, что я на другой платформе, те же шаги могут решить проблему OP. Если они этого не сделают, тогда кто-то другой или, возможно, ВЫ могли бы лучше помочь? РЕДАКТИРОВАТЬ: полностью пропустил тот факт, что вы ОП, извините - person Frank; 14.06.2015
comment
@Steven Penny, см. редактирование в моем предыдущем комментарии :) В любом случае, вы действительно пробовали мое решение (изменить кодировку символов)? Сообщите мне результат. Если это не сработает, будем надеяться, что кто-то другой может ответить на это. Удачи - person Frank; 14.06.2015
comment
@Frank К сожалению, Windows по-прежнему использует свои устаревшие наборы символов. Вы можете изменить кодовую страницу, но все, что вам нужно сделать, это изменить доступные вам символы. UTF-8 не является одной из этих кодовых страниц. И это при условии, что вы можете изменить кодовую страницу на желаемую. К сожалению, в этом отношении Windows все еще застряла в прошлом. - person ; 14.06.2015
comment
@Chrono Kitsune Спасибо за ваш вклад. К OP: программирование и компиляция в GNU / Linux не подходят для этого? Избавит вас от многих проблем - person Frank; 14.06.2015