在工作中使用qt,发现qt生成的日志文件和在终端输出的中文内容总是出现乱码。这已经影响了开发和排查问题的效率,于是决定研究一下。开发平台是在win10下,开发环境是qt5.15.2+vs2019。
经过排查发现,程序中使用的日志库是公司底层模块同事用C/C++语言写的,也就是说每次qt程序写日志都是将日志内容传给C/C++语言程序再生成日志文件或者输出到终端,为什么不直接用qt去实现日志功能,我只能说是历史包袱。
上网查了很多资料,知道是qt和C/C++字符编码格式的问题,于是写个demo进行测试,如下。
#if _MSC_VER >= 1600 //vs2010及其以上版本
#pragma execution_character_set("utf-8") //设置执行字符编码
#endif
#include "mainwindow.h"
#include <QApplication>
#include <QTextCodec>
#include <QByteArray>
#include <QDebug>
#include <QFile>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <QLibrary>
#ifdef Q_OS_WIN
#include <windows.h>
#endif
/*
* windows下需要使用
* 下面这连个转换函数也是从网上抄的,实际上实现的是多字节和宽字节互相转换的功能
* 而中文对应的UTF8正好是多字节(3个字节表示),而中文对应的GBK正好是宽字节(固定的2个字节表示)
*/
#ifdef Q_OS_WIN
std::string UTF8ToGBK(const char* strUTF8)
{
int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, NULL, 0);
wchar_t* wszGBK = new wchar_t[len+1];
memset(wszGBK, 0, len*2+2);
MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, wszGBK, len);
len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
char* szGBK = new char[len+1];
memset(szGBK, 0, len+1);
WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
std::string strTemp(szGBK);
if(wszGBK) delete[] wszGBK;
if(szGBK) delete[] szGBK;
return strTemp;
}
std::string GBKToUTF8(const char* strGBK)
{
int len = MultiByteToWideChar(CP_ACP, 0, strGBK, -1, NULL, 0);
wchar_t* wstr = new wchar_t[len+1];
memset(wstr, 0, len+1);
MultiByteToWideChar(CP_ACP, 0, strGBK, -1, wstr, len);
len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
char* str = new char[len+1];
memset(str, 0, len+1);
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL);
std::string strTemp = str;
if(wstr) delete[] wstr;
if(str) delete[] str;
return strTemp;
}
#endif
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
//设置中文编码
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
QTextCodec *codec = QTextCodec::codecForName("UTF-8");
QTextCodec::setCodecForLocale(codec);
QTextCodec::setCodecForCStrings(codec);
QTextCodec::setCodecForTr(codec);
#else
QTextCodec *codec = QTextCodec::codecForName("UTF-8"); //设置本地字符编码
QTextCodec::setCodecForLocale(codec);
qDebug()<<"本地环境编码 = "<<codec->name();
#endif
//中文编码16进制查询网站https://www.qqxiuzi.cn/bianma/zifuji.php
/*
测试字符串输出中文是否正确,有很多转换的方式,如下,
qDebug() << str;
qDebug() << QStringLiteral("2中文");
qDebug() << QString::fromLatin1("3中文");
qDebug() << QString::fromLocal8Bit("4中文");
qDebug() << QString::fromUtf8("5中文");
qDebug() << QString::fromWCharArray(L"6中文");
*/
/*数据输出测试
windwos下 :
终端显示 qtide
日志文件 将日志内容输出到txt文件中
linux下 :
终端显示 默认终端 qtide
日志文件 将日志内容输出到文件中
*/
//C语言风格代码测试 char const * p
{
char const * ch = " C 常量指针变量指向中文字符串,然后输出\n";
//使用qt方式输出
qDebug()<<"C Format Code "<<__LINE__<<" "<<ch;
#ifdef Q_OS_WIN
//转码输出
printf("C Format Code %d %s",__LINE__,UTF8ToGBK(ch).data());
fflush(stdout); //使得printf按顺序输出与编码无关
std::cout<<"C Format Code "<<__LINE__<<" "<<UTF8ToGBK(ch)<<std::endl;
#endif
//不转码输出用作参照
printf("C Format Code %d %s",__LINE__,ch);
fflush(stdout);
std::cout<<"C Format Code "<<__LINE__<<" "<<ch<<std::endl;
//使用C/C++的方式写入文件
FILE * fp;
#ifdef Q_OS_WIN
//windwos下转码写入
fp = fopen("./C/cwirteToGbk.txt","w");
if(fp)
{
fwrite(UTF8ToGBK(ch).data(),1,strlen(UTF8ToGBK(ch).data()),fp);
}
fclose(fp);
#endif
//不转码写入
fp = fopen("./C/cwirte.txt","w");
if(fp)
{
fwrite(ch,1,strlen(ch),fp);
}
fclose(fp);
}
//C++ 风格代码测试 string
{
std::string cplusstr = " 中文\n";
//qt方式输出
qDebug()<<"C++ Format Code "<<__LINE__<<" "<<cplusstr.data();
//C++方式输出
#ifdef Q_OS_WIN
std::cout<<"C++ Format Code "<<__LINE__<<" "<<UTF8ToGBK(cplusstr.data());
fflush(stdout);
#endif
std::cout<<"C++ Format Code "<<__LINE__<<" "<<cplusstr.data();
fflush(stdout);
//C++方式写入文件
std::ofstream ofs;
#ifdef Q_OS_WIN
ofs.open("./CPLUSPLUS/cpluswirteToGbk.txt", std::ios::out);
ofs << UTF8ToGBK(cplusstr.data());
ofs.close();
#endif
ofs.open("./CPLUSPLUS/cpluswirte.txt", std::ios::out);
ofs << cplusstr;
ofs.close();
}
//qt 风格代码测试 QString
{
QString qtest = " 中文";
qDebug()<<"qt Format Code "<<__LINE__<<qtest;
//转换成不同的编码进行输出
QByteArray array = qtest.toUtf8();
qDebug()<<"qt Format Code "<<__LINE__<<array.constData();
qDebug()<<"qt Format Code "<<__LINE__<<" array hex = "<<array.toHex();
array = qtest.toLocal8Bit();
qDebug()<<"qt Format Code "<<__LINE__<<array.constData();
qDebug()<<"qt Format Code "<<__LINE__<<" array hex = "<<array.toHex();
//qt方式写文件
QFile file("./QT/qtwrite.txt");
file.open(QIODevice::WriteOnly);
file.write(qtest.toStdString().data(),qtest.toStdString().size());
file.close();
file.setFileName("./QT/qtwriteToUtf-8.txt");
file.open(QIODevice::WriteOnly);
file.write(qtest.toUtf8(),(qtest.toUtf8()).size());
file.close();
file.setFileName("./QT/qtwriteToGbk.txt");
file.open(QIODevice::WriteOnly);
file.write(qtest.toLocal8Bit(),(qtest.toLocal8Bit()).size());
file.close();
}
/*
* 与动态库交互数据
*
* 向C/C++库中传入数据和从C/C++中获取数据测试
* C和C++库在windwos下 V2019 编译
*/
//windows下的测试
#ifdef Q_OS_WIN
{
//定义函数指针
typedef std::string (*ptransdatastr)(std::string); //输入string返回string
typedef char * (*ptransdatachar)(char *); //输入char * 返回char *
QLibrary * qlib = new QLibrary();
qlib->setFileName("./candcplusforwin.dll");
if(qlib->load())
{
ptransdatastr ptransdatastrfun = (ptransdatastr)(qlib->resolve("transdatastr"));
if(ptransdatastrfun)
{
QString cplus = " 中文";
std::string dllredata = ptransdatastrfun(UTF8ToGBK(cplus.toLocal8Bit().constData())).data();
qDebug()<<"return from dll transdatastr : "<<__LINE__<<dllredata.data();
qDebug()<<"return from dll transdatastr : "<<__LINE__<<GBKToUTF8(dllredata.data()).data();
}
else
{
qDebug()<<"解析符号transdatastr失败";
}
ptransdatachar ptransdatacharfun = (ptransdatachar)(qlib->resolve("transdatachar"));
if(ptransdatacharfun)
{
QString cplus = " 中文";
std::string dllredata = ptransdatacharfun((char *)UTF8ToGBK(cplus.toUtf8().constData()).data());
qDebug()<<"return from dll transdatachar : "<<__LINE__<<dllredata.data();
qDebug()<<"return from dll transdatachar : "<<__LINE__<<GBKToUTF8(dllredata.data()).data();
}
else
{
qDebug()<<"解析符号transdatachar失败";
}
}
else
{
qDebug()<<"加载库candcplusforwin.dll失败";
}
}
#endif
return a.exec();
}
/*
本地环境编码 = "UTF-8"
C Format Code 123 C 常量指针变量指向中文字符串,然后输出
C++ Format Code 167 中文
qt Format Code 196 " 中文"
qt Format Code 200 中文
qt Format Code 201 array hex = "2020e4b8ade69687"
qt Format Code 204 中文
qt Format Code 205 array hex = "2020e4b8ade69687"
return from dll transdatastr : 250 ??????c++?????? ????
return from dll transdatastr : 252 来自于c++库数据 中文
return from dll transdatachar : 266 ??????c++?????? ????
return from dll transdatachar : 268 来自于c++库数据 中文
C Format Code 127 C 常量指针变量指向中文字符串,然后输出
C Format Code 130 C 常量指针变量指向中文字符串,然后输出
C Format Code 134 C 甯搁噺鎸囬拡鍙橀噺鎸囧悜涓枃瀛楃涓诧紝鐒跺悗杈撳嚭
C Format Code 136 C 甯搁噺鎸囬拡鍙橀噺鎸囧悜涓枃瀛楃涓诧紝鐒跺悗杈撳嚭
C++ Format Code 172 中文
C++ Format Code 176 涓枃
transdatastr 9 中文
transdatastr 10 中文
transdatachar 24 中文
transdatachar 25 中文
*/
下面是C/C++动态库的代码
string transdatastr(string str)
{
cout << __FUNCTION__ << " " << __LINE__ << " " << str << endl;
printf("%s %d %s\n", __FUNCTION__, __LINE__,str.data());
str = "来自于c++库数据 " + str;
ofstream ofs;
ofs.open("./windllwritestr.txt", ios::out);
ofs << str;
ofs.close();
return str;
}
char* transdatachar(char* ch)
{
cout << __FUNCTION__ << " " << __LINE__ << " " << ch << endl;
printf("%s %d %s\n", __FUNCTION__, __LINE__, ch);
string temp = ch;
temp = "来自于c++库数据 " + temp;
ofstream ofs;
ofs.open("./windllwritech.txt", ios::out);
ofs << ch;
ofs.close();
return (char *)temp.data();
}
我认为日志内容选择UTF-8编码格式就挺好,毕竟兼容性强,windwos和linux上都可以使用。所以在代码中我将执行字符集和本地字符编码都设置成了UTF-8。
在获取终端输出的时候,需要注意,我原本想使用debugview++输出上面的所有内容,结果发现C/C++输出的内容并没有显示,我只能在qt ide上的应用程序输出窗口显示,此时要注意这个窗口是以什么编码格式显示内容的,我这里设置的是GBK,所以下面代码中转码不会输出乱码,不转码输出乱码。
#ifdef Q_OS_WIN
//转码输出
printf("C Format Code %d %s",__LINE__,UTF8ToGBK(ch).data());
fflush(stdout); //使得printf按顺序输出与编码无关
std::cout<<"C Format Code "<<__LINE__<<" "<<UTF8ToGBK(ch)<<std::endl;
#endif
//不转码输出用作参照
printf("C Format Code %d %s",__LINE__,ch);
fflush(stdout);
std::cout<<"C Format Code "<<__LINE__<<" "<<ch<<std::endl;
下图是设置窗口显示内容编码格式的位置
希望能帮到读者,如果发现问题,请大家指教。