原著:Paul DiLascia
翻译:NorthTibet
下载源代码:CAtWork0505.exe (171KB) 未引用参数 int SomeFunction(int arg1, int arg2) { UNREFERENCED_PARAMETER(arg2) ... }我还看到过这样的代码: int SomeFunction(int arg1, int /* arg2 */) { ... }你能解释它们的差别吗?哪一种用法更好?
Judy McGeough
是啊!为什么呢?让我们从 UNREFERENCED_PARAMETER 开始吧。这个宏在 winnt.h 中定义如下: #define UNREFERENCED_PARAMETER(P) (P) 换句话说 UNREFERENCED_PARAMETER 展开传递的参数或表达式。其目的是避免编译器关于未引用参数的警告。许多程序员,包括我在内,喜欢用最高级别的警告 Level 4(/W4)进行编译。Level 4 属于“能被安全忽略的事件”的范畴。虽然它们可能使你难堪,但很少破坏你的代码。例如,在你的程序中可能会有这样一些代码行: int x=1; 但你从没用到过 x。也许这一行是你以前使用 x 时留下来的,只删除了使用它的代码,而忘了删除这个变量。Warning Level 4 能找到这些小麻烦。所以,为什么不让编译器帮助你完成可能是最高级别的专业化呢?用Level 4 编译是展示你工作态度的一种方式。如果你为公众使用者编写库,Level 4 则是社交礼节上需要的。你不想强迫你的开发人员使用低级选项清洁地编译他们的代码。 int SomeFunction(int arg1, int arg2) { return arg1+5; } 使用 /W4,编译器抱怨: “warning C4100: ''arg2'' : unreferenced formal parameter.” 为了骗过编译器,你可以加上 UNREFERENCED_PARAMETER(arg2)。现在编译器在编译你的引用 arg2 的函数时便会住口。并且由于语句: arg2; 实际上不做任何事情,编译器不会为之产生任何代码,所以在空间和性能上不会有任何损失。 void OnSize(UINT nType, int cx, int cy); 这里 cx/cy 是窗口新的宽/高,nType 是一个类似 SIZE_MAXIMIZED 或 SIZE_RESTORED 这样的编码,表示窗口是否最大化或是常规大小。一般你不会在意 nType,只会关注 cx 和 xy。所以如果你想用 /W4,则必须使用 UNREFERENCED_PARAMETER(nType)。OnSize 只是上千个 MFC 和 Windows 函数之一。编写一个基于 Windows 的程序,几乎不可能不碰到未引用参数。 void CMyWnd::OnSize(UINT /* nType */, int cx, int cy) { } 现在 nType 是未命名参数,其效果就像你敲入 OnSize(UINT, int cx, int cy)一样。那么现在的关键问题是:你应该使用哪种方法——未命名参数,还是 UNREFERENCED_PARAMETER? void CMyWnd::OnSize(UINT nType, int cx, int cy) { ASSERT(nType != SIZE_MAXIMIZE); ... // use cx, cy } 质检团队竭尽所能以各种方式运行你的程序,ASSERT 从没有弹出过,于是你认为编译生成 Release 版本是安全的。但是此时 _DEBUG 定义没有了,ASSERT(nType != SIZE_MAXIMIZE)展开为 ((void)0),并且 nType 一下子成了一个未引用参数!这样进入你干净的编译。你无法注释掉参数表中的 nType,因为你要在 ASSERT 中使用它。于是在这种情况下——你唯一使用参数的地方是在 ASSERT 中或其它 _DEBUG 条件代码中——只有 UNREFERENCED_PARAMETER 会保持编译器在 Debug 和 Release 生成模式下都没有问题。知道了吗? #pragma warning( disable : 4100 ) 4100 是未引用参数的出错代码。pragma 抑制其余文件/模块的该警告。用下面方法可以重新启用这个警告: #pragma warning( default : 4100 ) 不管怎样,较好的方法是在禁用特定的警告之前保存所有的警告状态,然后,等你做完之后再回到以前的配置。那样,你便回到的以前的状态,这个状态不一定是编译器的默认状态。 #pragma warning( push ) #pragma warning( disable : 4100 ) void SomeFunction(...) { } #pragma warning( pop ) 当然,对于未引用参数而言,这种方法未免冗长,但对于其它类型的警告来说可能就不是这样了。库生成者都是用 #pragma warning 来阻塞警告,这样他们的代码可以用 /W4 进行清洁编译。MFC 中充满了这样的 pragmas 指令。还有好多的 #pragma warning 选项我没有在本文讨论。有关它们的信息请参考相关文档。
Jirair Osygian
我创建了一个简单的 MFC SDI 程序,该程序用表单视图(Form View)显示一个计数器。我想通过右键单击任务栏上程序的最小化按钮来控制启动/停止这个计数器。在表单视图上通过按钮控制的启动/停止功能运行正常,我也能将启动/停止命令加到系统菜单。但我单击加入的系统菜单时没反应。我如何处理这些定制的系统菜单消息?
Monicque Sharman
我两个问题一起回答,Jirair 问题的答案很简单:当你右键单击任务栏上应用程序的最小化按钮时,用户看到的菜单与用户单击左上角应用程序标题栏图标或按 Alt+Space 所看到的菜单一样。如 Figure 1 所示。这个菜单被称为系统菜单,其中包括命令如:还原、最小化、最大化和关闭等。Figure 1 系统菜单 你可以调用 ::GetSystemMenu 来获得此系统菜单,然后可以添加、删除或修改菜单项。你甚至可以通过关掉 WS_SYSMENU 窗口创建式样标志或 PreCreateWindow 虚函数来完全屏蔽掉这个系统菜单。但不论你做什么,当用户右键单击任务栏上应用程序的最小化按钮时,这个系统菜单还是会显示出来的。 到了 Monicque 的问题:如果在系统菜单中添加自己的命令,MFC 是如何处理它们的呢?如果按常规来做 ——在某个地方写一个 ON_COMMAND 处理器并将它加到消息映射中,你会发现你的处理器不起作用,怎么会这样呢? 那是因为 Windows 和 MFC 处理系统命令的方式与处理普通菜单命令的方式不一样。当用户调用窗体中的常规菜单命令或按钮时,Windows 向主窗口发送一个 WM_COMMAND 消息。如果你使用 MFC,那么其命令路由机制将捕获此消息并通过操作系统将它路由到该命令的 ON_COMMAND 命令处理器对象。(有关 MFC 命令处理机制的详细内容,参见我在 MSJ 1995年7月发表的文章:“Meandering Through the Maze of MFC Message and Command Routing”)。 然而系统命令不属于 WM_COMMAND 消息范围。而属于另外一个叫做——WM_SYSCOMMAND 的消息。不论命令ID是真正的系统命令如 SC_MINIMIZE 和 SC_CLOSE,还是你自己添加的其它命令ID都是如此。为了处理系统菜单命令,你必须处理显式处理 WM_SYSCOMMAND 并且要选择你自己的命令 IDs。这就需要你在主窗口消息映射中添加ON_WM_SYSCOMMAND,它有一个处理函数如下: CMainFrame::OnSysCommand(UINT nID, LPARAM lp) { if (nID==ID_MY_COMMAND) { ... // 处理它 return 0; } // 传递到基类:这一步很重要! return CFrameWnd::OnSysCommand(nID, lp); } 如果该命令不是你的,不要忘了将它传递到你的基类处理——典型地,那就是 CFrameWnd 或 CMDIFrameWnd。否则,Windows 将无法得到此消息,并且会破坏内建的命令。 void CMyApp::OnAppAbout() { static CAboutDialog dlg; dlg.DoModal(); } MFC 一个真正很酷的特性是它的命令路由系统,它使得象 CMyApp 这样的非窗口对象也能处理菜单命令。许多程序员甚至都不了解怎么会有这样的例外。如果你已经在应用程序对象中处理 ID_APP_ABOUT,那把ID_APP_ABOUT 添加到系统菜单后,为什么还要去实现一套单独的机制? int CMainFrame::OnCreate(...) { // 将我的菜单项添加到系统菜单 CMenu* pMenu = GetSystemMenu(FALSE); pMenu->AppendMenu(..ID_MYCMD1..); pMenu->AppendMenu(..ID_MYCMD2..); // 通过 MFC 路由系统命令 m_sysCmdHook.Init(this); return 0; } 一旦你调用 CSysCmdRouter::Init,你便可以按常规方式处理 ID_MYCMD1 和 ID_MYCMD2,为 MFC 命令路由机制中的任何对象编写 ON_COMMAND 处理例程——视图,文档,框架,应用程序或通过改写 OnCmdMsg 添加的任何其它命令对象。CSysCmdRouter 还让你用 ON_UPDATE_COMMAND_UI 处理器更新系统菜单。唯一要注意的是确保命令IDs不要与其它菜单命令(除非他们确实代表相同的命令)或内建系统命令发生冲突,内建系统命令从 SC_SIZE = 0xF000 开始。Visual Studio .NET 指定的命令 IDs 从 0x8000 = 32768 开始,所以如果你让 Visual Studio 来指定 IDs,只要不超过 0xF000-0x8000 = 0x7000 个命令即可。也就是十进制的 28,762。如果你的应用程序有超过 28000 个命令,那么你需要咨询编程精神病专家。 void CFrameWnd::OnInitMenuPopup(CMenu* pMenu, UINT nIndex, BOOL bSysMenu) { if (bSysMenu) return; // don''t support system menu ... } MFC 初始化系统菜单时不做任何事情。为什么要去关心这种事呢?万一你要让 bSysMenu 为 FALSE,即使是系统菜单,那该怎么办?这恰恰是 CSysCmdRouter 做的事情。它截取 WM_INITMENUPOPUP 并清除 bSysMenu 标志,也就是 LPARAM 的 HIWORD: if (msg==WM_INITMENUPOPUP) { lp = LOWORD(lp); // (set HIWORD = 0) } 现在,当 MFC 获得 WM_INITMENUPOPUP,它认为该菜单是常规菜单。只要你的命令 IDs 与真正的系统菜单不冲突,一切都运行得很好。如果你改写 OnInitMenuPopup,唯一丢失的东西是不能从主窗口菜单中区分系统菜单。嘿,你不能什么都想要!通过改写 CWnd::WindowProc,你总是能处理 WM_INITMENUPOPUP 的,或你想要区分,就比较 HMENUs。但你确实不用关心命令来自何处。
|
作者简介 Paul DiLascia 是一名自由作家,顾问和 Web/UI 设计者。他是《Writing Reusable Windows Code in C++》书(Addison-Wesley, 1992)的作者。通过 http://www.dilascia.com 可以获得更多了解。 |
本文出自 MSDN Magazine 的 May 2005 期刊,可通过当地报摊获得,或者最好是 订阅 |