C++在控制台下用wcout输出中文错误

今天本来想在window的任务栏上画一些东西(为啥?纯粹是好玩而已,或者你可能会想到某某插件会在任务栏那里搞个输入框~~可恶!!)。

首先要说明一下,现在这篇文章不是为了说怎么在任务栏上加内容,而是我在做这个事时,另外遇到的一个问题:在列举所有顶层window时,不能正确获取到window 的标题(这里其实是获取到了,但是因为标题中含有中文,没有正确打印出来,所以刚开始以为没有获取到)。就像刚开始遇到这个问题的时候,我觉得可能是没有获取到标题,先f1查看了GetWindowText的api说明,开头就有这么一段内容:

The GetWindowText function copies the text of the specified window's title bar (if it has one) into a buffer. If the specified window is a control, the text of the control is copied. However, GetWindowText cannot retrieve the text of a control in another application.

不是吧,GetWindowText只能获取自己所属进程里的标题,获取不到其他应用程序里的标题?有其他办法吗?继续翻看下去(虽然是英文的,但是还好基本能看懂)

在remark部分中有这么一段:

To retrieve the text of a control in another process, send a WM_GETTEXT message directly instead of calling GetWindowText.

说是可以用WM_GETTEXT获取到在其他进程中的标题哦~立刻试试。。。

因为GetWindowText部分没有说明WM_GETTEXT消息各参数的含义,而且平常很少用这个,所以先跳到WM_GETTEXT看看具体说明。根据说明,只要这样调用SendMessage就可以获取到标题:

/*其中len是指复制到szBuffer中的TCHAR的长度,因为我写的程序是想在ANSI和UNICODE两种环境下都可用的,所以用TCHAR,具体资料可以google一下*/

SendMessage(hWnd,WM_GETTEXT,(WPARAM)len,(LPARAM)szBuffer);

结果却跟GetWindowText获取到的一样,同样没能正确输出中文。还会有啥原因呢?直接google一下

查到了相关资料,不多说,直接上代码:

/***********************代码   开始*******************************/

#include<iostream>
#include<iomanip>
#include<set>
#include<vector>
#include<tchar.h>
#include <locale>
#include <Windows.h>

using namespace std;

int iCount=0;
/* 回调函数 */
BOOL CALLBACK EnumWindowProc(HWND hWnd, LPARAM lParam)
{
 int len=-1;
 //static vector
 RECT rect;
 TCHAR szClass[MAX_PATH];
 TCHAR szWindow[MAX_PATH];
 TCHAR szBuffer[MAX_PATH];
 if ( IsWindow(hWnd) &&
  IsWindowVisible(hWnd) &&
  (GetWindowLong(hWnd, GWL_EXSTYLE)&WS_EX_TOOLWINDOW)!=WS_EX_TOOLWINDOW &&
  GetWindowLong(hWnd, GWL_HWNDPARENT)==0 )
 {
  /**
  HDC hdc=GetDC(hWnd);
  TCHAR str[]=TEXT("这是取到的几个句柄,我写上这些以作标示#########");
  TextOut(hdc,0,0,TEXT(""),ARRAYSIZE(str));
  ReleaseDC(hWnd,hdc);**/

  /******下面这两句是使用wcout输出中文的关键,需要将其locale设为本地语言才能输出中文*******/
  locale loc("chs");
  wcout.imbue(loc);

  //获取窗口标题
  len=GetWindowText(hWnd, szWindow, MAX_PATH-1);
  if(len>0 ){
   //_tprintf(TEXT("窗口标题长度(GetWindowText):[%d],窗口标题:[%s]]n"),len,szWindow);
   wcout<<TEXT("窗口标题长度(GetWindowText):[")<<len<<TEXT("]")<<endl;
   wcout <<TEXT("窗口标题:[")<<szWindow<<TEXT("]")<<endl;   
  }
  
   //获取窗口类名称
  len=GetClassName(hWnd, szClass, MAX_PATH-1);
  if(len>0){
   //_tprintf(TEXT("窗口类名长度(GetClassName):[%d],窗口类名:[%s]"),len,szClass);
   wcout<<TEXT("窗口类名长度(GetClassName):[")<<len<<TEXT("]")<<endl;
   wcout <<TEXT("窗口类名:[")<<szClass<<TEXT("]")<<endl;
  }
   

  GetWindowRect(hWnd,&rect);
  cout<<"left:["<<rect.left <<"]"<<endl;
  cout<<"top:["<<rect.top<<"]"<<endl<<endl;
  /**cout<<SendMessage(hWnd,WM_GETTEXT,(WPARAM)len,(LPARAM)szBuffer)<<endl;
  _tprintf(szWindow);
  cout<<szBuffer<<endl
  **/
  cout<<"####################"<<endl;
  iCount++;  
 }

 return TRUE;
}

