《汇编语言程序设计》——仿windows计算器

2023-03-15,,

汇编语言程序设计

——计算器程序设计

目录

一、     题目与目标

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主程序创建窗口并获取和分发消息,然后结束程序。

主程序流程图及原代码如下:

invoke GetModuleHandle,NULL

;获得并保存本程序的句柄

mov hInstance,eax

invoke WinMain,hInstance,0,0,SW_SHOWDEFAULT

invoke ExitProcess,eax

;退出程序,返回eax值

Ø   主程序

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/

《汇编语言程序设计》——仿windows计算器的相关教程结束。

《《汇编语言程序设计》——仿windows计算器.doc》

下载本文的Word格式文档,以方便收藏与打印。