Win32 汇编学习 (8):菜单

本贴最后更新于 2352 天前,其中的信息可能已经时过境迁

这次我们将在我们的应用程序中加入一个菜单。

理论:

菜单可以说是 WINDOWS 最重要的元素之一。有了它,用户可以方便地选择操作命令.用户只要细读一下所有的菜单项就可以明了应用程序所提供的大概功能,而且可以立即操作,无须去阅读手册了.正因为菜单给了用户一种方便的方式,所以您在应用程序中加入菜单时就要遵守一般的标准.譬如:一般头两个菜单项是"File"和"Edit",最后是"Help",您可以在这中间插入您要定义的菜单项.如果所运行的菜单命令会弹出一个对话框,那么就要在该菜单项后加入省略符(...).菜单是一种资源,除菜单外还有其它像对话框,字符串,图标,位图资源等.在链接时链接程序将把资源加入到可执行程序中去,最后我们的执行程序中就既包括机器指令又包括了资源. 您可以在任何文本编辑器中编写脚本文件,在文件中您可以指定资源呈现出来的外观和其它的一些属性.当然更直观的方法是用资源编辑器,通常资源编辑器都打包在编译环境中,像 Visual C++ 带了资源编辑器. 我们可以按以下方式来定义一个菜单资源:

MyMenu  MENU 
{ 
   [menu list here] 
} 

这和 C 语言中的结构体的定义非常相似。 MyMenu 类似于被定义的变量,而 MENU 则类似于关键字。当然您可以用另外一种办法,那就是用 BEGIN 和 END 来代替花括号,这和 PASCAL 语言中的风格相同。
在菜单项的列表中是一大串的 MENUITEMPOPUP 语句。MENUITEM 定义了一个菜单项,当选择后不会激活对话框。它的语法如下:

MENUITEM "&text", ID [,options] 

它由关键字 MENUITEM 开头,紧跟在 MENUITEM 后的是指菜单项的名称字符串,符号“&“后的第一个字符将会带下划线,它也是该菜单项的快捷键。ID 的作用当该菜单被选中时,WINDOWS 的消息处理过程用来区分菜单项用的。毫无疑问,ID 号必须唯一。
options 有以下可供选择的属性:

  • GRAYED 代表该菜单项处于非激活状态,即当其被选中时不会产生 WM_COMMAND 消息。该菜单以灰色显示。
  • INACTIVE 代表该菜单项处于非激活状态,即当其被选中时不会产生 WM_COMMAND 消息。该菜单以正常颜色显示。
  • MENUBREAK 该菜单项和随后的菜单项会显示在新列中。
  • HELP 该菜单项和随后的菜单项右对齐。

POPUP 的语法如下:

POPUP "&text" [,options] 
{ 
  [menu list] 
}

POPUP 定义了一个菜单项当该菜单项被选中时又会弹出一个子菜单。
另外有一种特别类型的 MENUITEM 语句 MENUITEM SEPARATOR,它表示在菜单项位置画一条分隔线。定义完菜单后,您就可以在程序中使用脚本中定义的菜单资源了。您可以在程序的两个地方(或叫做用两种方式)使用它们:

  • WNDCLASSEX 结构体的成员 lpszMenuName 中。譬如,您有一个菜单“FirstMenu“,您可以按如下方法把它联系到您的窗口:
.DATA 
MenuName  db "FirstMenu",0 
........................... 
........................... 
.CODE 
........................... 
mov   wc.lpszMenuName, OFFSET MenuName 
........................... 
  • CreateWindowEx 函数中指明菜单的句柄:
.DATA 
MenuName  db "FirstMenu",0 
hMenu HMENU ? 
........................... 
........................... 
.CODE 
........................... 
invoke LoadMenu, hInst, OFFSET MenuName 
mov   hMenu, eax 
invoke CreateWindowEx,NULL,OFFSET ClsName,\ 
            OFFSET Caption, WS_OVERLAPPEDWINDOW,\ 
            CW_USEDEFAULT,CW_USEDEFAULT,\ 
            CW_USEDEFAULT,CW_USEDEFAULT,\ 
            NULL,\ 
           hMenu,\ 
            hInst,\ 
            NULL\ 
........................... 

您也许会问,这两着之间有什么不同呢?**当您用第一种方法时,由于是在窗口类中指定,故所有由该窗口类派生的窗口都将有相同的菜单。如果您想要从相同的类中派生的窗口有不同的菜单那就要使用第二中方法,该方法中通过函数 CreateWindowEx 指定的菜单会“覆盖” WNDCLASSEX 结构体中指定的菜单。**接下来我们看看当用户选择了一个菜单项时它是如何通知 WINDOWS 窗口过程的:当用户选择了一个菜单项时,WINDOWS 窗口过程会接收到一个 WM_COMMAND 消息,传进来的参数 wParam 的低字节包含了菜单项的 ID。好了,上面就是关于菜单项的一切,下面我们就来实践。

例子:

第一个例子显示了指定一个菜单项的第一种方法:

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include windows.inc
include user32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our Sixth Window",0
MenuName db "FirstMenu",0
Test_string db "你选择了测试菜单项",0
Hello_string db "你好",0
Goodbye_string db "再见",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.const
IDM_TEST equ 1
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_TEXT equ 4

.code
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
	
	LOCAL wc:WNDCLASSEX
	LOCAL msg:MSG
	LOCAL hwnd:HWND
	mov wc.cbSize,SIZEOF WNDCLASSEX
	mov wc.style,CS_HREDRAW or CS_VREDRAW
	mov wc.lpfnWndProc, offset WndProc
	mov wc.cbClsExtra,NULL
	mov wc.cbWndExtra,NULL
	push hInst
	pop wc.hInstance
	mov wc.hbrBackground,COLOR_WINDOW+1
	mov wc.lpszMenuName,offset MenuName
	mov wc.lpszClassName,offset ClassName
	invoke LoadIcon,NULL,IDI_APPLICATION
	mov wc.hIcon,eax
	mov wc.hIconSm,eax
	invoke LoadCursor,NULL,IDC_ARROW
	mov wc.hCursor,eax
	invoke RegisterClassEx,addr wc
	invoke CreateWindowEx,NULL,addr ClassName,addr AppName,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInst,NULL
	mov hwnd,eax
	invoke ShowWindow,hwnd,SW_SHOWNORMAL
	invoke UpdateWindow,hwnd
	.while TRUE
		invoke GetMessage,addr msg,NULL,0,0
		.break .if (!eax)
		invoke DispatchMessage,addr msg	
	.endw
	mov eax,msg.wParam
	ret

WinMain endp

WndProc proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
	
	.if uMsg==WM_DESTROY
		invoke PostQuitMessage,NULL
	.elseif uMsg==WM_COMMAND
		mov eax,wParam
		.if ax==IDM_TEST
			invoke MessageBox,NULL,addr Test_string,offset AppName,MB_OK
		.elseif ax==IDM_HELLO
			invoke MessageBox,NULL,addr Hello_string,offset AppName,MB_OK
		.elseif ax==IDM_GOODBYE
			invoke MessageBox,NULL,addr Goodbye_string,offset AppName,MB_OK
		.else
			invoke DestroyWindow,hWnd
		.endif
	.else
		invoke DefWindowProc,hWnd,uMsg,wParam,lParam
		ret
	.endif
	xor eax,eax
	ret

WndProc endp
end start

Menu.rc

#define IDM_TEST 1 
#define IDM_HELLO 2 
#define IDM_GOODBYE 3 
#define IDM_EXIT 4 

FirstMenu MENU 
{ 
 POPUP "&PopUp" 
        { 
         MENUITEM "&Say Hello",IDM_HELLO 
         MENUITEM "Say &GoodBye", IDM_GOODBYE 
         MENUITEM SEPARATOR 
         MENUITEM "E&xit",IDM_EXIT 
        } 
 MENUITEM "&Test", IDM_TEST 
} 

分析:
我们先来分析资源文件:

#define IDM_TEST 1                /* equal to IDM_TEST equ 1*/ 
#define IDM_HELLO 2 
#define IDM_GOODBYE 3 
#define IDM_EXIT 4 

