Fasm---Win32汇编学习5
Win32汇编学习5----定时器
定时器顾名思义,就像我们现实中的闹钟一样,我们设定一个时间段,然后设置在这个时间段发生什么事情。 就如我们的闹钟,我们设定一个时间段,那么到了这个时间段的话,则闹钟就会发出“嘟嘟嘟”的声音。 但是闹钟这个定时器,他不用我们设置相应的过程,就是到了时间段去发生什么事情。。 但是我们windows中定时器,需要我们自己去设置到了这个时间段去发生什么事情。。
在Dos操作系统中我们要用到定时功能一般由两种方法,一种是用一个空循环来延时;二是截获时钟中断,计算机的硬件时钟会以每55ms 一次的频率出发8号中断,而在默认的int 08h 中断处理程序中由依据调用int 1ch的代码,所以截获int 08h 或int 1ch都可以达到定时的要求。第一种方法的定时效果随计算机主频的不同可能会大不相同,相比之下,第二种方法常用。
在保护模式下,我们用户程序不可能去截获时钟中断,所以操作系统用提供定时器的方法来满足用户的类似需求。
定时器使用:
在应用程序需要使用定时器时,可以用SetTimer函数向Windows申请一个定时器,要求系统在指定的时间后,“通知”应用程序,如果申请成功的话,系统会以指定的时间周期调用SetTimer函数指定的回调函数,或者向指定的窗口过程发送WM_TIMER消息。与Dos操作系统固定以55ms(毫秒)间隔触发中断服务程序相比,SetTimer函数可以指定的消息间隔更加灵活,------以ms为单位,可以指定的时间周期为一个32位的整数。也就是说1-4294967295ms,这可以将近50天的范围。
但是具体的使用不要被这个函数的参数所迷惑,由于windows的定时器同样是基于时钟中断的,所以虽然单位是 ms,但精度还是55ms,如果指定一个小于55ms的周期不管是1ms 还是 54ms ,Windows最快的也只能是在每个时钟中断的时候触发这个定时器,也就是说实际上这个定时器以55ms为触发周期的,另外当指定一个时间间隔的时候,windows以与这个间隔最接近的55ms的整数倍时间来触发定时器,假定建立一个周期位1000ms的定时器,定时器的触发周期实际上不是1s而是989ms(55ms*18)。
使用定时器还有一个要点是定时器消息是一个低级别的消息,这表现两个方面:首先,Windows只有在消息队列没有其他消息的时候才会发送WM_TIMER消息。如果某个窗口过程忙于处理某个消息没有返回,使消息队列中由消息积累起来,那么WM_TIMER消息就会被丢弃,在消息队列再度空闲的时候,被丢弃的WM_TIMER消息不会在被补发,(用一句经典的话是“过去的就让它过去吧”);其次消息队列中不会有多条WM_TIMER消息,如果消息队列中已经有一条WM_TIMER消息,还没来得及处理,又到了定时的时刻,那么两条WM_TIMER消息就会被合并成一条。
由此可见应用程序不能靠定时器来保证某件事情必须在规定的时刻被处理,另外也不能对定时器消息计数来计算已经过去了多少时间。
那么我们来写个程序来给大家演示一下。
我们还用昨天的代码加以修改。
format PE GUI 4.0
include 'win32ax.inc'
macro memmov [dst, src]
{
common
push [src]
pop [dst]
}
ID_TIMER1 equ 100
;************************数据********************************
szClassName db 'first Windows',0
szWndName db '我的第一个程序',0
szCommand dd ?
hIcon rd 1
hInstanse rd 1
hCursor rd 1
hWnd rd 1
entry $
invoke GetModuleHandle,NULL
mov [hInstanse], eax
invoke GetCommandLine,NULL
mov [szCommand], eax
stdcall _WinMain,hInstanse, NULL, [szCommand], SW_SHOWDEFAULT
invoke ExitProcess,NULL
proc _WinMain hInstance:DWORD, hPrevInstance:DWORD, lpCmdLine:DWORD, nCmdShow:DWORD
local @wc : WNDCLASSEX
local @msg : MSG
invoke RtlZeroMemory,addr @wc,sizeof.WNDCLASSEX
invoke LoadIcon,NULL, IDI_WINLOGO
mov [hIcon], eax
invoke LoadCursor,NULL, IDC_ARROW
mov [hCursor], eax
mov [@wc.cbSize], sizeof.WNDCLASSEX
mov [@wc.style], CS_HREDRAW or CS_VREDRAW
mov [@wc.lpfnWndProc], _WndProc
mov [@wc.cbClsExtra], NULL
mov [@wc.cbWndExtra], NULL
memmov @wc.hInstance, hInstance
memmov @wc.hIcon, hIcon
memmov @wc.hCursor, hCursor
mov [@wc.hbrBackground], COLOR_WINDOW
mov [@wc.lpszMenuName], NULL
mov [@wc.lpszClassName], szClassName
;注册窗口类
invoke RegisterClassEx,addr @wc
;建立窗口
invoke CreateWindowEx, NULL, szClassName, szWndName,/
WS_OVERLAPPEDWINDOW,/
100, 100, 600, 400,/
NULL, NULL, [hInstanse], NULL
mov [hWnd], eax
invoke ShowWindow,[hWnd],SW_SHOWNORMAL
invoke UpdateWindow,[hWnd]
GetMsg:
invoke GetMessage,addr @msg, NULL, 0, 0
or eax, eax
jz EndMsg
invoke TranslateMessage,addr @msg
invoke DispatchMessage,addr @msg
jmp GetMsg
EndMsg:
mov eax, [@msg.wParam]
ret
endp
proc TimerProc hWnd:DWORD, wMsg:DWORD, idEvent:DWORD, dwTime:DWORD
pushad
invoke MessageBeep,-1
popad
ret
endp
proc _WndProc uses ebx esi edi,hWnd:DWORD, wMsg:DWORD, wParam:DWORD, lParam:DWORD
local @hdc:DWORD
local @ps : PAINTSTRUCT
local @rect: RECT
cmp [wMsg], WM_DESTROY
jz Quit
cmp [wMsg], WM_PAINT
jz PAIT
cmp [wMsg], WM_CREATE
jz SETTIMER
invoke DefWindowProc,[hWnd],[wMsg],[wParam],[lParam]
ret
SETTIMER:
invoke SetTimer,[hWnd], ID_TIMER1, 1000, TimerProc
jmp endWnd
PAIT:
invoke BeginPaint,[hWnd], addr @ps
mov [@hdc], eax ;保存设备环境的句柄
invoke GetClientRect,[hWnd], addr @rect
invoke DrawText,[@hdc], szWndName, -1, addr @rect,/
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,[hWnd], addr @ps
jmp endWnd
Quit:
invoke PostQuitMessage,NULL
invoke KillTimer,[hWnd], ID_TIMER1
jmp endWnd
endWnd:
xor eax,eax
ret
endp
;///输入表
section '.import' data import readable writeable
library kernel32, 'kernel32.dll',/
user32, 'user32.dll',/
gdi32, 'gdi32.dll'
include 'api/kernel32.inc'
include 'api/user32.inc'
include 'api/gdi32.inc'
我们可以看到上面我们的代码只有几句和之前的不同。
cmp [wMsg], WM_CREATE
jz SETTIMER
SETTIMER:
invoke SetTimer,[hWnd], ID_TIMER1, 1000, TimerProc
jmp endWnd
我们处理了WM_CREATE消息。那么WM_CREATE消息是在我们调用CreateWindowEx函数后触发。 我们这里通过在调用CreateWindowEx后,
来创建一个定时器。那么创建定时器的函数是SetTimer函数。
UINT SetTimer(
HWND hWnd, // handle of window for timer messages
UINT nIDEvent, // timer identifier
UINT uElapse, // time-out value
TIMERPROC lpTimerFunc // address of timer procedure
);
hWnd是定时器消息的窗口句柄。 nIDEvent是定时器的标志(因为我们靠它来分别多个定时器),uElapse参数是定时器的时间周期,以ms位单位,这个是必须只得另的。 lpTimerFunc是定时器过程。 如果定时器建立成功返回的是定时器的标示符。
撤销定时器的方法是通过KillTimer函数。
KillTimer(
HWND hWnd, // handle of window that installed timer
UINT uIDEvent // timer identifier
);
hWnd是我们创建定时器时候的参数, uIDEvent是定时器的标示符。
invoke SetTimer,[hWnd], ID_TIMER1, 1000, TimerProc
这个语句定了一个标示为ID_TIMER1、消息发往TimerProc子程序的定时器,时间周期为1秒。那么在我们创建定时器成功的话,那么每一秒,Windows就会调用我们这里指定的TimerProc函数。
proc TimerProc hWnd:DWORD, wMsg:DWORD, idEvent:DWORD, dwTime:DWORD
pushad
invoke MessageBeep,-1
popad
ret
endp
我们可以看到,TimerProc函数我们这里要遵循相应的Windows规定。它的实际参数我们要遵循。
我们可以看到这个过程,我们只是简单让它调用MessageBep函数。从这里我们就可以想到,在我们定时器创建成功的话,那么每秒会发生“嘟嘟”的声音,因为这里我们的定时器相应过程,我们只是让它简单的调用MessageBep函数来发出响声。那么此时大家可以将这段代码放到FASM编译器里看看是不是此时每秒会发生嘟嘟的声音。。
那么我们使用定时器还有一种方法就是通过相应WM_TIMER消息。那么此时就要将SetTimer最后一个参数设置为NULL。
看代码
format PE GUI 4.0
include 'win32ax.inc'
ID_TIMER1 equ 100
;************************数据********************************
szClassName db 'first Windows',0
szWndName db '我的第一个程序',0
szCommand dd ?
hIcon rd 1
hInstanse rd 1
hCursor rd 1
hWnd rd 1
macro memmov [dst, src]
{
common
push [src]
pop [dst]
}
entry $
invoke GetModuleHandle,NULL
mov [hInstanse], eax
invoke GetCommandLine,NULL
mov [szCommand], eax
stdcall _WinMain,hInstanse, NULL, [szCommand], SW_SHOWDEFAULT
invoke ExitProcess,NULL
proc _WinMain hInstance:DWORD, hPrevInstance:DWORD, lpCmdLine:DWORD, nCmdShow:DWORD
local @wc : WNDCLASSEX
local @msg : MSG
invoke RtlZeroMemory,addr @wc,sizeof.WNDCLASSEX
invoke LoadIcon,NULL, IDI_WINLOGO
mov [hIcon], eax
invoke LoadCursor,NULL, IDC_ARROW
mov [hCursor], eax
mov [@wc.cbSize], sizeof.WNDCLASSEX
mov [@wc.style], CS_HREDRAW or CS_VREDRAW
mov [@wc.lpfnWndProc], _WndProc
mov [@wc.cbClsExtra], NULL
mov [@wc.cbWndExtra], NULL
memmov @wc.hInstance, hInstance
memmov @wc.hIcon, hIcon
memmov @wc.hCursor, hCursor
mov [@wc.hbrBackground], COLOR_WINDOW
mov [@wc.lpszMenuName], NULL
mov [@wc.lpszClassName], szClassName
;注册窗口类
invoke RegisterClassEx,addr @wc
;建立窗口
invoke CreateWindowEx, NULL, szClassName, szWndName,/
WS_OVERLAPPEDWINDOW,/
100, 100, 600, 400,/
NULL, NULL, [hInstanse], NULL
mov [hWnd], eax
invoke ShowWindow,[hWnd],SW_SHOWNORMAL
invoke UpdateWindow,[hWnd]
GetMsg:
invoke GetMessage,addr @msg, NULL, 0, 0
or eax, eax
jz EndMsg
invoke TranslateMessage,addr @msg
invoke DispatchMessage,addr @msg
jmp GetMsg
EndMsg:
mov eax, [@msg.wParam]
ret
endp
proc _WndProc uses ebx esi edi,hWnd:DWORD, wMsg:DWORD, wParam:DWORD, lParam:DWORD
local @hdc:DWORD
local @ps : PAINTSTRUCT
local @rect: RECT
cmp [wMsg], WM_DESTROY
jz Quit
cmp [wMsg], WM_PAINT
jz PAIT
cmp [wMsg], WM_CREATE
jz SETTIMER
cmp [wMsg], WM_TIMER
jz TIMER
invoke DefWindowProc,[hWnd],[wMsg],[wParam],[lParam]
ret
TIMER:
invoke MessageBeep,-1
jmp endWnd
SETTIMER:
invoke SetTimer,[hWnd], ID_TIMER1, 1000, NULL
jmp endWnd
PAIT:
invoke BeginPaint,[hWnd], addr @ps
mov [@hdc], eax ;保存设备环境的句柄
invoke GetClientRect,[hWnd], addr @rect
invoke DrawText,[@hdc], szWndName, -1, addr @rect,/
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,[hWnd], addr @ps
jmp endWnd
Quit:
invoke PostQuitMessage,NULL
invoke KillTimer,[hWnd], ID_TIMER1
jmp endWnd
endWnd:
xor eax,eax
ret
endp
;///输入表
section '.import' data import readable writeable
library kernel32, 'kernel32.dll',/
user32, 'user32.dll',/
gdi32, 'gdi32.dll'
include 'api/kernel32.inc'
include 'api/user32.inc'
include 'api/gdi32.inc'
其实也很简单,就是响应WM_TIMER消息,因为我们如果不设定定时器相应过程,那么windows会在每次定时器周期到的话,会发送WM_TIMER给相应的窗口。 此时我们只要相应WM_TIMER消息就可以了。。