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

    

 

    

      


《汇编语言程序设计》

——计算器程序设计

目录

一、     题目与目标... 3

1.      题目... 3

2.      学习目的... 3

二、     分析与设计... 3

1.      系统分析... 3

2.      系统设计... 3

3.      功能分析... 5

4.      功能设计... 6

5.      界面设计... 6

6.      文件设计... 6

三、     程序系统说明书... 7

1.      创建计算器界面... 7

2.      引入头文件及库... 9

3.      定义常量... 9

4.      函数声明... 10

5.      程序说明... 11

Ø      工具子程序说明... 11

Ø      主程序... 19

Ø      WinMain主程序... 19

Ø      消息处理程序... 21

四、     设计与思考... 26

1.      为什么使用对话框?... 26

2.      如何应用系统的外观?... 26

3.      关于最小化... 27

4.      关于计算器... 27

5.      为什么要设计安装文件?... 28

6.      为什么要播放音乐?... 28

五、     课程设计的体会... 29

六、     参考资料... 30

七、     附录... 30

1.      系统模块总图... 31

2.      系统文件清... 31

 


 

一、   题目与目标

1.   题目

使用Win32编程设计一个功能及界面风格类似于Windows计算器的计算器程序,只要求实现标准型计算器。

主要实现的功能:

包含基本的四则运算、倒数运算、平方根运算。支持存储区的存储、清除、调出、累加等功能。

2.   学习目的

Ø  WIN32汇编程序编写。

Ø  用汇编实现简单的算法。

Ø  浮点数运算(浮点指令或者自己编程模拟)

Ø  综合解决问题的能力。

二、   分析与设计

1.   系统分析

本程序为Win32窗口应用程序,因此采用Windows开发包的文档中规定的Windows程序标准框架进行编程设计。

 

windows应用程序框架2.   系统设计

按照Windows程序标准框架,主程序用于获得并保存本程序的句柄,并调用窗口主程序WinMain创建窗口并进入消息循环。WinMain程序将获取的消息分发给消息处理程序Calculate进行处理。主程序及窗口主程序结构如下图:

 

计算器程序结构

消息处理程序Calculate用于相应窗口创立、销毁、按键等消息并进行处理,根据系统功能,消息处理程序Calculate结构图如下:

 

Calculator消息处理

3.   功能分析

如图所示,Windows自带的计算器按照功能划分可以分为以下5个区域:

 

计算器功能说明

显示区:文本框,用于显示输入的操作数及结果

数字键入区:在显示区中显示数字、小数点、正负号等;

运算区:包含双目运算符(+ - * /)、单目运算符(sqrt()、%、1/x)、等于号等

记忆区:清除记忆(MC)、显示记忆(MR)、记忆当前(MS)、记忆加(M+)以及记忆区存储情况的标签

