Как создать строковый литерал UTF-8 в Visual C ++ 2008 - PullRequest
61 голосов
/ 27 марта 2009

В VC ++ 2003 я мог просто сохранить исходный файл как UTF-8, и все строки использовались как есть. Другими словами, следующий код будет выводить строки как есть на консоль. Если исходный файл был сохранен как UTF-8, тогда вывод будет UTF-8.

printf("Chinese (Traditional)");
printf("中国語 (繁体)");
printf("중국어 (번체)");
printf("Chinês (Tradicional)");

Я сохранил файл в формате UTF-8 с помощью спецификации UTF-8. Однако компиляция с VC2008 приводит к:

warning C4566: character represented by universal-character-name '\uC911' 
cannot be represented in the current code page (932)
warning C4566: character represented by universal-character-name '\uAD6D' 
cannot be represented in the current code page (932)
etc.

Символы, вызывающие эти предупреждения, повреждены. Те, которые соответствуют локали (в данном случае 932 = японский), преобразуются в кодировку локали, то есть Shift-JIS.

Я не могу найти способ заставить VC ++ 2008 скомпилировать это для меня. Обратите внимание, что не имеет значения, какую локаль я использую в исходном файле. Кажется, не существует локали, которая говорит: «Я знаю, что я делаю, поэтому не меняйте мои строковые литералы». В частности, бесполезная псевдо-локаль UTF-8 не работает.

#pragma setlocale(".65001") 
=> error C2175: '.65001' : invalid locale

Ни один не делает "C":

#pragma setlocale("C") 
=> see warnings above (in particular locale is still 932)

Похоже, что VC2008 принудительно переводит все символы в указанную (или по умолчанию) локаль, и эта локаль не может быть UTF-8. Я не хочу изменять файл для использования escape-строк, таких как "\ xbf \ x11 ...", потому что тот же исходный код скомпилирован с использованием gcc, который вполне может работать с файлами UTF-8.

Есть ли способ указать, что компиляция исходного файла должна оставлять строковые литералы нетронутыми?

Иными словами, какие флаги компиляции я могу использовать, чтобы указать обратную совместимость с VC2003 при компиляции исходного файла. не меняйте строковые литералы, используйте их как байты за байтами.

Обновление

Спасибо за предложения, но я хочу избежать wchar. Поскольку это приложение имеет дело только со строками в UTF-8, использование wchar потребует от меня преобразования всех строк обратно в UTF-8, что не нужно. Все входные, выходные и внутренние обработки в UTF-8. Это простое приложение, которое отлично работает как в Linux, так и при компиляции с VC2003. Я хочу иметь возможность скомпилировать то же самое приложение с VC2008 и заставить его работать.

Чтобы это произошло, мне нужен VC2008, чтобы не пытаться преобразовать его в локаль моей локальной машины (японский, 932). Я хочу, чтобы VC2008 был обратно совместим с VC2003. Я хочу, чтобы настройка локали или компилятора гласила, что строки используются как есть, по существу как непрозрачные массивы char или как UTF-8. Похоже, я застрял с VC2003 и gcc, хотя VC2008 в этом случае пытается быть слишком умным.

Ответы [ 17 ]

32 голосов
/ 30 марта 2009

Обновление:

Я решил, что не существует гарантированного способа сделать это. Решение, которое я представляю ниже, работает для английской версии VC2003, но не удается при компиляции с японской версией VC2003 (или, возможно, это японская ОС). В любом случае, это не может зависеть от работы. Обратите внимание, что даже объявление всего как строк "L" "не сработало (и это болезненно для gcc, как описано ниже).

Вместо этого я считаю, что вам просто нужно стиснуть зубы, переместить весь текст в файл данных и загрузить его оттуда. Сейчас я храню и получаю доступ к тексту в файлах INI через SimpleIni (кросс-платформенная библиотека INI-файлов). По крайней мере, есть гарантия, что он работает, поскольку весь текст находится вне программы.

Оригинал:

Я отвечаю на это сам, так как только Эван, казалось, понял проблему. Ответы относительно того, что такое Юникод и как использовать wchar_t, не имеют отношения к этой проблеме, так как речь не идет об интернационализации или неправильном понимании кодировки символов Юникода. Я ценю вашу попытку помочь, извините, если я не достаточно ясно.

