消息值,托管字符串,扰乱代码及其它2011-01-20小刀人许多 C++ 爱好者已经对我最近的专栏中渗入了太多关于C#的内容表示关注。我承认这一点!我唯一的辩解是:由于 Microsoft® .NET Framework 已经获得广泛的认同,给我发送关于C#问题的读者越来越多,同时因为C# 和 C++ 如此类似,所以我就回答了一部分他们的问题。这不是我有意疏远 C++ 爱好者——上帝知道,我就是他们中的一员啊!不管怎样,为了突出重点,从这个月开始的 C++ 专栏将更多地专注于 C++ 的内容,包括托管扩展以及 MFC 这样的传统内容。因此提出你的 C++ 问题吧!我特别鼓励你提出关于托管 C++ 的问题。你使用它的时候有些什么体会?在你 2004 年 3 月 的专栏中,你通过重定义 WM_USER+1 实现 了 CMyOpenDlg 的初始化。我认为在通常意义上你误用了 WM_USER 的范围(它是保留给所有 RegisterClass 使用者的),此外还错在 WM_USER+1 已经 是一个预定义的对话框消息 DM_SETDEFID。你不应该再对这个消息用不同的值了吧?Jeff Partch你说得完全正确!WM_USER 是为所有实现窗口类的人保留的——无论是你,还是友好的 Redmondtonians(译注:Microsoft), 仰或是 Gleepoid 行星上的叛逆者。Figure 1 展示了正式的 Windows 消息值的细目分类,对此每个人都应该至少每十年复习一次。WM_USER 到 0x7FFF 是为私有窗口类保留的。你可以将这个范围 认为是在特定的窗体类中有意义的专用消息。举个例子,状态栏控件的 SB_SETTEXTA 使用 WM_USER+1。同时正如你所指出的一样, 对话框的 DM_GETDEFID 和 DM_SETDEFID 使用 WM_USER+0 和 WM_USER+1。我在 2004 年 3 月的专栏中使用 WM_USER+1 是与 DM_GETDEFID 相冲突的。想要定义其自己消息的应用程序应该使用 WM_APP。WM_APP 是确保不会与系统(WM_CREATE 等等)或类/特定控件消息如 DM_GETDEFID 相冲突的。Windows 定义 的 WM_APP 如下:
#if (WINVER >= 0x0400)
#define WM_APP 0x8000
#endif
正如每个 Windows 极客(Geek)所知道的那样,WINVER 0x0400 是指 Windows 95、Windows 98 和 Windows NT。所以 WM_APP 的使用还不到十年,这解释了为什么我没注意到它——在 2005 年之前,我不应该对下一个十年的消息范围妄加评论!但是我真的要为 CMyOpenDlg 使用 WM_APP 吗?CMyOpenDlg 介于 Windows 和应用程序之间。我把它写成一个扩展 ,以便其他程序员可以在其应用程序中使用。如果某个程序员已经在使用 WM_APP,并且是用 CMyOpenDlg,那么是否有冲突呢?在文件打开对话情况中这是不太可能的——但如果我正在写一个自定义控件 ,比如工具栏或进度条并需要定义自己的消息时该怎么办呢?我可以让他们基于 WM_APP 来避免和基本控件冲突(但是这时存在与应用程序冲突的风险)或者我可以选一个 消息号如:WM_USER+400,它远远超出 Windows 控件消息范围。不幸的是,这里没有正确的答案。这是一个自 Windows 发布以来就一直 折磨着控件和类库设计者的问题。简直就没有一个十全十美方法来划分消息空间而保证绝不导致冲突。你每次都必须根据经验来采取决定。对于 CMyOpenDialog,我 将选择 WM_USER+10 这样的消息,它不会与 DM_XXX 消息冲突。用注册消息可能又太夸张了。说到注册消息,WM_APP 取值范围和已注册消息取值范围(0xC000 - 0xFFFF)有什么不同,什么时候应该选用其中一个而不是另一个? 如果你只是在对自己说——就是,如果你打算仅仅将发送消息到自己应用程序的窗口中,你可以使用 WM_APP 取值范围。注册消 息则是全局消息,用于发送到别的程序员写的其它应用程序。举个例子,如果你正在写一个协作-管理应用程序,它自己能通过发送一个特定的消息来 确定具有相似意向的程序,这时你应该使用注册消息:
UINT WM_SAYHELLO = RegisterWindowMessage(_T("WmSayHello"));
这时其它协作应用程序可以用这个特定的名称“WmSayHello”注册并获得相同的值,于是他们都可以互相通信。每个 Windows 会话实际的 WM_SAYHELLO 值 将会各不相同,但在某一个对话中,注册它的所有应用程序将具有相同的值。当然,该值将总在 0xC000 - 0xFFFF 范围之内。糟了——MSDN® 杂志有最精明的读者!我正用托管 C++ 写一个应用程序并且碰到一个关于字符串 文字量的问题。我知道我能用_T("sample string") 或 S"Sample String" 创建一个字符串。我也知道 S 字符串文字量更有效率,但好象很少有人知道它为什么有效以及效率高在哪。两种类型的字符串 有何差别?托管字符串文字量确切含义是什么,以及为什么它更好?Randy Goodwin理解托管字符串文字量和普通C/C++ 字符串文字量之间差别的最好方法是用它们写相同的代码并考察其编译结果。我写了一个简单的托管 C++ 程序,StrLit.cpp,这个程序用C++和.NET风格创建了一些 String 文字量,代码如 Figure 2 所示。Figure 3 和 Figure 4 展示了用 ILDASM 反汇编的 Microsoft 中间语言(MSIL)代码。正如你可能知道的许多事情一样,其首要差别就是当你使用 C++ 文字量时,编译器产生一个对相应 String 构造函数的调用:
// String* c2 = new String(_T("This is a TCHAR string"));
ldsflda valuetype $ArrayType$0x11197cc2
modopt([Microsoft.VisualC]Microsoft.VisualC.IsConstModifier)
""?A0x1f1e2151.unnamed-global-0""
newobj instance void [mscorlib]System.String::.ctor(int8*)
我会马上解释这段冗长而令人费解的文字,但其基本思路是编译器加载了 C++ 字符串文字量的地址,然后产生一个 newobj 指令,该指令将文字量地址到 String 的构造函数。另外,编译器为非托管字符串文字量创建了一个专门的 Array 类型——这就是前面代码段中样子奇怪的 $ArrayType$0x11197cc2,还有一个 被命名为?A0x1f1e2151.unnamed-global-0 的静态实例,如图 Figure 3 所示: