Мой вопрос, кажется, сбил с толку людей. Вот что-то конкретное:
Наш код делает следующее:
FILE * fout = _tfsopen(_T("丸穴種類.txt"), _T("w"), _SH_DENYNO);
_fputts(W2T(L"刃物種類\n"), fout);
fclose(fout);
В соответствии с целью построения MBCS, приведенное выше создает правильно закодированный файл для кодовой страницы 932 (при условии, что 932 была системной кодовой страницей по умолчанию, когда она была запущена).
В соответствии с целью сборки UNICODE, вышеприведенное создает файл мусора, полный ????.
Я хочу определить символ, или использовать переключатель компилятора, или включить специальный заголовок, или ссылку на заданную библиотеку, чтобы вышеописанное продолжало работать, когда целью сборки является UNICODE без изменения исходного кода.
Вот вопрос, как это было раньше:
FILE*
потоки могут быть открыты в режиме t (перевод) или b (inary).
Настольные приложения могут быть скомпилированы для UNICODE или MBCS (под
Windows).Если мое приложение скомпилировано для MBCS, то запись строк MBCS в
Поток «wt» приводит к правильно сформированному текстовому файлу, содержащему текст MBCS
для системной кодовой страницы (то есть кодовой страницы «для не Unicode
программного обеспечения»).Поскольку наше программное обеспечение обычно использует _t версии большинства строк &
потоковые функции, в сборках MBCS вывод обрабатывается в основном
puts(pszMBString)
или что-то подобноеputc
и т. д. с
pszMBString
уже находится в системной кодовой странице (например, 932, когда
работаю на японской машине), строка выписана дословно
(хотя терминаторы строки массируютсяputs
а такжеgets
автоматически).Однако, если мое приложение скомпилировано для UNICODE, то написание MBCS
строки в потоке «wt» приводят к мусору (много символов «?????») (т.е. Я конвертирую UNICODE в код системы по умолчанию
страница а затем записать это в поток, используя, например,
fwrite(pszNarrow, 1, length, stream)
).
Я могу открыть свои потоки в двоичном режиме, и в этом случае я получу
правильный текст MBCS … но терминаторы строки больше не будут
CR + LF в стиле ПК, но вместо этого будет только LF в стиле UNIX. Это, потому что
в двоичном (непереведенном) режиме файловый поток не обрабатывает
LF-> CR + LF перевод.
Но что мне действительно нужно, так это возможность создавать те же файлы, которые я использовал при компиляции для MBCS: правильно
разделители строк и текстовые файлы MBCS с использованием кодовой страницы системы.Очевидно, я могу вручную настроить терминаторы строки и использовать
двоичные потоки. Тем не менее, это очень агрессивный подход, как я сейчас
должен найти каждый бит кода в системе, которая пишет текст
файлы, и измените его так, чтобы он делал все это правильно. Какие удары
я считаю, что цель UNICODE глупее / менее способна, чем
Цель MBCS, которую мы использовали! Конечно, есть способ переключить C
библиотека сказать «вывести узкие строки как есть, но обработать линию
терминаторы правильно, точно так же, как вы делаете это в сборках MBCS «?!
К сожалению, это огромная тема, которая заслуживает небольшой книги, посвященной ей. И этой книге, в основном, понадобится специальная глава для каждой целевой платформы, для которой она нужна (Linux, Windows [flavour], Mac и т. Д.).
Мой ответ будет касаться только настольных приложений Windows, скомпилированных для C ++ с или без MFC.
Обратите внимание: это относится к желанию считывать и записывать файлы MBCS (узкие) из сборки UNICODE, используя системную кодовую страницу по умолчанию (то есть кодовую страницу для программного обеспечения, отличного от Unicode). Если вы хотите читать и записывать файлы Unicode из сборки UNICODE, вы должны открыть файлы в двоичном режиме и вручную обработать преобразования спецификации и перевода строки (т. Е. При вводе, вы должны пропустить спецификацию (если есть), и оба преобразовать внешнюю кодировку в Windows Unicode [т.е. UTF-16LE], а также преобразовать любые последовательности CR + LF только в LF, а для вывода необходимо записать спецификацию (если есть) и преобразовать из UTF-16LE в любую целевую кодировку вы хотите, плюс вы должны конвертировать LF в CR + LF последовательности, чтобы это был правильно отформатированный текстовый файл ПК).
ОСТЕРЕГАЙТЕСЬ, что MS-библиотека std C устанавливает и получает, fwrite и т. Д., Которая при открытии в текстовом / переведенном режиме преобразует любой 0x0D в последовательность 0x0A 0x0D при записи и наоборот при чтении, независимо от того, читаете вы или запись одного байта, или широкого символа, или потока случайных двоичных данных — это не волнует, и все эти функции сводятся к выполнению слепых байтовых преобразований в режиме текста / перевода !!!
Также имейте в виду, что многие функции Windows API используют CP_ACP внутри, без какого-либо внешнего контроля за их поведением (например, WritePrivateProfileString()
). Отсюда причина, по которой можно захотеть убедиться, что все библиотеки работают с одним и тем же языковым стандартом: CP_ACP, а не с какой-либо другой, поскольку вы не можете управлять некоторыми функциями поведения, вы вынуждены соответствовать их выбору или не использовать их вообще.
При использовании MFC необходимо:
// force CP_ACP *not* CP_THREAD_ACP for MFC CString auto-conveters!!!
// this makes MFC's CString and CStdioFile and other interfaces use the
// system default code page, instead of the thread default code page (which is normally "c")
#define _CONVERSION_DONT_USE_THREAD_LOCALE
Для библиотек C ++ и C необходимо указать библиотекам использовать системную кодовую страницу:
// force C++ and C libraries based on setlocale() to use system locale for narrow strings
// (this automatically calls setlocale() which makes the C library do the same thing as C++ std lib)
// we only change the LC_CTYPE, not collation or date/time formatting
std::locale::global(std::locale(str(boost::format(".%||") % GetACP()).c_str(), LC_CTYPE));
Я делаю #define
во всех моих предварительно скомпилированных заголовках, прежде чем включать любые другие заголовки. Я установил глобальную локаль в main (или ее моральный эквивалент), один раз для всей программы (вам может потребоваться вызывать ее для каждого потока, который будет выполнять ввод / вывод или преобразование строк).
Цель сборки — UNICODE, и для большинства наших операций ввода-вывода мы используем явные преобразования строк перед выводом через CStringA(my_wide_string)
,
Еще одна вещь, о которой следует помнить, это два разных набора многобайтовых функций в стандартной библиотеке C под VS C ++ — те, которые используют локаль потока для своих операций, и другой набор, который использует то, что называется _setmbcp()
(который вы можете запросить через _getmbcp()
, Это фактическая кодовая страница (не локаль), которая используется для интерпретации всех узких строк (ПРИМЕЧАНИЕ: это всегда инициализируется как CP_ACP
т.е. GetACP()
кодом запуска VS C ++).
Полезные справочные материалы:
— секретные семьи сплит-в-окна-кодовые страницы-функций
— Разбираемся (объясняется, что в Windows действуют четыре разных локали)
— MS предлагает некоторые функции, которые позволяют установить кодировку для непосредственного использования, но я их не изучал
— Важное замечание об изменении в MFC, которое привело к тому, что оно больше не уважает CP_ACP, а скорее CP_THREAD_ACP по умолчанию, начиная с MFC 7.0
— Исследование того, почему консольные приложения в Windows крайне неудачны, когда дело доходит до ввода-вывода Unicode
— Макросы преобразования узкой / широкой строки в MFC / ATL (которые я не использую, но вы можете найти полезными)
— Маркер порядка байтов, который необходимо записать для файлов Unicode любой кодировки, понятной для другого программного обеспечения Windows
Библиотека C поддерживает как узкие (char
) и широкий (wchar_t
) строки. В Windows эти два типа строк называются MBCS (или ANSI) и Unicode соответственно.
Вполне возможно использовать узкие функции, даже если вы определили _UNICODE
, Следующий код должен выдавать один и тот же вывод, независимо от того, _UNICODE
определяется или нет:
FILE* f = fopen("foo.txt", "wt");
fputs("foo\nbar\n", f);
fclose(f);
В своем вопросе вы написали: «Я конвертирую UNICODE в кодовую страницу системы по умолчанию и записываю ее в поток». Это заставляет меня поверить, что ваша широкая строка содержит символы, которые нельзя преобразовать в текущую кодовую страницу, и, таким образом, заменить каждый из них знаком вопроса.
Возможно, вы могли бы использовать другую кодировку, кроме текущей кодовой страницы. Я рекомендую использовать кодировку UTF-8, где это возможно.
Обновление: тестирование вашего примера кода на компьютере Windows, работающем на кодовой странице 1252, вызов _fputts
возвращает -1, указывая на ошибку. errno
был установлен на EILSEQ
, что означает «Недопустимая последовательность байтов». Документация MSDN за fopen
говорится, что:
Когда функция потокового ввода-вывода Unicode работает в текстовом режиме (
по умолчанию), исходный или целевой поток считается последовательностью
многобайтовых символов. Поэтому функции потокового ввода Unicode
преобразовывать многобайтовые символы в широкие (как при вызове
mbtowc
функция). По той же причине, Unicode поток-вывод
функции преобразуют широкие символы в многобайтовые символы (как если бы
позвонить вwctomb
функция).
Это ключевая информация для этой ошибки. wctomb
будет использовать локаль для стандартной библиотеки C. При явной настройке языкового стандарта для стандартной библиотеки C на кодовую страницу 932 (Shift JIS) код работал идеально, а выходные данные были правильно закодированы в Shift JIS в выходном файле.
int main()
{
setlocale(LC_ALL, ".932");
FILE * fout = _wfsopen(L"丸穴種類.txt", L"w", _SH_DENYNO);
fputws(L"刃物種類\n", fout);
fclose(fout);
}
Альтернативное (и, возможно, предпочтительное) решение этой проблемы будет заключаться в том, чтобы обрабатывать преобразования самостоятельно, прежде чем вызывать узкие строковые функции стандартной библиотеки Си.
Когда вы компилируете для UNICODE, библиотека c ++ ничего не знает о MBCS. Если вы говорите, что открываете файл для вывода текста, он попытается обработать передаваемые ему буферы как буферы UNICODE.
Кроме того, MBCS является кодированием переменной длины. Чтобы разобрать его, библиотека c ++ должна перебирать символы, что, конечно, невозможно, если она ничего не знает о MBCS. Следовательно, невозможно «просто правильно обрабатывать терминаторы строки».
Я бы посоветовал вам либо подготовить свои строки заранее, либо создать собственную функцию, которая записывает строку в файл. Не уверен, что написание символов по одному будет эффективным (требуются измерения), но если нет, вы можете обрабатывать строки по частям, помещая все, что не содержит \ n, за один раз.