Win32匯編--定時(shí)器
1、定時(shí)器簡(jiǎn)介
在應(yīng)用程序需要使用定時(shí)器時(shí),可以用SetTimer函數(shù)向Windows申請(qǐng)一個(gè)定時(shí)器,要求系統(tǒng)在指定的時(shí)間以后“通知”應(yīng)用程序,如果申請(qǐng)成功的話,系統(tǒng)會(huì)以指定的時(shí)間周期調(diào)用SetTimer函數(shù)指定的回調(diào)函數(shù),或者向指定的窗口過程發(fā)送WM_TIMER消息,和DOS操作系統(tǒng)固定以55ms的間隔觸發(fā)中斷服務(wù)程序相比,SetTimer函數(shù)可以指定的時(shí)間間隔更為靈活——以ms為單位,可以指定的時(shí)間周期為一個(gè)32位的整數(shù),也就是從1~4294 967 295ms,這可是一個(gè)將近50天的范圍!
但是在具體的使用中不要被這個(gè)參數(shù)所迷惑:由于Windows的定時(shí)器同樣是基于時(shí)鐘中斷的,所以雖然參數(shù)的單位是ms,但精度還是55ms,如果指定一個(gè)小于55ms的周期,不管是1ms還是54ms,Windows最快也只能在每個(gè)時(shí)鐘中斷的時(shí)候觸發(fā)這個(gè)定時(shí)器,也就是說,實(shí)際上這個(gè)定時(shí)器是以55ms為觸發(fā)周期的;另外,當(dāng)指定一個(gè)時(shí)間間隔的時(shí)候,Windows以和這個(gè)間隔最接近的55ms的整數(shù)位時(shí)間來觸發(fā)定時(shí)器,假定建立一個(gè)周期為1000ms的定時(shí)器,定時(shí)器的觸發(fā)周期實(shí)際上不是1s而是989ms(55ms*18)。
使用定時(shí)器時(shí)還有一個(gè)要點(diǎn)就是定時(shí)器消息是一個(gè)低級(jí)別的消息,這表現(xiàn)在兩個(gè)方面:首先就是Windows只有在消息隊(duì)列中沒有其他消息的情況下才會(huì)發(fā)送WM_TIMER消息,如果窗口過程忙于處理某個(gè)消息沒有返回,使消息隊(duì)列中有消息積累起來,那么WM_TIMER消息就會(huì)被丟棄,在消息隊(duì)列再度空閑的時(shí)候,被丟棄的WM_TIMER消息不會(huì)被補(bǔ)發(fā);其次,消息隊(duì)列中不會(huì)有多條WM_TIMER消息,如果消息隊(duì)列中已經(jīng)有一條WM_TIMER消息,還沒來得及處理,又到了定時(shí)的時(shí)刻,那么兩條WM_TIMER消息會(huì)被合并成一條。
所以,應(yīng)用程序不能依靠定時(shí)器來保證某件事情必須在規(guī)定的時(shí)刻被處理,另外,也不能依賴對(duì)定時(shí)器消息計(jì)數(shù)來確定已經(jīng)過去了多少時(shí)間。
2、定時(shí)器的使用
//Timer.rc
#include <resource.h>
#define DLG_MAIN 1
#define ICO_1 1
#define ICO_2 2
#define IDC_SETICON 100
#define IDC_COUNT 101
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_1 ICON "1.ico"
ICO_2 ICON "2.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DLG_MAIN DIALOG 50, 50, 113, 40
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "定時(shí)器例子"
FONT 9 "宋體"
{
ICON ICO_1, IDC_SETICON, 8, 9, 18, 21
LTEXT "計(jì)數(shù):", -1, 35, 16, 25, 10
LTEXT "", IDC_COUNT, 62, 16, 40, 10
}
對(duì)資源的定義讀者現(xiàn)在一定不會(huì)陌生了,這個(gè)文件中定義了兩個(gè)圖標(biāo)和一個(gè)對(duì)話框,對(duì)話框中定義了一個(gè)圖標(biāo)框和兩個(gè)文本框,其中的一個(gè)文本框中的文字為空,這是以后顯示每秒一次的計(jì)數(shù)值用的。
//Timer.asm
.386
.model flat, stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定義
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ID_TIMER1 equ 1
ID_TIMER2 equ 2
ICO_1 equ 1
ICO_2 equ 2
DLG_MAIN equ 1
IDC_SETICON equ 100
IDC_COUNT equ 101
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 數(shù)據(jù)段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hWinMain dd ?
dwCount dd ?
idTimer dd ?
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代碼段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 定時(shí)器過程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcTimer proc _hWnd, uMsg, _idEvent, _dwTime
pushad
invoke GetDlgItemInt, hWinMain, IDC_COUNT, NULL, FALSE
inc eax
invoke SetDlgItemInt, hWinMain, IDC_COUNT, eax, FALSE
popad
ret
_ProcTimer endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 窗口過程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcDlgMain proc uses ebx edi esi, hWnd, uMsg, wParam, lParam
mov eax, uMsg
;**********************************************************************************
.if eax == WM_TIMER
mov eax, wParam
.if eax == ID_TIMER1
inc dwCount
mov eax, dwCount
and eax, 1
inc eax
invoke LoadIcon, hInstance, eax
invoke SendDlgItemMessage, hWnd, IDC_SETICON, STM_SETIMAGE, IMAGE_ICON, eax
.elseif eax == ID_TIMER2
invoke MessageBeep, -1
.endif
;**********************************************************************************
.elseif eax == WM_INITDIALOG
push hWnd
pop hWinMain
invoke SetTimer, hWnd, ID_TIMER1, 250, NULL
invoke SetTimer, hWnd, ID_TIMER2, 2000, NULL
invoke SetTimer, NULL, NULL, 1000, addr _ProcTimer
mov idTimer, eax
;**********************************************************************************
.elseif eax == WM_CLOSE
invoke KillTimer, hWnd, ID_TIMER1
invoke KillTimer, hWnd, ID_TIMER2
invoke KillTimer, NULL, idTimer
invoke EndDialog, hWnd, NULL
;**********************************************************************************
.else
mov eax, FALSE
ret
.endif
mov eax, TRUE
ret
_ProcDlgMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke DialogBoxParam, hInstance, DLG_MAIN, NULL, offset _ProcDlgMain, NULL
invoke ExitProcess, NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
這個(gè)程序的基本結(jié)構(gòu)非常簡(jiǎn)單,就是一個(gè)標(biāo)準(zhǔn)的對(duì)話框程序而已,在WM_INITDIALOG中用SetTimer申請(qǐng)了3個(gè)定時(shí)器,并在WM_CLOSE消息中用KillTimer撤銷這3個(gè)定時(shí)器。
申請(qǐng)一個(gè)定時(shí)器使用SetTimer函數(shù),函數(shù)的使用方法如下:
invoke SetTimer, hWnd, nIDEvent, uElapse, lpTimerFunc
hWnd參數(shù)是WM_TIMER消息發(fā)往的窗口句柄;nIDEvent參數(shù)是一個(gè)用戶指定的任意整數(shù),用來標(biāo)識(shí)一個(gè)程序中的多個(gè)定時(shí)器;uElapse是時(shí)間周期,以ms為單位,這個(gè)參數(shù)是必須指定的;lpTimerFunc是定時(shí)器過程,在下面的內(nèi)容中有詳細(xì)介紹。如果定時(shí)器建立成功的話,函數(shù)的返回值是定時(shí)器的標(biāo)識(shí)符。
撤銷定時(shí)器的函數(shù)是KillTimer,該函數(shù)的使用方法是:
invoke KillTimer, hWnd, uIDEvent
參數(shù)hWnd和uIDEvent就是建立定時(shí)器時(shí)使用的數(shù)值。
使用SetTimer函數(shù)的方法有兩種,第一種方法是要求Windows將WM_TIMER消息發(fā)往指定的窗口過程,這時(shí)候lpTimerFunc必須為NULL,如例子中的:
invoke SetTimer, hWnd, ID_TIMER1, 250, NULL
invoke SetTimer, hWnd, ID_TIMER2, 2000, NULL
這兩個(gè)句子設(shè)置了兩個(gè)標(biāo)識(shí)分別為ID_TIMER1和ID_TIMER2的定時(shí)器,定時(shí)周期分別為250ms和2s。在窗口過程收到WM_TIMER消息的時(shí)候,wParam中是用SetTimer建立定時(shí)器時(shí)使用的標(biāo)識(shí)uIDEvent,所以程序可以建立一個(gè)分支,通過判斷wParam來處理不同的定時(shí)器引起的WM_TIMER的消息。在例子中,當(dāng)wParam是ID_TIMER1的時(shí)候更換圖標(biāo)框中的圖標(biāo),是ID_TIMER2的時(shí)候用MessageBeep函數(shù)來發(fā)出一聲“嘟”的聲音。如果要撤銷用這種方法建立的定時(shí)器,那么只需要用建立時(shí)的hWnd和uIDEvent參數(shù)簡(jiǎn)單地調(diào)用KillTimer就可以了。
還有一種使用定時(shí)器的方法,那就是要求Windows在時(shí)間到的時(shí)候調(diào)用指定的定時(shí)器過程,而不是某個(gè)窗口過程,那么只需要指定lpTimerFunc參數(shù),如例子中的:
invoke SetTimer, NULL, NULL, 1000, addr _ProcTimer
這句語句要求系統(tǒng)把定時(shí)器消息發(fā)送到_ProcTimer定時(shí)器過程中去,但是,這時(shí)候沒有參數(shù)用來指定定時(shí)器標(biāo)識(shí),到最后如何用KillTimer撤銷這個(gè)定時(shí)器呢?答案是SetTimer函數(shù)會(huì)返回一個(gè)標(biāo)識(shí),程序可以保存這個(gè)標(biāo)識(shí)并在KillTimer函數(shù)中使用。
當(dāng)然,這種用法中的定時(shí)器標(biāo)識(shí)也可以自己指定,但這時(shí)候一定要同時(shí)指定hWnd,雖然這個(gè)hWnd沒有實(shí)際的用途,如果hWnd為NULL,那么即使指定了定時(shí)器標(biāo)識(shí),這個(gè)標(biāo)識(shí)也會(huì)被忽略,如:
invoke SetTimer, hWnd, ID_TIMER3, 1000, addr _ProcTimer
這個(gè)語句定義了一個(gè)標(biāo)識(shí)為ID_TIMER3、消息發(fā)往_ProcTimer子程序的定時(shí)器。
定時(shí)器過程是如下定義的:
TimerProc proc hWnd, uMsg, idEvent, dwTime
Windows回調(diào)定時(shí)器過程的時(shí)候會(huì)有4個(gè)參數(shù),uMsg總是WM_TIMER,hWnd和idEvent是窗口句柄和定時(shí)器標(biāo)識(shí)。由于有idEvent參數(shù),所以我們同樣可以把多個(gè)定時(shí)器消息指向同一個(gè)定時(shí)器過程中,并且根據(jù)idEvent參數(shù)構(gòu)建一個(gè)分支來處理不同定時(shí)器引發(fā)的消息。
程序中還可能遇到一種情況:當(dāng)在SetTimer中指定的定時(shí)器標(biāo)識(shí)已經(jīng)存在會(huì)怎樣呢?答案是Windows會(huì)用新的參數(shù)代替老的定時(shí)器參數(shù),函數(shù)執(zhí)行以后,這個(gè)標(biāo)識(shí)的定時(shí)器消息將以新的時(shí)間周期發(fā)送。
注意:例子程序的窗口過程中把WM_TIMER的消息的處理代碼放在第一個(gè)分支上,這是對(duì)程序的簡(jiǎn)單優(yōu)化,把頻繁發(fā)生的消息放到前面可以使程序少執(zhí)行一系列的比較指令,像WM_CREATE和WM_DESTROY等僅發(fā)生一次的消息可以放到分支的最后面。
3、取Windows時(shí)間
“定時(shí)器”這個(gè)詞很容易讓人聯(lián)想到時(shí)鐘,但是在前面介紹過,定時(shí)器是不能用來構(gòu)造時(shí)鐘的,定時(shí)器用于時(shí)鐘程序中只能是用在定時(shí)刷新屏幕這個(gè)功能上,要得到系統(tǒng)的時(shí)間還是要靠別的方法。
在Win32編程中,和獲得系統(tǒng)時(shí)間相關(guān)的函數(shù)有3個(gè):
invoke GetLocalTime, lpSystemTime
invoke GetSystemTime, lpSystemTime
invoke GetTickCount
它們之間的區(qū)別是:
GetTickCout返回的是本次Windows啟動(dòng)以來的ms數(shù),得到的時(shí)間數(shù)值直接在eax中返回,由于這是一個(gè)32位的整數(shù),可以表示的范圍是1~ffffffffms,所以當(dāng)Windows連續(xù)運(yùn)行49.7天以后,計(jì)數(shù)器會(huì)清零并重新開始。
GetLocalTime返回當(dāng)前的時(shí)間,GetSystemTime返回當(dāng)前的格林威治標(biāo)準(zhǔn)時(shí)間,這兩個(gè)函數(shù)返回的時(shí)間數(shù)據(jù)包括年、月、日、時(shí)、分、秒、毫秒以及星期,數(shù)據(jù)比較多,所以無法放在eax中返回,應(yīng)用程序需要預(yù)先設(shè)置一個(gè)SYSTEMTIME結(jié)構(gòu)的緩沖區(qū),并將緩沖區(qū)地址lpSystemTime當(dāng)參數(shù)傳遞給函數(shù),函數(shù)會(huì)把時(shí)間數(shù)據(jù)返回到這個(gè)緩沖區(qū)中。
SYSTEMTIME結(jié)構(gòu)的定義如下:
SYSTEMTIME STRUCT
wYear WORD ? ;年
wMonth WORD ? ;月
wDayOfWeek WORD ? ;星期,0=星期日,1=星期一,……
wDay WORD ? ;日
wHour WORD ? ;時(shí)
wMinute WORD ? ;分
wSecond WORD ? ;秒
wMilliseconds WORD ? ;毫秒
SYSTEMTIME ENDS
需要注意的是,結(jié)構(gòu)中的字段全部是word類型的,而Win32程序中用的往往是dword型變量,所以在使用這些數(shù)據(jù)之前往往要先把它們轉(zhuǎn)換為dword類型,用movzx指令就可以很方便地完成這個(gè)工作,如movzx eax, stSystemTime.wYear將wYear字段擴(kuò)展到32位后放在eax中。
和獲得系統(tǒng)時(shí)間的函數(shù)相對(duì)應(yīng),可以用下面的兩個(gè)函數(shù)設(shè)置系統(tǒng)時(shí)間:
invoke SetLocalTime, lpSystemTime
invoke SetSystemTime, lpSystemTime
同樣,SetLocalTime中的參數(shù)代表本地時(shí)間,SetSysTime中的參數(shù)代表格林威治標(biāo)準(zhǔn)時(shí)間,在調(diào)用函數(shù)之前,要把需要設(shè)置的時(shí)間放到一個(gè)SYSTEMTIME結(jié)構(gòu)中并把結(jié)構(gòu)地址當(dāng)做參數(shù)傳遞給Windows。