/* main函数 */
int main(int argc, char* argv[])
{
 //test();
 EnumWindows(EnumWindowProc, NULL); //枚举所有顶层窗口
 cout<<"Find "<< iCount << " windows."<<endl;

 cin>>iCount;
 return 0;
}

/***********************代码 结束*******************************/

主要就是代码中的红色字体,在wcout前,先设置中文环境,并且在wcout时,需要用TEXT,这里只是为了把字符串转换成unicode版的,不然也会不能输出中文。具体见这里

另外,因为最开始就遇到无法正确输出中文的原因,在查找的过程中,找到与GetWindowText相关的一个资料,平常应该不太会注意到的,在这里也转载部分内容。

/***************************以下内容为转载的********************************/

GetWindowText函数远比你想象的要复杂。

在GetWindowText函数帮助文档中试图通过简短的文字来解释这个函数的复杂性,如果你无法理解一些长篇大论的文字,那么这种做法无疑是很好的,但简短的文字同样意味着整个内容会变得有些晦涩难懂。

下面,我们就来讲述GetWindowText函数的完整内容。

3.1窗口如何来管理文本

在窗口类中可以通过两种方法来管理文本:既可以让窗口自己进行管理,也可以让系统进行管理。默认的情况是由系统进行管理。

如果窗口类让系统来管理文本,那么系统会进行以下这些工作:

·对WM_NCCREATE消息进行默认的处理:将传递给CreateWindow/Ex函数的参数lpWindowName提取出来,并将这个字符串保存在某个“特殊的位置”。

·对WM_GETTEXT消息进行默认的处理:从“特殊的位置”上提取字符串。

·对WM_SETTEXT消息进行默认的处理:将字符串复制到“特殊的位置”。

如果是由窗口类自己来管理窗口中的文本,那么系统将不会做任何特殊的处理,而是由窗口类来负责响应WM_GETTEXT/WM_SETTEXT消息,并且直接返回/保存字符串。

框架窗口(Frame Windows)通常是由系统来管理窗口中的文本,而自定义控件(Custom control)通常是由它们自己来管理窗口中的文本。

3.2 深入GetWindowText函数

在GetWindowText函数中有一个要求:函数需要迅速地得到窗口文本并且不会被挂起。FindWindow函数需要通过窗口文本来查找窗口。而任务切换(Taskswitching)程序也需要获得窗口文本,以便在切换器(switcher)窗口中显示窗口的标题。某个挂起的程序阻塞其他应用程序的情况是不应该发生的。这就是任务切换程序所要面对的实际情况。

这就要求不应该发送WM_GETTEXT消息,因为WM_GETTEXT的目标窗口可能被挂起。此时,GetWindowText应该从“特殊的位置”上获取文本,因为这种做法不会受到挂起程序的影响。

而另一方面,GetWindowText也用于从对话框的控件中提取文本,而这些控件通常使用的是自定义的文本管理机制。因此,这又要求应该发送WM_GETTEXT消息,因为这是获得自定义管理文本的唯一方法。

于是,在GetWindowText函数中采取了一种折中的方法:

·如果是从同一进程的窗口中得到窗口文本,那么GetWindowText将发送WM_GETTEXT消息。

·如果是从另外一个进程的窗口中得到窗口文本,那么GetWindowText将会在“特殊的位置”上获取字符串,而不是发送消息。

根据第一条原则,如果你想要获得自己进程中的窗口文本,而这个窗口被挂起了,那么GetWindowText函数也会被挂起。不过,因为这个窗口是属于你自己的进程,所以函数被挂起是你自己犯的错误,并且你应该为此负责。发送WM_GETTEXT消息将确保我们能够正确地得到那些使用自定义文本管理方式的窗口(通常是自定义控件)中的文本。

根据第二条规则,如果想要获得另一个进程中的窗口文本,那么GetWindowText将不会发送消息,而只是在“特殊的位置”上获取字符串。通常,使用最多的方式是获取另一个进程中的框架窗口文本,而在框架窗口中一般不会使用自定义的窗口文本管理方式,因此往往能够获得正确的字符串。

而在GetWindowText的帮助文档中将上述讨论内容简化为“GetWindowText无法从另一个应用程序的窗口中得到文本”。

3.3如果不喜欢这些规则,该怎么办