Проблема в том, что у меня есть исходные файлы, которые нужно кросс-компилировать на различных платформах и компиляторах. Программа выполняет обработку UTF-8. Это не заботится о любых других кодировках. Я хочу иметь строковые литералы в UTF-8, как в настоящее время работает с gcc и vc2003. Как мне сделать это с VC2008? (т.е. обратно совместимое решение).

Вот что я нашел:

gcc (v4.3.2 20081105):

  • строковые литералы используются как есть (необработанные строки)
  • поддерживает исходные файлы в кодировке UTF-8
  • исходные файлы не должны иметь спецификацию UTF-8

VC2003:

  • строковые литералы используются как есть (необработанные строки)
  • поддерживает исходные файлы в кодировке UTF-8
  • исходные файлы могут иметь или не иметь спецификацию UTF-8 (это не имеет значения)

VC2005 +:

  • строковые литералы массируются компилятором (без необработанных строк)
  • литералы строки символов перекодируются в указанную локаль
  • UTF-8 не поддерживается в качестве целевой локали
  • исходные файлы должны иметь спецификацию UTF-8

Итак, простой ответ заключается в том, что для этой конкретной цели VC2005 + не работает и не предоставляет обратно совместимый путь компиляции. Единственный способ получить строки Unicode в скомпилированную программу - через UTF-8 + BOM + wchar, что означает, что мне нужно преобразовать все строки обратно в UTF-8 во время использования.

Нет простого кроссплатформенного метода преобразования wchar в UTF-8, например, в какой размер и кодировку входит wchar? На Windows, UTF-16. На других платформах? Различается. Подробности см. В проекте ICU .

В итоге я решил, что буду избегать затрат на конвертацию на всех компиляторах, кроме vc2005 + с источником, подобным следующему.

#if defined(_MSC_VER) && _MSC_VER > 1310
// Visual C++ 2005 and later require the source files in UTF-8, and all strings 
// to be encoded as wchar_t otherwise the strings will be converted into the 
// local multibyte encoding and cause errors. To use a wchar_t as UTF-8, these 
// strings then need to be convert back to UTF-8. This function is just a rough 
// example of how to do this.
# define utf8(str)  ConvertToUTF8(L##str)
const char * ConvertToUTF8(const wchar_t * pStr) {
    static char szBuf[1024];
    WideCharToMultiByte(CP_UTF8, 0, pStr, -1, szBuf, sizeof(szBuf), NULL, NULL);
    return szBuf;
}
#else
// Visual C++ 2003 and gcc will use the string literals as is, so the files 
// should be saved as UTF-8. gcc requires the files to not have a UTF-8 BOM.
# define utf8(str)  str
#endif

Обратите внимание, что этот код является просто упрощенным примером. Производственное использование потребовало бы его очистки различными способами (безопасность потоков, проверка ошибок, проверка размера буфера и т. Д.).

Используется как следующий код. В моих тестах на gcc, vc2003 и vc2008 он корректно компилируется и работает правильно:

std::string mText;
mText = utf8("Chinese (Traditional)");
mText = utf8("中国語 (繁体)");
mText = utf8("중국어 (번체)");
mText = utf8("Chinês (Tradicional)");
16 голосов
/ 01 сентября 2009

Brofield,

У меня была точно такая же проблема, и я наткнулся на решение, которое не требует преобразования ваших исходных строк в широкие символы и обратно: сохраните ваш исходный файл как UTF-8 без подписи и VC2008 уйдет это один. Отлично сработало, когда я решил сбросить подпись. Подведем итог:

Unicode (UTF-8 без подписи) - кодовая страница 65001, не выдает предупреждение c4566 в VC2008 и не заставляет VC связываться с кодировкой, в то время как кодовая страница 65001 (UTF-8 с подписью) выдает c4566 ( как вы нашли).

Надеюсь, вам еще не поздно помочь, но это может ускорить ваше приложение VC2008, чтобы убрать ваш обходной путь.

16 голосов
/ 29 марта 2009

