UTF-8, CString и CFile? (С++, MFC)
В настоящее время я работаю над программой MFC, которая специально должна работать с UTF-8. В какой-то момент мне приходится записывать данные UTF-8 в файл; для этого я использую CFiles и CStrings.
Когда я получаю сообщение utf-8 (точнее, российские символы) в файл, результат выглядит как
Ðàñïå÷àòàíî:
Ñèñòåìà
Ïðîèçâîäñòâî
и т.д. Это, конечно, не utf-8. Чтобы правильно прочитать эти данные, мне нужно изменить настройки системы; изменение символов не ASCII в русской таблице кодирования действительно работает, но тогда все мои латинские символы, отличные от ascii, будут терпеть неудачу.
Во всяком случае, как я это делаю.
CFile CSVFile( m_sCible, CFile::modeCreate|CFile::modeWrite);
CString sWorkingLine;
//Add stuff into sWorkingline
CSVFile.Write(sWorkingLine,sWorkingLine.GetLength());
//Clean sWorkingline and start over
Я что-то упустил? Должен ли я использовать что-то другое вместо этого? Есть ли какая-то уловка, которую я пропустил?
Я буду настроен на вашу мудрость и опыт, коллеги-программисты.
EDIT:
Конечно, поскольку я только что задал вопрос, я наконец нашел что-то интересное, которое можно найти здесь здесь. Думал, что могу поделиться им.
ИЗМЕНИТЬ 2:
Хорошо, поэтому я добавил спецификацию к моему файлу, который теперь содержит китайский символ, вероятно, потому, что я не преобразовал свою линию в UTF-8. Чтобы добавить бомбу, я сделал...
char BOM[3]={0xEF, 0xBB, 0xBF};
CSVFile.Write(BOM,3);
И после этого я добавил...
TCHAR TestLine;
//Convert the line to UTF-8 multibyte.
WideCharToMultiByte (CP_UTF8,0,sWorkingLine,sWorkingLine.GetLength(),TestLine,strlen(TestLine)+1,NULL,NULL);
//Add the line to file.
CSVFile.Write(TestLine,strlen(TestLine)+1);
Но тогда я не могу скомпилировать, так как я действительно не знаю, как получить длину TestLine. strlen, похоже, не принимает TCHAR.
Исправлено, вместо этого использовалась статическая длина 1000.
ИЗМЕНИТЬ 3:
Итак, я добавил этот код...
wchar_t NewLine[1000];
wcscpy( NewLine, CT2CW( (LPCTSTR) sWorkingLine ));
TCHAR* TCHARBuf = new TCHAR[1000];
//Convert the line to UTF-8 multibyte.
WideCharToMultiByte (CP_UTF8,0,NewLine,1000,TCHARBuf,1000,NULL,NULL);
//Find how many characters we have to add
size_t size = 0;
HRESULT hr = StringCchLength(TCHARBuf, MAX_PATH, &size);
//Add the line to the file
CSVFile.Write(TCHARBuf,size);
Он компилируется отлично, но когда я смотрю на свой новый файл, он точно такой же, как когда у меня не было всего этого нового кода (например: "àñïå ÷ àòàíî":). Мне кажется, что я не сделал шаг вперед, хотя, я думаю, только маленькая вещь отделяет меня от победы.
ИЗМЕНИТЬ 4:
Я удалил ранее добавленный код, как спросил Нейт, и я решил использовать его код вместо этого, а это значит, что теперь, когда я могу добавить свою строку, у меня есть...
CT2CA outputString(sWorkingLine, CP_UTF8);
//Add line to file.
CSVFile.Write(outputString,::strlen(outputString));
Все компилируется отлично, но русские символы отображаются как???????. Ближе, но все же не так.
Кстати, я хотел бы поблагодарить всех, кто пытался/пытался мне помочь, это очень ценится. Я застрял на этом какое-то время, я не могу дождаться, когда эта проблема исчезнет.
ЗАКЛЮЧИТЕЛЬНЫЙ РЕДАКТ (надеюсь)
Изменив способ, которым я впервые получил свои символы UTF-8 (я перекодировал без особого знания), что было ошибочным с моим новым способом вывода текста, я получил приемлемые результаты. Добавляя спецификацию UTF-8 char в начале моего файла, ее можно было бы читать как Unicode в других программах, например Excel.
Ура! Спасибо всем!
Ответы
Ответ 1
Когда вы выводите данные, которые вам нужно сделать (это предполагает, что вы компилируете в режиме Unicode, что очень рекомендуется):
CString russianText = L"Привет мир";
CFile yourFile(_T("yourfile.txt"), CFile::modeWrite | CFile::modeCreate);
CT2CA outputString(russianText, CP_UTF8);
yourFile.Write(outputString, ::strlen(outputString));
Если _UNICODE
не определен (вы работаете в многобайтовом режиме), вам нужно знать, на какой кодовой странице находится ваш входной текст, и преобразовать его в то, что вы можете использовать. Этот пример показывает работу с русским текстом, который находится в формате UTF-16, сохраняя его в UTF-8:
// Example 1: convert from Russian text in UTF-16 (note the "L"
// in front of the string), into UTF-8.
CW2A russianTextAsUtf8(L"Привет мир", CP_UTF8);
yourFile.Write(russianTextAsUtf8, ::strlen(russianTextAsUtf8));
Скорее всего, ваш русский текст находится на другой кодовой странице, такой как KOI-8R. В этом случае вам нужно преобразовать с другой кодовой страницы в UTF-16. Затем преобразуйте UTF-16 в UTF-8. Вы не можете напрямую конвертировать из KOI-8R в UTF-8 с помощью макросов преобразования, поскольку они всегда пытаются преобразовать узкий текст на страницу системного кода. Так что простой способ сделать это:
// Example 2: convert from Russian text in KOI-8R (code page 20866)
// to UTF-16, and then to UTF-8. Conversions between UTFs are
// lossless.
CA2W russianTextAsUtf16("\xf0\xd2\xc9\xd7\xc5\xd4 \xcd\xc9\xd2", 20866);
CW2A russianTextAsUtf8(russianTextAsUtf16, CP_UTF8);
yourFile.Write(russianTextAsUtf8, ::strlen(russianTextAsUtf8));
Вам не нужна спецификация (это необязательно, я бы не использовал ее, если для этого не было конкретной причины).
Убедитесь, что вы прочитали этот: http://msdn.microsoft.com/en-us/library/87zae4a3(VS.80).aspx. Если вы неправильно используете CT2CA
(например, используя оператор присваивания), у вас возникнут проблемы. На странице связанной документации показаны примеры использования и способы ее не использовать.
Дополнительная информация:
- C в
CT2CA
указывает const
. Я использую его, когда это возможно, но некоторые преобразования поддерживают только неконстантную версию (например, CW2A
).
- T в
CT2CA
указывает, что вы конвертируете из LPCTSTR
. Таким образом, будет работать, будет ли ваш код скомпилирован с флагом _UNICODE
или нет. Вы также можете использовать CW2A
(где W обозначает широкие символы).
- A в
CT2CA
указывает, что вы конвертируете в строку ANSI (8-бит char).
- Наконец, второй параметр
CT2CA
указывает на кодовую страницу, на которую вы конвертируете.
Чтобы сделать обратное преобразование (от UTF-8 до LPCTSTR), вы можете сделать:
CString myString(CA2CT(russianText, CP_UTF8));
В этом случае мы преобразуем строку ANSI в формате UTF-8 в LPCTSTR. LPCTSTR
всегда считается UTF-16 (если определено _UNICODE
) или текущая страница системного кода (если _UNICODE
не определено).
Ответ 2
Вам нужно будет преобразовать sWorkingLine
в UTF-8, а затем записать его в файл.
WideCharToMultiByte может конвертировать строки Unicode в UTF-8, если вы выберете кодовую страницу CP_UTF8
.
MultiByteToWideChar может конвертировать символы ASCII в unicode.
Ответ 3
Убедитесь, что вы используете Unicode (TCHAR is wchar_t). Затем перед записью данных преобразуйте его с помощью функции WideCharToMultiByte Win32 API.