我没想到这系列还能出第三期,我佛了,Windows,你拿什么来赔我!
这次的问题是:在遍历窗口句柄想要输出每个窗口的标题时,中文标题无法正常输出。
废话不多说,直接上示例程序。
#include <iostream>
#include <Windows.h>
using namespace std;
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
const int n = 255;
wchar_t className[n];
wchar_t title[n];
GetClassName(hwnd, className, n);
GetWindowText(hwnd, title, n);
wprintf(L"window title: %ls\n", title);
wprintf(L"window class name: %ls\n\n", className);
return TRUE;
}
int main(int argc, char* argv[])
{
EnumWindows(EnumWindowsProc, 0);
return 0;
}
可以看来,title 有很多 「???」字符,还有一些根本就完全不显示了。这明显是有问题的。
在上一篇文里说过,「???」出现的原因,是遇到了该代码页中不存在的值,用了一个默认的字符代替,在我这里是用 ‘?’ 代替。也就是说,在控制台也输出文件不做任何操作的默认情况下,MSVC 会采用 ANSI 代码页作为当前的字符集,在这里就是 GBK。
那么,有没有办法让 MSVC 用支持 Unicode 的编码格式进行输出呢?例如使用 utf-8 或者 utf-16 格式进行输出?
还真有。
解决方案
C 风格的文件
c++ – Output unicode strings in Windows console app – Stack Overflow
上面这个回答说了,如果是 C 风格的文件(包括 stdin 和 stdout 文件描述符),可以用 _setmode 函数改变为 utf-8 或 utf-16 输出。
_setmode(_fileno(stdout), _O_U16TEXT); // 文件将会以 utf-16 格式输出
_setmode(_fileno(stdout), _O_U8TEXT); // 文件将会以 utf-8 格式输出
#include <iostream>
#include <Windows.h>
#include <io.h> // _setmode
#include <fcntl.h> // _O_U8TEXT
using namespace std;
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
const int n = 255;
wchar_t className[n];
wchar_t title[n];
GetClassName(hwnd, className, n);
GetWindowText(hwnd, title, n);
wprintf(L"window title: %ls\n", title);
wprintf(L"window class name: %ls\n\n", className);
return TRUE;
}
int main(int argc, char* argv[])
{
_setmode(_fileno(stdout), _O_U8TEXT);
EnumWindows(EnumWindowsProc, 0);
return 0;
}
所以上面的代码只需要改成这样就可以输出了。
对于文件的话,也是一样的:
#include <iostream>
#include <Windows.h>
#include <io.h>
#include <fcntl.h>
using namespace std;
FILE* fp = fopen("output.txt", "w");
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
const int n = 255;
wchar_t className[n];
wchar_t title[n];
GetClassName(hwnd, className, n);
GetWindowText(hwnd, title, n);
fwprintf(fp, L"window title: %ls\n", title);
fwprintf(fp, L"window class name: %ls\n\n", className);
return TRUE;
}
int main(int argc, char* argv[])
{
fp = fopen("output.txt", "w");
_setmode(_fileno(fp), _O_U8TEXT);
EnumWindows(EnumWindowsProc, 0);
return 0;
}
C++ 风格的文件
c++ – Wrote to a file using std::wofstream. The file remained empty – Stack Overflow
可以用这个回答里面的方法。MSVC 提供了 codecvt_utf8 和 codecvt_utf16 ,可以用于修改文件流的编码形式。
不过我试了之后发现只有 codecvt_utf8 有效,不知道为啥 codecvt_utf16 出来的是乱码,所以示例只用 codecvt_utf8 了。
代码示例:
#include <iostream>
#include <fstream>
#include <locale>
#include <codecvt>
#include <Windows.h>
using namespace std;
wofstream out;
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
const int n = 255;
wchar_t className[n];
wchar_t title[n];
GetClassName(hwnd, className, n);
GetWindowText(hwnd, title, n);
out << L"window title: " << title << endl;
out << L"window class name: " << className << endl << endl;
return TRUE;
}
int main(int argc, char* argv[])
{
out.open(L"output.txt");
std::locale loc(std::locale::classic(), new std::codecvt_utf8<wchar_t>);
out.imbue(loc);
EnumWindows(EnumWindowsProc, 0);
return 0;
}
但不知道为什么这个方法对 wcout 没用,从控制台里输出的还是乱码,所以控制台还是老老实实用上面的 _setmode 吧,这样就可以直接用 wcout 输出中文了。