Хотя, вероятно, лучше использовать широкие строки, а затем при необходимости конвертировать в UTF-8. Я думаю, что ваш лучший выбор - как вы уже упоминали, использовать шестнадцатеричные экранированные символы в строках. Например, если вы хотите получить кодовую точку \uC911, вы можете просто сделать это.

const char *str = "\xEC\xA4\x91";

Я считаю, что это будет работать просто отлично, но не очень читабельно, поэтому, если вы сделаете это, пожалуйста, прокомментируйте это, чтобы объяснить.

14 голосов
/ 09 марта 2010

Файл / Дополнительные параметры сохранения / Кодировка: «Unicode (UTF-8 без подписи ) - кодовая страница 65001»

8 голосов
/ 19 июля 2012

Стандартное поведение Visual C ++ (2005+) COMPILER для исходных файлов:

  • CP1252 (для этого примера западноевропейская кодовая страница):
    • "Ä"C4 00
    • 'Ä'C4
    • L"Ä"00C4 0000
    • L'Ä'00C4
  • UTF-8 без спецификации:
    • "Ä"C3 84 00 (= UTF-8)
    • 'Ä' → предупреждение: многосимвольная константа
    • "Ω"E2 84 A6 00 (= UTF-8, как и ожидалось)
    • L"A"00C3 0084 0000 (неправильно!)
    • L'Ä' → предупреждение: многосимвольная константа
    • L"Ω"00E2 0084 00A6 0000 (неправильно!)
  • UTF-8 с спецификацией:
    • "Ä"C4 00 (= CP1252, не более UTF-8),
    • 'Ä'C4
    • "Ω" → ошибка: невозможно преобразовать в CP1252!
    • L"Ä"00C4 0000 (правильно)
    • L'Ä'00C4
    • L"Ω"2126 0000 (правильно)

Видите ли, компилятор C обрабатывает файлы UTF-8 без спецификации так же, как CP1252. В результате компилятор не может смешивать строки UTF-8 и UTF-16 в скомпилированный вывод! Таким образом, вы должны выбрать один файл исходного кода:

  • либо использовать UTF-8 с спецификацией и генерировать только строки UTF-16 (т.е. всегда использовать префикс L),
  • или UTF-8 без спецификации и генерировать только строки UTF-8 (т.е. никогда не использовать префикс L).
  • 7-битные символы ASCII не участвуют и могут использоваться с или без L префикса

Независимо, редактор может автоматически определять файлы UTF-8 без спецификации как файлы UTF-8.

6 голосов
/ 14 февраля 2012

Из комментария к этому очень хорошему блогу
«Использование UTF-8 в качестве внутреннего представления для строк в C и C ++ с Visual Studio»
=> http://www.nubaria.com/en/blog/?p=289

#pragma execution_character_set("utf-8") 

Требуется Visual Studio 2008 SP1 и следующее исправление:

http://support.microsoft.com/kb/980263 ....

4 голосов
/ 15 сентября 2009

Как насчет этого? Вы сохраняете строки в кодированном файле UTF-8, а затем предварительно обрабатываете их в исходный файл C ++ в кодировке ASCII. Вы сохраняете кодировку UTF-8 внутри строки, используя шестнадцатеричные экранированные символы. Строка

"中国語 (繁体)"

конвертируется в

"\xE4\xB8\xAD\xE5\x9B\xBD\xE8\xAA\x9E (\xE7\xB9\x81\xE4\xBD\x93)"

Конечно, это нечитается любому человеку, и цель состоит просто в том, чтобы избежать проблем с компилятором.

Вы можете использовать препроцессор C ++ для ссылки на строки в преобразованном заголовочном файле или конвертировать весь исходный код UTF-8 в ASCII перед компиляцией, используя этот трюк.

3 голосов
/ 19 декабря 2010

Переносимое преобразование из любой имеющейся у вас кодировки является простым, используя char_traits :: widen ().

#include <locale>
#include <string>
#include <vector>

/////////////////////////////////////////////////////////
// NativeToUtf16 - Convert a string from the native 
//                 encoding to Unicode UTF-16
// Parameters:
//   sNative (in): Input String
// Returns:        Converted string
/////////////////////////////////////////////////////////
std::wstring NativeToUtf16(const std::string &sNative)
{
  std::locale locNative;

  // The UTF-16 will never be longer than the input string
  std::vector<wchar_t> vUtf16(1+sNative.length());

  // convert
  std::use_facet< std::ctype<wchar_t> >(locNative).widen(
        sNative.c_str(), 
        sNative.c_str()+sNative.length(), 
        &vUtf16[0]);

  return std::wstring(vUtf16.begin(), vUtf16.end());
}