上面的几行定义了菜单项的 ID 号。只要注意菜单项 ID 号必须唯一外,您可以给 ID 号任何值。

FirstMenu MENU 

用关键字 MENU 定义菜单。

 POPUP "&PopUp" 
        { 
         MENUITEM "&Say Hello",IDM_HELLO 
         MENUITEM "Say &GoodBye", IDM_GOODBYE 
         MENUITEM SEPARATOR 
         MENUITEM "E&xit",IDM_EXIT 
        } 

定义一个有四个菜单项的子菜单,其中第三个菜单项是一个分隔线。

 MENUITEM "&Test", IDM_TEST 

定义主菜单中的一项。下面我们来看看源代码。

MenuName db "FirstMenu",0
Test_string db "你选择了测试菜单项",0
Hello_string db "你好",0
Goodbye_string db "再见",0

MenuName 是资源文件中指定的菜单的名字。因为您可以在脚本文件中定义任意多个菜单,所以在使用前必须指定您要使用那一个,接下来的行是在选中菜单项时显示在相关对话框中的字符串。

IDM_TEST equ 1
IDM_HELLO equ 2 
IDM_GOODBYE equ 3 
IDM_EXIT equ 4 

定义用在 WINDOWS 窗口过程中的菜单项 ID 号。这些值必须和脚本文件中的相同。

    .ELSEIF uMsg==WM_COMMAND 
        mov eax,wParam 
        .IF ax==IDM_TEST 
            invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK 
        .ELSEIF ax==IDM_HELLO 
            invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK 
        .ELSEIF ax==IDM_GOODBYE 
            invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK 
        .ELSE 
            invoke DestroyWindow,hWnd 
        .ENDIF 

在本窗口过程中我们处理 WM_COMMAND 消息。当用户选择了一个菜单项时,该菜单项的 ID 放入参数 wParam 中被同时送到 WINDOWS 的窗口过程,我们把它保存到 eax 寄存器中以便和预定义的菜单项 ID 比较用。前三种情况下,当我们选中 Test、Say Hello、Say GoodBye 菜单项时,会弹出一个对话框其中显示一个相关的字符串,选择 Exit 菜单项时,我们就调用函数 DestroyWindow,其中的参数是我们窗口的句柄,这样就销毁了窗口。就像您所看到的,通过在一个窗口类中指定菜单名的方法来给一个应用程序生成一个菜单是简单而直观的。除此方法外您还可以用另一种方法,其中资源文件是一样的,源文件中也只有少数的改动,这些改动如下:

.data? 
hInstance HINSTANCE ? 
CommandLine LPSTR ? 
hMenu HMENU ?                    ; handle of our menu 

定义了一个变量来保存我们的菜单的句柄,然后:

        invoke LoadMenu, hInst, OFFSET MenuName 
        mov    hMenu,eax 
        INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ 
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ 
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,hMenu,\ 
           hInst,NULL 

调用 LoadMenu 函数,该函数需要实例句柄和菜单名的字符串,调用的结果返回指向菜单的句柄,然后传给函数 CreateWindowEx 刚返回的菜单句柄就可以了。

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1083 引用 • 3461 回帖 • 257 关注
  • asm
    15 引用 • 5 回帖
  • Windows

    Microsoft Windows 是美国微软公司研发的一套操作系统,它问世于 1985 年,起初仅仅是 Microsoft-DOS 模拟环境,后续的系统版本由于微软不断的更新升级,不但易用,也慢慢的成为家家户户人们最喜爱的操作系统。

    216 引用 • 463 回帖
  • 读书笔记
    8 引用 • 3 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
Akkuman
python与二次元爱好者,开车司机 荆州