清除键区:退格(Backspace)、清除当前数据(CE)、初始化操作(C

4.   功能设计

Ø  数字:添加文本框字符串添加数字字符,调用函数BtnNum完成该功能;

Ø  小数点:为当前输入数字添加小数点,将判断是否小数点的变量HasPoint赋值为1

Ø  正负号:将当前数字取相反数并在对话框显示,拟通过浮点运算求相反数并调用ShowNum函数显示数字

Ø  双目运算符:计算结果,调用函数BtnOperator实现运算功能

Ø  等号:计算结果,调用函数BtnEqual实现运算功能

Ø  单目运算符:立即对当前数字进行运算并输出结果

Ø  MS:将当前数据保存在变量Remember中,并在记忆区存储情况的标签中显示相应的信息

Ø  M+:将当前数据加到变量Remember上,并在记忆区存储情况的标签中显示相应的信息

Ø  MR:将变量Remember数据显示到文本框中;

Ø  MC:将变量Remember归零,并在记忆区存储情况的标签中显示相应的信息

Ø  C:初始化计算器,调用函数Init实现该功能,并在文本框显示0.

Ø  CE:将当前数字清零

Ø  Backspace:删除当前数据的末位数字

5.   界面设计

系统界面仿照Windows计算器程序界面设计,并使用资源文件进行定义,设计界面如下:

 

计算器界面

6.   文件设计

程序源文件包含两个部分:

Ø  头文件(Calculator.inc):头文件中引入程序所需要的库以及常量和函数申明

Ø  源文件(Calculator.asm):汇编程序源代码

Ø  资源文件(Calculator.rc):定义程序的窗口界面以及相关资源

Ø  说明文件(Calculator.exe.manifest):说明程序的相关配置及信息

三、   程序系统说明书

1.   创建计算器界面

利用资源文件定义系统界面,代码如下

#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_NUM0ID_NUM9

2.   引入头文件及库

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

3.   定义常量

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

4.   函数声明

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 =======================
     

5.   程序说明

Ø  工具子程序说明

n  PackNum

PackNum函数将输出数据的字符串Output进行数字分组。它首先获取小数点以前的数字位数并保存在寄存器eax中,然后将(eax-1)/3即为需要添加的字符‘,’数目,并保存在eax,对于小数点以后的字符都向后移动eax位,对于小数点以前的字符,向后移动eax位并用ecx计数,当ecx计数到3是添加字符‘,’并将ecx设为1eax减一,重复上述步骤直到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中,然后ecx0,并将Output中字符向前移动ecx个单位,遇见‘,’字符则将ecx1,直到字符串结束。

函数的流程图如下:

 

函数源代码如下:

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地址存在esiedi中,通过循环将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主程序用于创建窗口并获取和分发消息。

主程序流程图如下:

 

程序源代码如下:

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

四、   设计与思考

1.   为什么使用对话框?

使用对话框做为主程序窗口的启发来源于Windows程序设计》(【美】Charles Petzold 北京大学出版社)中的范例《HEXCALC:窗口还是对话框?》HEXCALC程序可能是写程序偷懒的经典之作,这个程序完全不呼叫CreateWindow,也不处理WM_PAINT消息,不取得设备内容,也不处理鼠标消息。但是它只用了不到150行的原始码,就构成了一个具有完整键盘和鼠标接口以及10种运算的十六进制计算器。受到它的启发,以及为了利用资源文件定义系统界面的简洁与方便,于是本程序将对话框就作为主程序。

事实上对话框就是窗口。通常Windows使用它自己内部的窗口消息处理程序处理对话框窗口的消息,然后,Windows将这些消息传送给建立对话框的程序内的对话框程序。在本程序中,我们让Windows使用对话框模板建立一个窗口,但是自己写程序处理这个窗口的消息,方便而简洁。

2.   如何应用系统的外观?

为了能够利用系统的外观,根据《如何将 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>

3.   关于最小化

本程序中的最小化按钮与Windows计算器的最小化大不一样!单击本程序的最小化按钮就会将主程序最小化到系统托盘,当单击系统托盘的小图标时,窗口就会显示出来,右键单击系统托盘图标时则会显示菜单栏。如图:

 

这样的设计为用户节省了空间,并且在不需要的时候不影响其它应用程序的工作。它的启发与阅读《Iczelionwin32汇编教程》不无关系,其中的第二十三课 系统托盘中的快捷图标详细的介绍了相关的内容。

4.   关于计算器

当你点击计算器中的“帮助”“关于计算器”的时候你会看到下面的弹出窗口:

 

您可能以为自己在使用Windows计算器,哈哈,其实这完全是笔者玩的一个小把戏,这一切很简单,仅仅是调用了一个有关Shell的函数而已—— invoke ShellAbout,hWin,addr ProgramName,addr Author,hIcon 小小的添加项为程序添加了几分乐趣,为此我还特点观看了一点有关shell的知识,在其中《Win32开发人员参考库第五卷:windows Shell》(David Iseminger ,机械工业出版社,2001

此外“帮助”→“帮助主题”时也会弹出一个帮助的窗口,方便用户了解和使用计算器。这也仅仅是调用函数WinHelp弹出了windows自带的帮助文档。

5.   为什么要设计安装文件?

由于本程序项目工程比较复杂,而且需要包含相应的帮助文档、图标文件以及相关的文件,以及创立并修改注册表的键值一保存相关信息,并且为了确保能够在不同的系统上运行提高兼容性,特意使用Visual Studio2008制作了安装文件。安装文件的界面友好,明确的提示用户需要进行的操作。

6.   为什么要播放音乐?

如果您仔细的话会发现该计算器还添加了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

Ø  IczelionWin32汇编教程》

Ø  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/

七、   附录

1.   系统模块总图

 

2.   系统文件清单

posted @ 2011-07-07 18:30  狼の禅  阅读(6979)  评论(17编辑  收藏  举报
我要啦免费统计