Теоретически, обратный путь от UTF-16 до UTF-8 должен быть таким же легким, но я обнаружил, что локали UTF-8 не работают в моей системе должным образом (VC10 Express на Win7).

Таким образом, я написал простой конвертер на основе RFC 3629.

/////////////////////////////////////////////////////////
// Utf16ToUtf8 -   Convert a character from UTF-16 
//                 encoding to UTF-8.
//                 NB: Does not handle Surrogate pairs.
//                     Does not test for badly formed 
//                     UTF-16
// Parameters:
//   chUtf16 (in): Input char
// Returns:        UTF-8 version as a string
/////////////////////////////////////////////////////////
std::string Utf16ToUtf8(wchar_t chUtf16)
{
    // From RFC 3629
    // 0000 0000-0000 007F   0xxxxxxx
    // 0000 0080-0000 07FF   110xxxxx 10xxxxxx
    // 0000 0800-0000 FFFF   1110xxxx 10xxxxxx 10xxxxxx

    // max output length is 3 bytes (plus one for Nul)
    unsigned char szUtf8[4] = "";

    if (chUtf16 < 0x80)
    {
        szUtf8[0] = static_cast<unsigned char>(chUtf16);
    }
    else if (chUtf16 < 0x7FF)
    {
        szUtf8[0] = static_cast<unsigned char>(0xC0 | ((chUtf16>>6)&0x1F));
        szUtf8[1] = static_cast<unsigned char>(0x80 | (chUtf16&0x3F));
    }
    else
    {
        szUtf8[0] = static_cast<unsigned char>(0xE0 | ((chUtf16>>12)&0xF));
        szUtf8[1] = static_cast<unsigned char>(0x80 | ((chUtf16>>6)&0x3F));
        szUtf8[2] = static_cast<unsigned char>(0x80 | (chUtf16&0x3F));
    }

    return reinterpret_cast<char *>(szUtf8);
}


/////////////////////////////////////////////////////////
// Utf16ToUtf8 -   Convert a string from UTF-16 encoding
//                 to UTF-8
// Parameters:
//   sNative (in): Input String
// Returns:        Converted string
/////////////////////////////////////////////////////////
std::string Utf16ToUtf8(const std::wstring &sUtf16)
{
    std::string sUtf8;
    std::wstring::const_iterator itr;

    for (itr=sUtf16.begin(); itr!=sUtf16.end(); ++itr)
        sUtf8 += Utf16ToUtf8(*itr);
    return sUtf8;
}

Я считаю, что это должно работать на любой платформе, но я не смог протестировать его, кроме как на моей собственной системе, поэтому в нем могут быть ошибки.

#include <iostream>
#include <fstream>

int main()
{
    const char szTest[] = "Das tausendschöne Jungfräulein,\n"
                          "Das tausendschöne Herzelein,\n"
                          "Wollte Gott, wollte Gott,\n"
                          "ich wär' heute bei ihr!\n";

    std::wstring sUtf16 = NativeToUtf16(szTest);
    std::string  sUtf8  = Utf16ToUtf8(sUtf16);

    std::ofstream ofs("test.txt");
    if (ofs)
        ofs << sUtf8;
    return 0;
}
1 голос
/ 15 сентября 2009

Может быть, попробовать эксперимент:

#pragma setlocale(".UTF-8")

или

#pragma setlocale("english_england.UTF-8")
1 голос
/ 04 мая 2017

Я знаю, что опаздываю на вечеринку, но думаю, мне нужно распространить это . Для Visual C ++ 2005 и более поздних версий, если исходный файл не содержит BOM (метку порядка байтов), а системный языковой стандарт не является английским, VC будет считать, что исходный файл не в Unicode.

Чтобы правильно скомпилировать исходные файлы UTF-8, вы должны сохранить в UTF-8 без кодировки BOM , а языковой стандарт системы (не поддерживающий Unicode) должен быть английский .

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...