LINUX.ORG.RU

Кросплатформенная работа с консолью

 


1

1

Привет.
Дела такие: программа читает файлы с диска, выводит какие-то их части в консоль, компилируется под линукс и винду, кодировка файлов постоянная (пусть будет ютф-8).

И вот как лучше это сделать - пока не до конца понял. Можно читать файлы, конвертировать в wchar_t, писать wchar_t в консоль (если не ошибаюсь, то wchar_t автоматом конвертнётся в кодировку локали), но как конвертировать utf-8 в wchar_t(codecvt умеет лишь char<->wchar_t)? Взять за внутреннюю кодировку utf-32/16? Но тогда как конвертировать в кодировку локали при печати в консоль? Хочется обойтись без препроцессорной #ifdef win … . Переключать из софтину кодировку консоли в винде? В общем, как правильно.

Изи. Правда от ifdef не деться…

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstdio>
#include <cwchar>
#include <cassert>
#include <fstream>
#include <codecvt>

#ifdef _WIN32
#include <io.h>
#include <fcntl.h>
#endif

int main()
{
#ifdef _WIN32
	int result = _setmode(_fileno(stdout), _O_U16TEXT);
	assert(result != -1);
#endif
	std::fstream file("test.txt");
	std::string s;
	std::getline(file, s);

	std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> conversion;
	std::wstring w = conversion.from_bytes(s);
	std::wcout << w << std::endl;
}

https://imgur.com/a/OZ1iwPX

fsb4000 ★★★ ()
Последнее исправление: fsb4000 (всего исправлений: 1)

Я бы разделил логику апы и системнозависимое. Чтобы этих самых системнозависимых штук не было вообще видно в основном коде. А там, в системно зависимых файлах уже сделать ifdef...

Ну и я когда что-то подобное делал сделал две отдельные сущности:

1. Это непосредственно функциональность работы с кодировками.

2. Это входящие аргументы, в объекты пункта 1, они содержат в себе (или хардкорно вшитые параметры, или по феншую полученные через запрос-ответ соответствующих системных апи данные для работы функциональности пункта 1)

В итоге например у меня с кодировками работа была обёрнута так:

 jsonWriter.String(Utils::Misc::EnvToAppEncoding(classInfo.second.path,LocaleSet::getInstance().getFileSystemEncoding()));

//path - строка полученная из внешнего мира (операционка)
//getFileSystemEncoding - возвращает кодировку
//EnvToAppEncoding - преобразует из внешней кодировки во внутреннюю кодировку аппы - utf-16 или 32, не помню что уже там, винде одна в линуксе другая, в общем в wchar_t строку :)

Еще в винде до сих пор в 2020 в консоли для кириллицы используется cp-866 по дефолту, соответственно чтобы консоль выводила правильно киррилицу нужно выбрать какое-то решение.

Имхо, самое лучшее и простое - это учитвать это в программе, и подстаивать вывод из стандартных Си++ потоков в стандартную для консоли кодировку.

Существуют способы переключить по запросу приложения консоль в режим utf-8. Еще не помню точно, но вроде этот способ влечет за собой еще необходимость сменить в настройках консоли шрифт на какой-то определённый иначе не будут отображаться какие-то символы или типа того — короче на мой взгляд это плохой и тяжелый путь и я первый вариант лучше.

При этом если из консольной аппы ты захочешь файлы читать, то кодировка имен в файлах будет cp1251 :)

--- А еще utf-cpp либа мне помогла, посмотри на неё.

bonta ★★★ ()
Последнее исправление: bonta (всего исправлений: 1)

Спасибо за ответы.
Ребят мне пришла в голову гениальная идея - можно ведь выбирать кодировку narrow charters (gcc во всяком случае умеет), т.е. обычного char. Может можно компилить шлангом в винде с char==utf8, читать файлы, конвертировать стандартной codecvt в wchar_t, и писать в консоль (конвертация в кодировку консолу автоматом). Должно работать, наверное.

pavlick ()
Ответ на: комментарий от pavlick

без проблем.

https://imgur.com/a/KAabLIp

ещё раз посмотри мой код выше, и читай маны:

https://docs.microsoft.com/ru-ru/cpp/c-runtime-library/reference/setmode?view=vs-2019

https://docs.microsoft.com/ru-ru/cpp/c-runtime-library/reference/setmode?view=vs-2019#example-1

fsb4000 ★★★ ()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от fsb4000

Благодарю. Хотелось написать как-то платформонезависимо, но увы … . Вообще жесть, 2020 год, Юникод на дворе, а писать по-русски в консоль - нетривиальная задача. Зато Кортана.

pavlick ()
Ответ на: комментарий от pavlick

почитай ещё что @bonta написал, так тоже можно.

Вообще жесть, 2020 год, Юникод на дворе, а писать по-русски в консоль - нетривиальная задача.

Одна дополнительная строчка в начале(или 2 если проверять возвращаемое значение)

_setmode(_fileno(stdout), _O_U16TEXT);
#include <fcntl.h>
#include <io.h>
#include <stdio.h>

int main(void) {
    _setmode(_fileno(stdout), _O_U16TEXT);
    wprintf(L"Привет мир!\n");
}

https://imgur.com/a/3B6BZC9

fsb4000 ★★★ ()

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

lovesan ★☆ ()
Ответ на: комментарий от fsb4000

Ну как-то всё больно хрупко, запустил в https://rextester.com/l/vcpp в ответ кракозябры.
Зы: а обычный принтф кириллицу понимает. Хз, чудеса какие-то.

