有的时候, 你可能需要在程序中每隔一段时间去做某事,这就需要用到定时器,在汇编中你可以调用windows的SetTimer函数设置一个定时器来达到这个目的。今天我们就写一个简单的时钟程序, 让程序显示显示当前的时间。
先看代码:
; FileName: Clock.asm
; Author: Thinker
; Link & Compile:
; ml /c /coff Clock.asm
; rc Clock.rc
; link /subsystem:windows Clock.obj Clock.res
.386
.model flat, stdcall
option casemap : none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
IDD_MAIN EQU 101
IDC_STATIC_TIME EQU 1000
.data?
hInstance dd ?
.const
szFormat db '%04d-%02d-%02d %02d:%02d:%02d', 0
.code
_DlgProc proc hWnd, uMsg, wParam, lParam
local @stTime : SYSTEMTIME
local @szBuf[128]: byte
mov eax, uMsg
.if eax == WM_TIMER
invoke GetLocalTime, addr @stTime
movzx eax, @stTime.wSecond
push eax
movzx eax, @stTime.wMinute
push eax
movzx eax, @stTime.wHour
push eax
movzx eax, @stTime.wDay
push eax
movzx eax, @stTime.wMonth
push eax
movzx eax, @stTime.wYear
push eax
push offset szFormat
lea eax, @szBuf
push eax
call wsprintf
add esp, 32
invoke SetDlgItemText, hWnd, IDC_STATIC_TIME, addr @szBuf
.elseif eax == WM_INITDIALOG
invoke SetTimer, hWnd, 1, 1000, NULL
.elseif eax == WM_CLOSE
invoke KillTimer, hWnd, 1
invoke EndDialog, hWnd, 0
.else
mov eax, FALSE
ret
.endif
mov eax, TRUE
ret
_DlgProc endp
start:
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke DialogBoxParam, hInstance, IDD_MAIN, NULL, offset _DlgProc, NULL
invoke ExitProcess, 0
end start
资源文件:
// Clock.rc
#include <resource.h>
#define IDD_MAIN 101
#define IDC_STATIC_TIME 1000
IDD_MAIN DIALOG DISCARDABLE 0, 0, 108, 29
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_CENTER
CAPTION "Clock"
FONT 10, "Verdana"
BEGIN
CTEXT "2009-07-19 16:36:30",IDC_STATIC_TIME,0,4,107,17,
SS_CENTERIMAGE
END
程序运行效果如下:
程序和以前的一样, 重点在_DlgProc这个对话框过程中, 对于start标签部分,在前面的文章中已经讲过多遍,这里就不再赘述。现在来重点看看这个窗口过程:
在一开始, 定义了2个局部变量:SYSTEMTMEM结构类型的@stTime和一个byte类型的数组, 这个数组可以容纳128个字符。接着我们重点处理了3个消息:WM_TIMER, WM_INITDIALOG和WM_CLOSE
WM_CLOSE :
这个消息的处理很简单, 用KillTimer销毁定时器, 同时用EndDialog结束对话框
KillTimer第一个参数是窗口句柄,指明要结束的是哪个对话框的定时器
第二个参数是定时器标识ID, 用于指明要结束的是这个对话框中的哪个定时器,
因为一个窗口中,可以有多个定时器, 每个定时器都要有唯一的一个整数标识
WM_INITDIALOG :
这个消息是对话框初始化的消息, 我们这里设置了一个定时器
定时器设置的函数为SetTimer, 函数原型是:
UINT_PTR SetTimer(
HWND hWnd, // handle to window
UINT_PTR nIDEvent, // timer identifier
UINT uElapse, // time-out value
TIMERPROC lpTimerFunc // timer procedure
);
hWnd 定时器窗口句柄
nIDEvent 定时器ID
uElapse 触发定时器的时间间隔
lpTimerFunc 定时器触发时的处理函数
该函数调用成功后, 每隔uElapse这个时间间隔(时间单位为毫秒)就会触发定时器
如果lpTimerFunc不为空, 则用lpTimerFunc指向的函数来处理事件, 否则就会发送一个WM_TIMER消息到
线程的消息队列,也就是说, 我们会在窗口的过程里面收到WM_TIMER消息
WM_TIMER :
因为我们把定时器的lpTimerFunc设置为NULL,时间设置为1000毫秒, 所以, 我们将每隔1秒钟收到一个WM_TIMER消息, 这里, 我们调用GetLocalTime获取系统当前的时间,这个时间是本地的时间,我们传进一个
SYSTEMTIME的结构指针进去, 该函数会自动用当前的时间填充这个结构的每个成员变量, 需要注意的是, 这个结构
的每个成员变量都是WORD类型的, 我们必须进行0扩展, 将16位的无符号数扩展到32位无符号数, 然后调用wsprintf这个唯一的遵循__cdecl调用约定 的windows api进行结构化的输出, 输出的字符串为szBuf,字符串的格式化形式是:
%04d-%02d-%02d %02d:%02d:%02d
%d表示是一个十进制整数, 前面加上数字表示该字符占用的位数, 如果前面再加0,表示不够的用0填充
这个格式化字符串表示, 年份占用4位, 月份、天数、时分秒都各占2位, 其它的字符原样输出, 最终的
输出结果可能是这样:
2009-07-19 21:41:30
最后用SetDlgItemText将字符串显示在一个静态文本框中。
这里有一点特别需要注意的是, 我们不能这样做:
invoke GetLocalTime, addr @stTime
invoke wsprintf, addr @szBuf, offset szFormat, @stTime.wYear, @stTime.wMonth, /
@stTime.wDay, @stTime.wHour, @stTime,wMinute, @stTime.wSecond
invoke SetDlgItemText, hWnd, IDC_STATIC_TIME, addr @szBuf
这样的代码从逻辑上看似乎没有问题, 一开始我就是这么写的, 但是我却忽视了在32位汇编中, 用invoke伪指令调用函数的时候, 默认的所有参数都是32位的, 都是DWORD类型的, 而我们这里却用@stTime.wYear这个word类型的, 可以想象最终的结果是不正确的, 事实上也证明了, 程序的运行结果是错误的
这里, 我们必须先把每个要调用的参数扩展成32位的, 然后用push指令从右往左依次压入栈中, 接着用call指令调用这个wsprintf函数, 因为这个函数是wsprintf调用约定 , 所以, 必须在后面用add esp, 32来平衡堆栈
这个32是这样来的, 因为调用wsprintf用了8个参数, 每个参数都是32位的, 即4个字节, 所以总共有4*8=32个字节
关于这个程序的汇编代码分析, 将在下一篇文章 中叙述。
<script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"></script>