推荐标签 标签

  • BAE

    百度应用引擎(Baidu App Engine)提供了 PHP、Java、Python 的执行环境,以及云存储、消息服务、云数据库等全面的云服务。它可以让开发者实现自动地部署和管理应用,并且提供动态扩容和负载均衡的运行环境,让开发者不用考虑高成本的运维工作,只需专注于业务逻辑,大大降低了开发者学习和迁移的成本。

    19 引用 • 75 回帖 • 616 关注
  • frp

    frp 是一个可用于内网穿透的高性能的反向代理应用,支持 TCP、UDP、 HTTP 和 HTTPS 协议。

    16 引用 • 7 回帖 • 2 关注
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    124 引用 • 580 回帖
  • Flume

    Flume 是一套分布式的、可靠的,可用于有效地收集、聚合和搬运大量日志数据的服务架构。

    9 引用 • 6 回帖 • 613 关注
  • Kotlin

    Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,由 JetBrains 设计开发并开源。Kotlin 可以编译成 Java 字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。在 Google I/O 2017 中,Google 宣布 Kotlin 成为 Android 官方开发语言。

    19 引用 • 33 回帖 • 51 关注
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    544 引用 • 3531 回帖
  • 区块链

    区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。所谓共识机制是区块链系统中实现不同节点之间建立信任、获取权益的数学算法 。

    91 引用 • 751 回帖
  • SQLServer

    SQL Server 是由 [微软] 开发和推广的关系数据库管理系统(DBMS),它最初是由 微软、Sybase 和 Ashton-Tate 三家公司共同开发的,并于 1988 年推出了第一个 OS/2 版本。

    19 引用 • 31 回帖 • 2 关注
  • 面试

    面试造航母,上班拧螺丝。多面试,少加班。

    324 引用 • 1395 回帖 • 1 关注
  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖
  • Ruby

    Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。

    7 引用 • 31 回帖 • 196 关注
  • NGINX

    NGINX 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 NGINX 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。

    311 引用 • 546 回帖
  • 快应用

    快应用 是基于手机硬件平台的新型应用形态;标准是由主流手机厂商组成的快应用联盟联合制定;快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台;以平台化的生态模式对个人开发者和企业开发者全品类开放。

    15 引用 • 127 回帖 • 1 关注
  • 分享

    有什么新发现就分享给大家吧!

    245 引用 • 1776 回帖 • 1 关注
  • Ubuntu

    Ubuntu(友帮拓、优般图、乌班图)是一个以桌面应用为主的 Linux 操作系统,其名称来自非洲南部祖鲁语或豪萨语的“ubuntu”一词,意思是“人性”、“我的存在是因为大家的存在”,是非洲传统的一种价值观,类似华人社会的“仁爱”思想。Ubuntu 的目标在于为一般用户提供一个最新的、同时又相当稳定的主要由自由软件构建而成的操作系统。

    123 引用 • 168 回帖
  • jsoup

    jsoup 是一款 Java 的 HTML 解析器,可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API,可通过 DOM,CSS 以及类似于 jQuery 的操作方法来取出和操作数据。

    6 引用 • 1 回帖 • 473 关注
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    88 引用 • 1234 回帖 • 441 关注
  • abitmean

    有点意思就行了

    39 关注
  • 资讯

    资讯是用户因为及时地获得它并利用它而能够在相对短的时间内给自己带来价值的信息,资讯有时效性和地域性。

    54 引用 • 85 回帖
  • gRpc
    11 引用 • 9 回帖 • 49 关注
  • TensorFlow

    TensorFlow 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。

    20 引用 • 19 回帖 • 1 关注
  • 运维

    互联网运维工作,以服务为中心,以稳定、安全、高效为三个基本点,确保公司的互联网业务能够 7×24 小时为用户提供高质量的服务。

    148 引用 • 257 回帖
  • 锤子科技

    锤子科技(Smartisan)成立于 2012 年 5 月,是一家制造移动互联网终端设备的公司,公司的使命是用完美主义的工匠精神,打造用户体验一流的数码消费类产品(智能手机为主),改善人们的生活质量。

    4 引用 • 31 回帖 • 8 关注
  • ngrok

    ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。

    7 引用 • 63 回帖 • 613 关注
  • CAP

    CAP 指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。

    11 引用 • 5 回帖 • 580 关注
  • 游戏

    沉迷游戏伤身,强撸灰飞烟灭。

    171 引用 • 814 回帖
  • ActiveMQ

    ActiveMQ 是 Apache 旗下的一款开源消息总线系统,它完整实现了 JMS 规范,是一个企业级的消息中间件。

    19 引用 • 13 回帖 • 641 关注