pavlick ()
Последнее исправление: pavlick (всего исправлений: 1)
Ответ на: комментарий от bonta

Еще в винде до сих пор в 2020 в консоли для кириллицы используется cp-866 по дефолту

Это не так. В винде все API работающие с текстом завязаны на UTF-16, в т.ч. консольные.

cp866 для обратной совместимости используется в cmd.exe при выводе бинарных данных, и только

lovesan ★☆ ()
Ответ на: комментарий от lovesan

cp866 для обратной совместимости используется в cmd.exe при выводе бинарных данных, и только

Это теперь навсегда ))? Надо же как-то делать нормальную юникодную консоль дефолтно. Ну там привязаться к флагам компилятора, с11/с++11 или старше - давать нормальную юникодную консоль. А то что-то непотребное

pavlick ()
Последнее исправление: pavlick (всего исправлений: 1)
Ответ на: комментарий от pavlick

Надо же как-то делать нормальную юникодную консоль дефолтно.

Консоль там и есть юникодная, и как раз «по дефолту», уже несколько десятилетий.

cmd.exe это не консоль, это cmd.exe

bash из WSL, PowerShell, и другие консольные приложения - без проблем «по-дефолту» пишут юникод.

PS:

#include <string.h>
#include <windows.h>

int main(void)
{	
    const wchar_t* message = L"Hello, World!";
    WriteConsoleW(
        GetStdHandle(STD_OUTPUT_HANDLE),
        message,
        wcslen(message),
        NULL,
        NULL);
    return 0;
}
lovesan ★☆ ()
Ответ на: комментарий от pavlick

Да @lovesan прав. Кроме _setmode можно пользоваться напрямую API Windows. Или пользоваться функциями из библиотеки conio.h

#include <conio.h>

int main(void)
{
    _cputws(L"Привет мир!\n");
}

https://i.imgur.com/wCRaJDY.png

https://i.imgur.com/CSqMBRq.png

fsb4000 ★★★ ()
Последнее исправление: fsb4000 (всего исправлений: 1)

Почему файл 22 имеет нулевой размер на выходе?

#include <iostream>
#include <locale>
#include <fstream>
using namespace std;
 
int main()
{
    locale l(locale(""), new codecvt<wchar_t, char, mbstate_t>);
    //locale l(locale(""));
    wofstream f("22");
    f.imbue(l);
    f << L"Прпвава jkjkj";
}
std::codecvt<wchar_t, char, std::mbstate_t> 	conversion between the system's native wide and the single-byte narrow character sets 

если раскомментировать locale l(locale("")), то запись происходит (utf-8).

pavlick ()
Ответ на: комментарий от invy

Что там велосипедить? Люди совсем обленились. Там внутри, если вырезать шаблонный балласт, примерно вот это: https://stackoverflow.com/questions/7153935/how-to-convert-utf-8-stdstring-to-utf-16-stdwstring

Без гугла такой же пишется не более, чем за 5 минут по табличке §3 из RFC3629. Если это велосипед, то я тогда не знаю, зачем вообще программы писать самому. Пусть их другие пишут.

(Напомню, речь конкретно про codecvt_utf8_utf16)

anonymous ()
Ответ на: комментарий от anonymous

Но оно уже есть в языке. От слова депрекейтед оно работать не перестанет. А каждая написанная строка - источник расходов, ее нужно поддерживать потом.

invy ★★★★★ ()
Ответ на: комментарий от fsb4000

хм... возможно, там надо было предварительно консоль установить в какой-то режим. уже не помню, давно это было. но точно помню, что L"бла-бла-бла" работало.

Iron_Bug ★★★★ ()
Ответ на: комментарий от Iron_Bug

возможно, там надо было предварительно консоль установить в какой-то режим

да.

https://docs.microsoft.com/ru-ru/cpp/c-runtime-library/reference/setmode?view=vs-2019

Ему об этом и написали, но парниша решил что есть вариант более кроссплатформенный(на самом деле нет), и вот его окончательный вариант, после чего он пометил тему решенной:

Локали, кодировки (комментарий)

fsb4000 ★★★ ()
Последнее исправление: fsb4000 (всего исправлений: 2)
Ответ на: комментарий от fsb4000

откопала стюардессу. она для cout, но вроде для wcout то же самое должно быть.

                std::locale loc = std::locale ("en_US.UTF8");//"ru_RU.cp1251";

                // Generate locales and imbue them to iostream
                std::locale::global(loc);
                cout.imbue(loc);
вроде раньше это под маздаем работало.

Iron_Bug ★★★★ ()
Ответ на: комментарий от fsb4000

Ну то я в целом с локалями разбирался. А что касательно этого - я всё на ус намотал, ютф-16 или wchar_t не хочу. Подозреваю, что должно работать

#include <iostream>
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
using namespace std;
int main(void) {
    _setmode(_fileno(stdout), _O_U8TEXT);
    const char8_t *nc = u8"привет мир";  // если с поддержкой с++20, иначе просто char
    const char *p = (char*)nc;
    cout << p << endl;
}

Если не взлетит, то в баню эту винду с её недоконсолью.

ЗЫ: спасибо за ответы.

pavlick ()
Последнее исправление: pavlick (всего исправлений: 1)
Ответ на: комментарий от pavlick

Если не взлетит, то в баню эту винду с её недоконсолью.

вообще разрабатывать под что-то, чего у тебя нет это адски трудная задача. Так что если тебе действительно понадобится поддержка Windows, то советую тебе в VirtualBox/Qemu поставить её. Одними онлайн компиляторами добиться работоспособности будет крайне трудно…

fsb4000 ★★★ ()