如果你不喜欢第二条规则,例如希望得到另一个进程中自定义控件的文本,那么可以自己发送WM_GETTEXT消息。此时,由于没有使用GetWindowText函数,因此就不受这条规则的约束。

注意,如果目标窗口被挂起,那么你的应用程序将同样被挂起,因为SendMessage函数只有当目标窗口处理完这条消息时才会返回。

同样需要注意的是,由于WM_GETTEXT是在系统消息范围之内(0到WM_USER-1),因此像把当前进程中的缓冲区传送到目标进程以及从目标进程将结果字符串返回到当前进程中等这些操作[这个过程也被称之为列集(marshalling)],就不需要你自己进行特殊的处理。事实上,无论你采取什么样的特殊处理,最终都将是错误的。窗口管理器将自动为你完成列集操作。

3.4能否给出一个说明这种差异的示例

我们来考虑下面这个控件:

SampleWndProc(…)

{

case WM_GETTEXT:

Lstrcpyn((LPTSTR)lParam, TEXT(“Booga!, (int)wParam);

Return lstrlen((LPTSTR)lParam);

case WM_GETTEXTLENGTH: return 7; //lstrlen(“Booga!) + null

}

在应用程序A中,我们进行了以下的操作:

Hwnd = CreateWindow(“Sample”, “Frappy”, …);

 

现在来考虑进程B,这个进程得到了应用程序A创建的窗口句柄(无论通过什么方法):

TCHAR szBuf[80];

GetWindowText(hwnd, szBuf, 80);

 

上面这段代码将会返回szBuf=“Frappy”,因为这是从“特殊的位置”上获得窗口文本。然而,下面的代码:

SendMessage(hwnd, WM_GETTEXT, 80, (LPARAM)szBuf);

将会返回szBuf=“Booga!”。

3.5为什么GetWindowText的规则如此奇怪

让我们把时光机器(wayback machine)倒回1983年,当时个人电脑中的配置通常是8086的处理器,主频47MHz,两个360K的51/4英寸软盘驱动器(如果你很富有的话,也可能是一个软盘驱动器和一个10MB的硬盘驱动器),以及256KB内存。

这就是Windows 1.0在当时所处的情况。

Windows 1.0是一个协作式多任务(cooperatively multitasked)操作系统,并没有抢先式多任务(preemptive multitasking)的概念。如果程序得到了系统的控制权,那么它可以想占有多久,就占有多久。只有当你调用像PeekMessage或者GetMessage这样的函数时,才会将控制权转交给其他的应用程序。

这是很重要的,因为在缺少硬件内存管理器的情况下,你需要特别确保不会在内存使用上出现问题。

在协作式多任务操作系统中,很重要的一点是,如果你的程序正在运行,那么你就不仅要知道没有其他的程序在运行,而且还要知道每个窗口都会响应消息。为什么?因为如果这些窗口挂起了,那么你将无法得到系统控制权!

这意味着发送消息将总是安全的。你永远都不用担心将消息发送给一个挂起的窗口,因为你很清楚不会存在挂起的窗口。

因此,在Windows 1.0这个简单的系统中,GetWindowText函数是非常直接了当的:

int WINAPI GetWindowText(HWND hwnd, LPSTR pchBuf, int cch)

{

//在简单时期的做法

return SendMessage(hwnd, WM_GETTEXT, (WPARAM)cch, (LPARAM)pchBuff));

}

对于所有的窗口,以上代码无论在什么时候都是有效的。对于不同进程中的窗口并没有进行特殊处理。

当操作系统过渡到Win32时,抢先式多任务的方式强行改变了这个规则,此时,你想要进行通信的窗口可能不会对消息进行响应。

现在,你就遇到了一个向后兼容性的问题。正如前面的描述,系统的许多组件和程序都要求在获取窗口文本时不会遇到挂起的情况。因此,如何才能够实现在获取窗口文本时不会遇到挂起的情况,而同时又可以让一些控件(例如编辑控件等)能够进行自定义的文本管理工作?

在Win32中GetWindowText函数的规则正是为了解决这个相互矛盾的目标而制定的。

转载自csdn这里

 

» 本文链接地址:https://www.xidige.com/232

打开支付宝扫一扫,即可进行扫码打赏哦

扫码支持
扫码打赏,你说多少就多少

标签:

分享到:

扫一扫 在手机阅读、分享本文

上一篇: 下一篇:
评论区0人评论268人参与

电子邮件地址不会被公开。 必填项已用*标注

*

loading

赞助商广告