MyTetra Share
Делитесь знаниями!
Как в Qt работать с русскими именами файлов и директорий под Windows
Время создания: 23.09.2018 16:29
Автор: xintrea
Текстовые метки: qt, qt5, кодировка, локаль, имя, файл, директория, открыть, читать, писать, windows
Раздел: Компьютер - Программирование - Язык C++ (Си++) - Библиотека Qt - Локализация
Запись: xintrea/mytetra_syncro/master/base/1537709346z7j60byo93/text.html на raw.github.com

Всемирно известная компания Microsoft отличается большой оригинальностью в области стандартов. Иначе как объяснить появление операционной системы, в которой кодировка имен файлов и кодировка консоли не совпадают?



В русской редакции Windows есть следующая застарелая проблема: в качестве кодировки локали, а, следовательно, и кодировки имен файлов и директорий, принята кодировка CP 1251. Однако кодировкой консоли является CP 866. Как с этим жить в Qt?


Проблема в том, что в Qt5 не предусмотрено разделение на кодировку локали и кодировку консоли. Если в русскоязычной Windows выполнить следующий код:



QTextCodec::setCodecForLocale(QTextCodec::codecForName("CP 866"));



то после его выполнения вывод в консоль русских строк будет работать правильно. То есть, вывод через qDebug(), qInfo(), qWarning(), qCritical(), qFatal() начнет работать сразу, без всяких ухищрений:



qDebug() << "Привет, мир!";



А вывод через cout нужно оформлять немного сложнее, учитывая что кодировка исходников UTF-8:



cout << QString::fromUtf8("Привет, ").toLocal8Bit().data();


QString world="мир!";

cout << world.toLocal8Bit().data();



В любом случае, в консоли станут появляться правильные русские символы.


Однако, если установлена кодировка CP 866, все действия с файлами и директориями, в именах которых присутствуют русские символы, не будут работать в таких классах как QFile и QDir. Да и низкоуровневые функции fopen() / fclose() тоже не смогут работать с русскоязычными именами файлов.


Если же установить в программе кодек на кодировку 1251:



QTextCodec::setCodecForLocale(QTextCodec::codecForName("CP 1251"));



то сразу заработают файловые операции через QFile и QDir над файлами и директориями, в именах которых присутствуют национальные русскоязычные символы. Функции fopen() / fclose() тоже заработают, если имена файлов указывать через QString- методы .toLocal8Bit().data().


Проблема в том, что кодек локали в Qt всего один. И он глобальный, и устанавливается вызовом QTextCodec::setCodecForLocale(имяКодека). В таких условиях невозможно одновременно работать с разными кодировками для локали и консоли. Что же делать?


Вариантов немного.


Вариант 1. Забить на кодировку консоли. Выставить кодировку локали как CP 1251 и спокойно работать. Да, в консоль будут сыпаться кракозябры, но хотя бы весь функционал программы по работе с файлами будет нормально работать.


Вариант 2. Выставить кодировку локали как CP 1251. А чтобы сообщения сыпались в правильной кодировке, можно переопределить хендлер консольных сообщений. В нем можно перед печатью в консоль установить кодировку локали в CP 866, а при выходе вернуть в CP1251. Код будет примерно такой:



// Установка хендлера

void setDebugMessageHandler()

{

qDebug() << "Debug message before set message handler";


qDebug() << "Set alternative handler myMessageOutput() for debug message";


#if QT_VERSION < 0x050000

qInstallMsgHandler(myMessageOutput);

#else

qInstallMessageHandler(myMessageOutput);

#endif


qDebug() << "Debug message after set message handler";

}



// Обработчик (хендлер) вызовов qDebug() и прочих видов сообщений

// Внутри этого обработчика нельзя использовать вызовы qDebug(), т. к. получится рекурсия

#if QT_VERSION < 0x050000

void myMessageOutput(QtMsgType type, const char *msg)

#else

void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msgText)

#endif

{

#if QT_VERSION >= 0x050000

Q_UNUSED(context);

#endif



#if QT_VERSION < 0x050000

QString msgText( QString::fromUtf8(msg) );

#endif



switch (type) {

case QtDebugMsg:

smartPrintDebugMessage("[DBG] "+msgText+"\n");

break;

case QtWarningMsg:

smartPrintDebugMessage("[WRN] "+msgText+"\n");

break;

case QtCriticalMsg:

smartPrintDebugMessage("[CRERR] "+msgText+"\n");

break;

case QtFatalMsg:

smartPrintDebugMessage("[FTERR] "+msgText+"\n");

abort();

#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))

case QtInfoMsg:

smartPrintDebugMessage("[INF] "+msgText+"\n");

break;

#endif

}


}



void smartPrintDebugMessage(QString msg)

{

// Установка кодека для нормальной работы консоли

QTextCodec::setCodecForLocale(QTextCodec::codecForName("CP 866"));


QTime currTime = QTime::currentTime();

QString timeText=currTime.toString("hh:mm:ss");

msg=timeText+" "+msg;


cout << msg.toLocal8Bit().data();

// Установка кодека для нормальной работы локали

QTextCodec::setCodecForLocale(QTextCodec::codecForName("CP 1251"));

}



В начале программы вызывается функция setDebugMessageHandler() , после чего дальнейшая работа идет стандартными средствами. Файлы с русскими именами будут открываться, а в консоль будут сыпаться сообщения с правильными русскими символами.


Для операционных систем Linux уже давно стандартом является кодировка UTF-8, и такой свистопляски не требуется. А для создания кроссплатформенного решения, нужно условной компиляцией устанавливать кодек "UTF-8" и все.


 
MyTetra Share v.0.59
Яндекс индекс цитирования