《汇编语言程序设计》——仿windows计算器
|
《汇编语言程序设计》
——计算器程序设计
目录
使用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.****.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.****.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/