《汇编语言程序设计》
——计算器程序设计
目录
一、 题目与目标
1. 题目
2. 学习目的
二、 分析与设计
1. 系统分析
2. 系统设计
3. 功能分析
4. 功能设计
5. 界面设计
6. 文件设计
三、 程序系统说明书
1. 创建计算器界面
2. 引入头文件及库
3. 定义常量
4. 函数声明
5. 程序说明
Ø 工具子程序说明
Ø 主程序
Ø WinMain主程序
Ø 消息处理程序
四、 设计与思考
1. 为什么使用对话框?
2. 如何应用系统的外观?
3. 关于最小化
4. 关于计算器
5. 为什么要设计安装文件?
6. 为什么要播放音乐?
五、 课程设计的体会
六、 参考资料
七、 附录
1. 系统模块总图
2. 系统文件清单
使用Win32编程设计一个功能及界面风格类似于Windows计算器的计算器程序,只要求实现标准型计算器。
主要实现的功能:
包含基本的四则运算、倒数运算、平方根运算。支持存储区的存储、清除、调出、累加等功能。
Ø WIN32汇编程序编写。
Ø 用汇编实现简单的算法。
Ø 浮点数运算(浮点指令或者自己编程模拟)。
Ø 综合解决问题的能力。
本程序为Win32窗口应用程序,因此采用Windows开发包的文档中规定的Windows程序标准框架进行编程设计。
按照Windows程序标准框架,主程序用于获得并保存本程序的句柄,并调用窗口主程序WinMain创建窗口并进入消息循环。WinMain程序将获取的消息分发给消息处理程序Calculate进行处理。主程序及窗口主程序结构如下图:
消息处理程序Calculate用于相应窗口创立、销毁、按键等消息并进行处理,根据系统功能,消息处理程序Calculate结构图如下:
3. 功能分析
如图所示,Windows自带的计算器按照功能划分可以分为以下5个区域:
显示区:文本框,用于显示输入的操作数及结果
数字键入区:在显示区中显示数字、小数点、正负号等;
运算区:包含双目运算符(+ - * /)、单目运算符(sqrt()、%、1/x)、等于号等
记忆区:清除记忆(MC)、显示记忆(MR)、记忆当前(MS)、记忆加(M+)以及记忆区存储情况的标签
清除键区:退格(Backspace)、清除当前数据(CE)、初始化操作(C)
Ø 数字:添加文本框字符串添加数字字符,调用函数BtnNum完成该功能;
Ø 小数点:为当前输入数字添加小数点,将判断是否小数点的变量HasPoint赋值为1
Ø 正负号:将当前数字取相反数并在对话框显示,拟通过浮点运算求相反数并调用ShowNum函数显示数字
Ø 双目运算符:计算结果,调用函数BtnOperator实现运算功能
Ø 等号:计算结果,调用函数BtnEqual实现运算功能
Ø 单目运算符:立即对当前数字进行运算并输出结果
Ø MS:将当前数据保存在变量Remember中,并在记忆区存储情况的标签中显示相应的信息
Ø M+:将当前数据加到变量Remember上,并在记忆区存储情况的标签中显示相应的信息
Ø MR:将变量Remember数据显示到文本框中;
Ø MC:将变量Remember归零,并在记忆区存储情况的标签中显示相应的信息
Ø C:初始化计算器,调用函数Init实现该功能,并在文本框显示0.
Ø CE:将当前数字清零
Ø Backspace:删除当前数据的末位数字
系统界面仿照Windows计算器程序界面设计,并使用资源文件进行定义,设计界面如下:
6. 文件设计
程序源文件包含两个部分:
Ø 头文件(Calculator.inc):头文件中引入程序所需要的库以及常量和函数申明
Ø 源文件(Calculator.asm):汇编程序源代码
Ø 资源文件(Calculator.rc):定义程序的窗口界面以及相关资源
Ø 说明文件(Calculator.exe.manifest):说明程序的相关配置及信息
利用资源文件定义系统界面,代码如下
#include "resource.h"
#define ISOLATION_AWARE_ENABLED
#define ID_NUM0 300 #define ID_NUM1 301 #define ID_NUM2 302 #define ID_NUM3 303 #define ID_NUM4 304 #define ID_NUM5 305 #define ID_NUM6 306 #define ID_NUM7 307 #define ID_NUM8 308 #define ID_NUM9 309 #define ID_NEG 310 #define ID_POINT 311 #define ID_MUL 312 #define ID_DIV 313 #define ID_SUB 314 #define ID_ADD 315 #define ID_EQU 316 #define ID_PER 317 #define ID_DAO 318 #define ID_SQRT 319 #define ID_MC 320 #define ID_MR 321 #define ID_MS 322 #define ID_MPLUS 323 #define ID_M 324 #define ID_BACK 325 #define ID_CE 326 #define ID_C 327 #define ID_RESULT 328 #define ID_COPY 1001 #define ID_PASTE 1002 #define ID_STANDARD 1003 #define ID_SCIENCE 1004 #define ID_PACKET 1006 #define ID_HELP 1007 #define ID_ABOUT 1008 #define ID_EXIT 1009
Calculator DIALOGEX 0, 0, 170, 133 STYLE DS_CENTER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX CLASS "Calculator" CAPTION "计算器" FONT 8, "Tahoma" BEGIN PUSHBUTTON "0",ID_NUM0,36,99,23,16,0 PUSHBUTTON "1",ID_NUM1,36,81,23,16,0 PUSHBUTTON "2",ID_NUM2,61,81,23,16,0 PUSHBUTTON "3",ID_NUM3,87,81,23,16,0 PUSHBUTTON "4",ID_NUM4,36,63,23,16,0 PUSHBUTTON "5",ID_NUM5,61,63,23,16,0 PUSHBUTTON "6",ID_NUM6,87,63,23,16,0 PUSHBUTTON "7",ID_NUM7,36,44,23,16,0 PUSHBUTTON "8",ID_NUM8,61,44,23,16,0 PUSHBUTTON "9",ID_NUM9,87,44,23,16,0 PUSHBUTTON "+/-",ID_NEG,61,99,23,16,0 PUSHBUTTON ".",ID_POINT,87,99,23,16,0 PUSHBUTTON "/",ID_DIV,113,44,23,16,0 PUSHBUTTON "*",ID_MUL,113,63,23,16,0 PUSHBUTTON "-",ID_SUB,113,81,23,16,0 PUSHBUTTON "+",ID_ADD,113,99,23,16,0 PUSHBUTTON "sqrt",ID_SQRT,139,44,23,16,0 PUSHBUTTON "%",ID_PER,139,63,23,16,0 PUSHBUTTON "1/x",ID_DAO,139,81,23,16,0 PUSHBUTTON "=",ID_EQU,139,99,23,16,0 PUSHBUTTON "MC",ID_MC,6,44,23,16,0 PUSHBUTTON "MR",ID_MR,6,63,23,16,0 PUSHBUTTON "MS",ID_MS,6,81,23,16,0 PUSHBUTTON "M+",ID_MPLUS,6,99,23,16,0 PUSHBUTTON "Backspace",ID_BACK,36,23,42,16,0 PUSHBUTTON "CE",ID_CE,79,23,41,16,0 PUSHBUTTON "C",ID_C,122,23,41,16,0 EDITTEXT ID_RESULT,5,2,160,13,ES_RIGHT | ES_NUMBER ,0 CTEXT "",ID_M,9,23,17,14,SS_SUNKEN | NOT WS_BORDER END
Menu MENU LOADONCALL BEGIN POPUP "编辑(&F)" BEGIN MENUITEM "复制(&C) Ctrl+C",ID_COPY MENUITEM "粘贴(&P) Ctrl+P",ID_PASTE MENUITEM SEPARATOR MENUITEM "关闭(&E)",ID_EXIT END POPUP "查看(&V)" BEGIN MENUITEM "标准型(&T)",ID_STANDARD MENUITEM "科学型(&S)",ID_SCIENCE,GRAYED MENUITEM SEPARATOR MENUITEM "数字分组(&I)",ID_PACKET END POPUP "帮助(&H)" BEGIN MENUITEM "帮助主题(&H)",ID_HELP MENUITEM SEPARATOR MENUITEM "关于计算器(&A)",ID_ABOUT END POPUP "", GRAYED BEGIN MENUITEM "复制(&C) Ctrl+C",1001 MENUITEM "粘贴(&P) Ctrl+P",1002 MENUITEM SEPARATOR MENUITEM "标准型(&T)",1003 MENUITEM "科学型(&S)",1004,GRAYED MENUITEM SEPARATOR MENUITEM "数字分组(&I)",1006 MENUITEM SEPARATOR MENUITEM "帮助主题(&H)",1007 MENUITEM "关于计算器(&A)",1008 MENUITEM SEPARATOR MENUITEM "关闭(&E)",1009 END END
Icon ICON MOVEABLE PURE LOADONCALL DISCARDABLE "Calculator.ico" |
文件分别定义了对话框,菜单和Icon图标等资源,为了在程序中方便对消息的处理,此处有意连续定义了ID_NUM0~ID_NUM9
在Calculator.inc头文件中统一定义程序所需的头文件及引入库
;--------------------------- 头文件声明--------------------------- include windows.inc include user32.inc include kernel32.inc include comctl32.inc include masm32.inc include shell32.inc ;--------------------------- 引入库声明--------------------------- includelib user32.lib includelib comctl32.lib includelib masm32.lib |
在Calculator.inc中定义程序所需常量
;---------------------------- 常量声明---------------------------- ID_NUM0 equ 300 ID_NUM1 equ 301 ID_NUM2 equ 302 ID_NUM3 equ 303 ID_NUM4 equ 304 ID_NUM5 equ 305 ID_NUM6 equ 306 ID_NUM7 equ 307 ID_NUM8 equ 308 ID_NUM9 equ 309 ID_NEG equ 310 ID_POINT equ 311 ID_MUL equ 312 ID_DIV equ 313 ID_SUB equ 314 ID_ADD equ 315 ID_EQU equ 316 ID_PER equ 317 ID_DAO equ 318 ID_SQRT equ 319 ID_MC equ 320 ID_MR equ 321 ID_MS equ 322 ID_MPLUS equ 323 ID_M equ 324 ID_BACK equ 325 ID_CE equ 326 ID_C equ 327 ID_RESULT equ 328 ID_COPY equ 1001 ID_PASTE equ 1002 ID_STANDARD equ 1003 ID_SCIENCE equ 1004 ID_PACKET equ 1006 ID_HELP equ 1007 ID_ABOUT equ 1008 ID_EXIT equ 1009 ID_NOTIFYICON equ 2000 WM_SHELLNOTIFY equ WM_USER+1 |
在Calculator.inc声明了自定义函数的原型
;---------------------------- 函数声明---------------------------- WinMain PROTO :DWORD, :DWORD, :DWORD, :DWORD ; 窗口主程序 Calculate PROTO :DWORD,:DWORD,:DWORD,:DWORD ; 消息处理程序 PackNum PROTO ; 数字分组子程序 UnpackNum PROTO ; 数字不分组子程序 BtnNum PROTO :DWORD ; 数字按键消息处理程序 ShowNum PROTO ; 显示数据子程序 ShowTextM PROTO ; 显示存储信息子程序 Init PROTO ; 初始化计算器子程序 GetResult PROTO ; 计算结果子程序 BtnOperator PROTO ; 双目运算符消息处理程序 BtnEqual PROTO ; 等于消息处理程序 |
数据段定义
;===================== Start 数据段定义Start ===================== .data ProgramName db "计算器",0 ;程序名 Author db "作者:桂杨",0 ;作者 HelpFile db "rc.hlp",0 ;帮助文档 hInstance db ? ;主程序句柄 hEdit db ? ;输出文本框句柄 hTextM db ? ;记忆标签句柄 hMenu db ? ;菜单句柄 hIcon db ? ;Icon句柄 DialogName db "Calculator",0 ;对话框名称 MenuName db "Menu",0 ;菜单名称 IconName db "Icon",0 ;Icon名称 TextM db 'M',0 ;M Output db "0.",0,30 dup(0) ;输出字符串 IsStart db 1 ;判断是否运算开始 HasPoint db 0 ;判断是否存在小数点 HasEqueal db 0 ;判断是否存在等号 Remember dq 0.0 ;记忆数据 Number dq 0.0 ;记录临时数据 Result dq 0.0 ;记录结果 Operand dq 0.0 ;记录操作数 IsPacket db 0 ;数字分组 Operator db '.' ;记录运算符 IsError db 0 ;记录是否出现异常 Div0 db "除数不能为零。",0 FunctionError db "函数输入无效。",0 hGlobal HANDLE ? ;剪切板内存块句柄 pGlobal db ? ;pointer to allocate memory NumLittle REAL8 1.0E-12 Num10 REAL8 10.0 ;实数10 Num100 REAL8 100.0 ;实数100 NotifyIcon NOTIFYICONDATA<> ;通知栏图标 ;======================= End 数据段定义End ======================= |
n PackNum
PackNum函数将输出数据的字符串Output进行数字分组。它首先获取小数点以前的数字位数并保存在寄存器eax中,然后将(eax-1)/3即为需要添加的字符‘,’数目,并保存在eax中,对于小数点以后的字符都向后移动eax位,对于小数点以前的字符,向后移动eax位并用ecx计数,当ecx计数到3是添加字符‘,’并将ecx设为1且eax减一,重复上述步骤直到eax等于0。
函数的流程图如下:
函数源代码如下:
PackNum proc USES eax ebx ecx edx lea esi,Output mov eax,0 .while (BYTE PTR[esi]!='.') inc eax inc esi .endw .while (BYTE PTR[esi]!=0) inc esi .endw dec eax mov edx,0 mov ecx,3 div ecx .while (BYTE PTR[esi]!='.') mov bx,[esi] mov [esi+eax],bx dec esi .endw mov bx,[esi] mov [esi+eax],bx dec esi mov ecx,0 .while (eax!=0) .if(ecx<3) mov bx,[esi] mov [esi+eax],bx inc ecx .else mov BYTE PTR[esi+eax],',' dec eax mov ecx,1 .endif dec esi .endw lea esi,Output .while (BYTE PTR[esi]!=0) mov bx,[esi] inc esi .endw ret PackNum endp |
n UnpackNum
UnpackNum函数将进行数字分组输出的字符串Output解分组。它首先获取Output地址存在esi中,然后ecx赋0,并将Output中字符向前移动ecx个单位,遇见‘,’字符则将ecx加1,直到字符串结束。
函数的流程图如下:
函数源代码如下:
UnpackNum proc USES ecx lea esi,Output mov ecx,0 .while (BYTE PTR[esi+ecx]!=0) .if(BYTE PTR[esi]==",") inc ecx .endif mov bx,[esi+ecx] mov [esi],bx inc esi .endw ret UnpackNum endp |
n ShowNum
ShowNum函数将Output字符串处理后在文本框中显示出来。它首先调用UnpackNum函数对Output解分组,然后获取Output地址存在esi、edi中,通过循环将Output尾地址存在esi中,将字符‘.’地址存在edi中,如果edi等于esi则表明Output中无字符‘.’,则在结尾添加字符‘.’。如果IsPacked等于1则对Output调用UnpackNum函数对其分组,最后向文本框发送WM_SETTEXT消息显示数据。
函数的流程图如下:
函数源代码如下:
ShowNum proc invoke UnpackNum lea esi,Output lea edi,Output .while (BYTE PTR[esi]!=0) inc esi .endw .while (BYTE PTR[edi]!='.') && (edi<esi) inc edi .endw .if esi==edi mov BYTE PTR[esi],'.' mov BYTE PTR[esi+1],0 .endif .if IsPacket==1 invoke PackNum .endif invoke SendMessage,hEdit,WM_SETTEXT,0,addr Output ret ShowNum endp |
n BtnNum
BtnNum函数响应数字按钮消息,向文本框中添加字符。
函数源代码如下:
BtnNum proc USES eax,Num:DWORD lea esi,Output mov eax,Num sub eax,252 .if IsStart==1 mov [esi],eax inc esi mov BYTE PTR[esi],'.' inc esi mov BYTE PTR[esi],0 mov IsStart,0 .else .while BYTE PTR[esi]!='.' inc esi .endw .if HasPoint==1 .while BYTE PTR[esi]!=0 inc esi .endw mov [esi],ax inc esi mov BYTE PTR[esi],0 .else .if BYTE PTR[Output]=='0' lea esi,Output mov [esi],eax mov BYTE PTR[esi+1],'.' mov BYTE PTR[esi+2],0 .else mov [esi],eax inc esi mov BYTE PTR[esi],'.' inc esi mov BYTE PTR[esi],0 .endif .endif .endif invoke ShowNum ret BtnNum endp |
n BtnOperator
BtnOperator函数响应运算符按钮消息,进行运算并输出结果。首先判断是否为等号,如果不是则调用GetResult函数先进行一次运算,然后将当前操作符存入Operator变量中。
函数源代码如下:
BtnOperator proc USES eax .if HasEqueal!=1 invoke GetResult .endif .if eax == ID_MUL mov Operator,'*' .elseif eax == ID_DIV mov Operator,'/' .elseif eax == ID_SUB mov Operator,'-' .elseif eax == ID_ADD mov Operator,'+' .endif mov HasEqueal,0 ret BtnOperator endp |
n BtnEqual
BtnEqual函数响应等号按钮消息,进行运算并输出结果。首先判断是否为起始状态,如果不是则调用GetResult函数,并将HasEqual变量置1。
函数源代码如下:
BtnEqual proc .if (IsStart==1) && (HasEqueal==0) fstp Number fst Number fld Number .endif invoke GetResult mov HasEqueal,1 ret BtnEqual endp |
n GetResult
BtnEqual函数响应等号按钮消息,进行运算并输出结果。首先判断是否为起始状态,如果不是则调用GetResult函数,并将HasEqual变量置1。
函数源代码如下:
GetResult proc USES eax invoke UnpackNum finit .if (IsStart==1) && (HasEqueal==0) .else .if HasEqueal!=1 invoke StrToFloat,addr Output, addr Operand .endif fld Result fld Operand .if Operator=='.' fst Result jmp Show .elseif Operator=='+' fadd ST(1),ST(0) .elseif Operator=='-' fsub ST(1),ST(0) .elseif Operator=='*' fmul ST(1),ST(0) .elseif Operator=='/' fldz fcomi ST(0),ST(1) jnz NotZero mov IsError,1 invoke SendMessage,hEdit,WM_SETTEXT,0,addr Div0 ret NotZero: fstp Operand fdiv ST(1),ST(0) .endif fstp Operand fst Result Show: mov IsStart,1 mov HasPoint,0 invoke FloatToStr2,Result,addr Output invoke ShowNum .endif ret GetResult endp |
n ShowTextM
ShowTextM函数判断Remember中的值是否为0,如果不是是则在标签中显示‘M’,否则清空标签中内容。
函数源代码如下:
ShowTextM proc fld NumLittle fldz fsub Remember fabs fcomi ST(0),ST(1) ja NotZero invoke SendMessage,hTextM,WM_SETTEXT,0,NULL jmp PopNumLittle NotZero:invoke SendMessage,hTextM,WM_SETTEXT,0,addr TextM PopNumLittle:fstp Operand fstp Operand mov IsStart,1 mov HasPoint,0 ret ShowTextM endp |
n Init
Init函数负责进行必要的初始化操作,如对状态变量的初始化以及的FPU的初始化。
函数源代码如下:
Init proc mov IsStart,1 ;初始化 mov HasPoint,0 ;清除小数点 mov HasEqueal,0 fldz fst Number ;清除结果 fst Operand mov Operator,'.' ;清除运算符 mov IsError,0 finit ;初始化FPU ret Init endp |
主程序用于获得并保存本程序的句柄,调用WinMain主程序创建窗口并获取和分发消息,然后结束程序。
主程序流程图及原代码如下:
|
|
Ø 主程序
WinMain主程序用于创建窗口并获取和分发消息。
主程序流程图如下:
程序源代码如下:
WinMain proc hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD LOCAL wc:WNDCLASSEX ;窗口类 LOCAL msg:MSG ;消息 LOCAL hWnd:HWND ;对话框句柄
mov wc.cbSize,sizeof WNDCLASSEX ;WNDCLASSEX的大小 mov wc.style,CS_BYTEALIGNWINDOW or CS_BYTEALIGNWINDOW ;窗口风格or CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc,OFFSET Calculate ;窗口消息处理函数地址 mov wc.cbClsExtra,0 ;在窗口类结构后的附加字节数,共享内存 mov wc.cbWndExtra,DLGWINDOWEXTRA ;在窗口实例后的附加字节数(!注意点) mov eax,hInst mov wc.hInstance,eax ;窗口所属程序句柄 mov wc.hbrBackground,COLOR_BTNFACE+1 ;背景画刷句柄 mov wc.lpszMenuName,NULL ;菜单名称指针 mov wc.lpszClassName,OFFSET DialogName ;类名称指针 invoke LoadIcon,hInst,addr IconName ;加载Icon mov wc.hIcon,eax ;图标句柄 invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax ;光标句柄 mov wc.hIconSm,0 ;窗口小图标句柄
invoke RegisterClassEx,addr wc ;注册窗口类 invoke CreateDialogParam,hInst,addr DialogName,0,addr Calculate,0 ;调用对话框窗口 mov hWnd,eax ;保存对话框句柄 invoke ShowWindow,hWnd,CmdShow ;最后一个参数可设置为SW_SHOWNORMAL invoke UpdateWindow,hWnd ;更新窗口 StartLoop: ;消息循环 invoke GetMessage,addr msg,0,0,0 ;获取消息 cmp eax,0 je ExitLoop invoke TranslateMessage,addr msg ;转换键盘消息 invoke DispatchMessage,addr msg ;分发消息 jmp StartLoop ExitLoop: ;结束消息循环 mov eax,msg.wParam ret WinMain endp |
消息处理程序用于处理用户消息。
消息处理程序流程图如下:
消息处理程序源代码如下:
Calculate proc hWin:DWORD,uMsg:UINT,aParam:DWORD,bParam:DWORD LOCAL pt:POINT .if uMsg == WM_INITDIALOG invoke GetDlgItem,hWin,ID_RESULT ;获取输出文本框句柄 mov hEdit,eax ;保存文本框句柄 invoke GetDlgItem,hWin,ID_M ;获取记忆标签句柄 mov hTextM,eax ;保存记忆标签句柄 invoke LoadIcon,hInstance,addr IconName ;载入Icon mov hIcon,eax ;保存Icon句柄 invoke SendMessage,hWin,WM_SETICON,ICON_SMALL ,eax invoke LoadMenu,hInstance,addr MenuName ;加载菜单 mov hMenu,eax ;保存菜单句柄 invoke SetMenu,hWin,eax invoke CheckMenuRadioItem, hMenu, ID_STANDARD, ID_SCIENCE,ID_STANDARD,MF_BYCOMMAND ;选中标准型 invoke SendMessage,hEdit,WM_SETTEXT,0,addr Output ;显示"0." .elseif uMsg == WM_SIZE .if aParam==SIZE_MINIMIZED ;最小化 mov NotifyIcon.cbSize,sizeof NOTIFYICONDATA push hWin pop NotifyIcon.hwnd mov NotifyIcon.uID,ID_NOTIFYICON mov NotifyIcon.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP mov NotifyIcon.uCallbackMessage,WM_SHELLNOTIFY mov eax,hIcon mov NotifyIcon.hIcon,eax invoke lstrcpy,addr NotifyIcon.szTip,addr ProgramName invoke ShowWindow,hWin,SW_HIDE ;隐藏窗口 invoke Shell_NotifyIcon,NIM_ADD,addr NotifyIcon .endif .elseif uMsg == WM_SHELLNOTIFY .if aParam==ID_NOTIFYICON .if (bParam==WM_LBUTTONDOWN) ;单击通知栏图标 invoke ShowWindow,hWin,SW_SHOW ;显示窗口 invoke Shell_NotifyIcon,NIM_DELETE,addr NotifyIcon ;删除通知栏图标 .elseif (bParam==WM_RBUTTONDOWN) ;右键通知栏图标 invoke GetCursorPos,addr pt invoke GetSubMenu,hMenu,3 invoke TrackPopupMenu,eax,TPM_LEFTALIGN,pt.x,pt.y,NULL,hWin,NULL .endif .endif .elseif uMsg == WM_CHAR ;热键操作 mov eax,aParam sub eax,'0' add eax,ID_NUM0 .if (eax>=ID_NUM0) && (eax<=ID_NUM9) ;数字按钮 invoke Calculate,hWin,WM_COMMAND,eax,0 .elseif (eax==0ffh) ;ID_COPY invoke Calculate,hWin,WM_COMMAND,ID_COPY,0 .elseif (eax==112h) ;ID_PASTE invoke Calculate,hWin,WM_COMMAND,ID_PASTE,0 .elseif (eax==104h) ;ID_BACK invoke Calculate,hWin,WM_COMMAND,ID_BACK,0 .elseif (eax==265) ;ID_EQU invoke Calculate,hWin,WM_COMMAND,ID_EQU,0 .elseif (eax==298) ;ID_POINT invoke Calculate,hWin,WM_COMMAND,ID_POINT,0 .elseif(eax==295) ;ID_ADD invoke Calculate,hWin,WM_COMMAND,ID_ADD,0 .elseif (eax==297) ;ID_SUB invoke Calculate,hWin,WM_COMMAND,ID_SUB,0 .elseif (eax==294) ;ID_MUL invoke Calculate,hWin,WM_COMMAND,ID_MUL,0 .elseif (eax==299) ;ID_DIV invoke Calculate,hWin,WM_COMMAND,ID_DIV,0 .endif .elseif uMsg == WM_COMMAND mov eax,aParam .if eax == ID_CE ;清零按钮CE lea esi,Output mov BYTE PTR[esi],'0' mov BYTE PTR[esi+1],'.' mov BYTE PTR[esi+2],0 .if IsError==1 invoke Init .endif invoke SendMessage,hEdit,WM_SETTEXT,0,addr Output .elseif eax == ID_C ;初始化按钮C invoke Calculate,hWin,WM_COMMAND,ID_CE,bParam invoke Init .elseif IsError==1 ret .elseif eax == ID_BACK ;退格按钮Backspace invoke UnpackNum .if IsStart==0 lea esi,Output .while BYTE PTR[esi]!=0 inc esi .endw .if BYTE PTR[esi-1]=='.' .if HasPoint==1 mov HasPoint,0 .else .if BYTE PTR[esi-3]=='-' lea esi,Output mov BYTE PTR[esi],'0' mov BYTE PTR[esi+1],'.' mov BYTE PTR[esi+2],0 .else mov BYTE PTR[esi-2],'.' mov BYTE PTR[esi-1],0 .endif .endif .else mov BYTE PTR[esi-1],0 .endif lea esi,Output .if BYTE PTR[esi]=='.' mov BYTE PTR[esi],'0' mov BYTE PTR[esi+1],'.' mov BYTE PTR[esi+2],0 .endif invoke ShowNum .endif .elseif (eax >= ID_NUM0) && (eax <= ID_NUM9) ;数字按钮 .if HasEqueal==1 invoke Init .endif invoke BtnNum,eax .elseif eax == ID_POINT ;小数点按钮 mov BYTE PTR HasPoint,1 mov BYTE PTR IsStart,0 .elseif eax == ID_NEG ;正负号按钮 invoke UnpackNum invoke StrToFloat,addr Output, addr Number finit fldz fld Number fsub fstp Number invoke FloatToStr2,Number,addr Output invoke ShowNum .elseif (eax >= ID_MUL) && (eax <= ID_ADD) ;双目运算符按钮 invoke BtnOperator .elseif eax == ID_EQU ;等于按钮 invoke BtnEqual .elseif eax == ID_PER ;百分号按钮 mov Operator,'*' invoke GetResult invoke UnpackNum invoke StrToFloat,addr Output, addr Number finit fld Number fld Num100 fdiv fstp Number invoke FloatToStr2,Number,addr Output invoke ShowNum .elseif eax == ID_DAO ;倒数按钮 invoke UnpackNum invoke StrToFloat,addr Output, addr Number finit fld Number fldz fcomi ST(0),ST(1) jnz NotZero mov IsError,1 invoke SendMessage,hEdit,WM_SETTEXT,0,addr Div0 ret NotZero: fstp Number fstp Number fld1 fld Number fdiv .if HasEqueal==1 fst Result .endif fstp Number invoke FloatToStr2,Number,addr Output invoke ShowNum .elseif eax == ID_SQRT ;开方按钮 invoke UnpackNum invoke StrToFloat,addr Output, addr Number finit fld Number fldz fcomi ST(0),ST(1) jb Positive mov IsError,1 invoke SendMessage,hEdit,WM_SETTEXT,0,addr FunctionError ret Positive: fstp Number fsqrt .if HasEqueal==1 fst Result .endif fstp Number invoke FloatToStr2,Number,addr Output invoke ShowNum .elseif eax == ID_MC ;MC按钮 fldz fstp Remember invoke SendMessage,hTextM,WM_SETTEXT,0,NULL .elseif eax == ID_MR ;MR按钮 invoke FloatToStr2,Remember,addr Output invoke ShowNum mov IsStart,0 .elseif eax == ID_MS ;MS按钮 invoke UnpackNum invoke StrToFloat,addr Output, addr Remember invoke ShowTextM .elseif eax == ID_MPLUS ;M+按钮 finit fld Remember invoke UnpackNum invoke StrToFloat,addr Output, addr Remember fld Remember fadd fstp Remember invoke ShowTextM .elseif eax == ID_COPY ;复制 invoke GlobalAlloc,GMEM_MOVEABLE,35 ;配置一个内存块 mov hGlobal ,eax invoke GlobalLock,hGlobal ;锁定内存块 mov pGlobal ,eax lea esi,Output mov edi,pGlobal mov ecx,35 rep movsb ;复制字符串 invoke GlobalUnlock,hGlobal ;解锁内存块 invoke OpenClipboard, NULL ;打开剪切板 invoke EmptyClipboard ;清空剪切板 invoke SetClipboardData,CF_TEXT,hGlobal ;把内存句柄交给剪贴簿 invoke CloseClipboard ;关闭剪切板 .elseif eax == ID_PASTE ;粘贴 invoke IsClipboardFormatAvailable,CF_TEXT ;确定剪贴簿是否含有CF_TEXT格式的数据 invoke OpenClipboard,NULL ;打开剪切板 invoke GetClipboardData,CF_TEXT ;得到代表文字的内存块代号 mov hGlobal,eax invoke GlobalLock ,hGlobal ;解锁内存块 mov pGlobal,eax mov ecx,35 lea edi,Output mov esi,eax rep movsb ;复制字符串 invoke GlobalUnlock ,hGlobal ;解锁内存块 invoke CloseClipboard ;关闭剪切板 invoke ShowNum .elseif eax == ID_PACKET ;数字分组 .if IsPacket==0 invoke CheckMenuItem,hMenu,ID_PACKET,MF_CHECKED ;选中数字分组 .else invoke CheckMenuItem,hMenu,ID_PACKET,MF_UNCHECKED ;选中数字分组 .endif xor IsPacket,1 invoke ShowNum .elseif eax == ID_HELP ;帮助 invoke WinHelp,hWin,addr HelpFile,HELP_CONTENTS,1 .elseif eax == ID_ABOUT ;关于 invoke ShellAbout,hWin,addr ProgramName,addr Author,hIcon .elseif eax == ID_EXIT ;关闭 invoke Calculate,hWin,WM_CLOSE,aParam,bParam .endif .elseif uMsg == WM_CLOSE invoke Shell_NotifyIcon,NIM_DELETE,addr NotifyIcon invoke EndDialog,hWin,NULL invoke PostQuitMessage,0 ;退出消息循环 .else invoke DefWindowProc,hWin,uMsg,aParam,bParam ret .endif invoke SetFocus,hWin xor eax,eax ;关于WM_KEYDOWN原因 ret Calculate endp |
使用对话框做为主程序窗口的启发来源于《Windows程序设计》(【美】Charles Petzold 北京大学出版社)中的范例《HEXCALC:窗口还是对话框?》HEXCALC程序可能是写程序偷懒的经典之作,这个程序完全不呼叫CreateWindow,也不处理WM_PAINT消息,不取得设备内容,也不处理鼠标消息。但是它只用了不到150行的原始码,就构成了一个具有完整键盘和鼠标接口以及10种运算的十六进制计算器。受到它的启发,以及为了利用资源文件定义系统界面的简洁与方便,于是本程序将对话框就作为主程序。
事实上对话框就是窗口。通常Windows使用它自己内部的窗口消息处理程序处理对话框窗口的消息,然后,Windows将这些消息传送给建立对话框的程序内的对话框程序。在本程序中,我们让Windows使用对话框模板建立一个窗口,但是自己写程序处理这个窗口的消息,方便而简洁。
为了能够利用系统的外观,根据《如何将 Windows XP 主题应用于 Office COM 加载项》(http://support.microsoft.com/kb/830033/zh-cn)一文,定义说明文件Calculator.exe.manifest,然后在资源文件中添加代码#define ISOLATION_AWARE_ENABLED 1 即可。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <noInherit/> <assemblyIdentity processorArchitecture="*" type="win32" name="Calculator" version="1.0.0.0"/> <description>Calculator</description> <description>作者:桂杨</description> <dependency optional="yes"> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.1.0" publicKeyToken="6595b64144ccf1df" language="*" processorArchitecture="*"/> </dependentAssembly> </dependency> </assembly> |
本程序中的最小化按钮与Windows计算器的最小化大不一样!单击本程序的最小化按钮就会将主程序最小化到系统托盘,当单击系统托盘的小图标时,窗口就会显示出来,右键单击系统托盘图标时则会显示菜单栏。如图:
这样的设计为用户节省了空间,并且在不需要的时候不影响其它应用程序的工作。它的启发与阅读《Iczelion的win32汇编教程》不无关系,其中的第二十三课 系统托盘中的快捷图标详细的介绍了相关的内容。
当你点击计算器中的“帮助”→“关于计算器”的时候你会看到下面的弹出窗口:
您可能以为自己在使用Windows计算器,哈哈,其实这完全是笔者玩的一个小把戏,这一切很简单,仅仅是调用了一个有关Shell的函数而已—— invoke ShellAbout,hWin,addr ProgramName,addr Author,hIcon 。小小的添加项为程序添加了几分乐趣,为此我还特点观看了一点有关shell的知识,在其中《Win32开发人员参考库第五卷:windows Shell》(David Iseminger ,机械工业出版社,2001)
此外“帮助”→“帮助主题”时也会弹出一个帮助的窗口,方便用户了解和使用计算器。这也仅仅是调用函数WinHelp弹出了windows自带的帮助文档。
由于本程序项目工程比较复杂,而且需要包含相应的帮助文档、图标文件以及相关的文件,以及创立并修改注册表的键值一保存相关信息,并且为了确保能够在不同的系统上运行提高兼容性,特意使用Visual Studio2008制作了安装文件。安装文件的界面友好,明确的提示用户需要进行的操作。
如果您仔细的话会发现该计算器还添加了MID音乐播放的功能,您可以选择一个MID音乐来播放,也可以暂停它或者继续播放,使您在工作之余能够稍稍放松。之所以要写这个是希望能够学习Windows通用对话框的调用以及打开文件并进行播放。(注:由于这段代码是闲暇之余添加上去的,所以上面的说明可能并未包含该部分的)
对于Win32的初学者,最大的问题莫过于假设Win32汇编程序设计的环境了,一个方便的汇编程序的编写和调试环境对开发人员来说非常重要。受《Intel汇编语言程序设计(第五版)》(【美】Kip R Irvine,电子工业出版社,2008)启示以及自己对Visual Studio的熟悉,笔者选择了Visual Studio2008 作为开发环境,它能够自动进行链接、汇编并生成应用程序,非常的方便。至于其他环境的架设(如MASM32等),本人将相关资料整理成了博客(http://blog.csdn.net/KingWolfOfSky/archive/2009/07/23/4375411.aspx)。
《Windows程序设计(第五版)》(【美】Charles Petzold 北京大学出版社,1999)确实是一本好书,它详细的讲述了Win32图形界面编程的方法。使用对话框应用程序的启发也来自于中的范例《HEXCALC:窗口还是对话框?》。这可惜这本书已经不再出版了。
设计过程中关于对FPU的操作,以及浮点数转化和表示。关于FPU一节,《Intel汇编语言程序设计(第五版)》(【美】Kip R Irvine,电子工业出版社,2008)已经做了很详细深入的介绍,关于浮点数的表示相关问题,本人查阅了IEEE相关的规定,并整理成了Blog——《计算机中浮点数的表示与IEEE 754》(http://blog.csdn.net/KingWolfOfSky/archive/2009/09/08/4533404.aspx)
在学习Win32编程的过程中更令人迷人的是windows操作系统对进程、内存的管理与调度,于是本人饶有兴趣的参看了《现代操作系统》(【荷】Andrew S. Tanenbaum 机械工业出版社,2009)以及《Windows核心编程(第五版)》(【美】Jeffery Richter 清华大学出版社,2008);虽然并不是十分清楚,但是对其中的工作原理有了一定的了解。
程序中设计的问题的确让人烦恼,例如无法改变PUSHBUTTON的字体颜色,除非自绘,然而对于美工不好的我来说这的确不是一个好的选择。曾经花费两天的时间试图改变PUSHBUTTON的字体颜色,显然以失败而告终,这告诉我们应当了解一些语言和架构能完成什么、不能做到什么,这样才算真正的了解它。
- 《80X86汇编语言程序设计》,王元珍、曹忠升、韩宗芬,华中科技大学出版社,2005
- 《Iczelion的Win32汇编教程》
- 《Intel汇编语言程序设计(第五版)》,【美】Kip R Irvine,电子工业出版社,2008
- 《汇编语言编程艺术》,Randall Hyde,清华大学出版社 ,2005
- 《IBM PC汇编语言程序设计(第五版)》,Peter Abel,人民邮电出版社,2002
- 《Win32开发人员参考库第五卷:Windows Shell》,David Iseminger,机械工业出版社,2001
- 《Microsoft MASM 参考手册》
- 《现代操作系统》,【荷】Andrew S. Tanenbaum 机械工业出版社,2009
- 《Windows核心编程(第五版)》,【美】Jeffery Richter 清华大学出版社,2008
- 《Windows程序设计(第五版)》,【美】Charles Petzold ,北京大学出版社,1999
- 《Intel® 64 and IA-32 Architectures Software Developer's Manuals》
- MSDN Library: www.microsoft.com/china/MSDN/library/