luqingfei@C++

为中华之崛起而崛起!
兼听则明,偏听则暗。

Win32汇编--定时器

 

Win32汇编--定时器

 

1、定时器简介

在应用程序需要使用定时器时,可以用SetTimer函数向Windows申请一个定时器,要求系统在指定的时间以后“通知”应用程序,如果申请成功的话,系统会以指定的时间周期调用SetTimer函数指定的回调函数,或者向指定的窗口过程发送WM_TIMER消息,和DOS操作系统固定以55ms的间隔触发中断服务程序相比,SetTimer函数可以指定的时间间隔更为灵活——以ms为单位,可以指定的时间周期为一个32位的整数,也就是从1~4294 967 295ms,这可是一个将近50天的范围!

 

但是在具体的使用中不要被这个参数所迷惑:由于Windows的定时器同样是基于时钟中断的,所以虽然参数的单位是ms,但精度还是55ms,如果指定一个小于55ms的周期,不管是1ms还是54msWindows最快也只能在每个时钟中断的时候触发这个定时器,也就是说,实际上这个定时器是以55ms为触发周期的;另外,当指定一个时间间隔的时候,Windows以和这个间隔最接近的55ms的整数位时间来触发定时器,假定建立一个周期为1000ms的定时器,定时器的触发周期实际上不是1s而是989ms55ms*18)。

 

使用定时器时还有一个要点就是定时器消息是一个低级别的消息,这表现在两个方面:首先就是Windows只有在消息队列中没有其他消息的情况下才会发送WM_TIMER消息,如果窗口过程忙于处理某个消息没有返回,使消息队列中有消息积累起来,那么WM_TIMER消息就会被丢弃,在消息队列再度空闲的时候,被丢弃的WM_TIMER消息不会被补发;其次,消息队列中不会有多条WM_TIMER消息,如果消息队列中已经有一条WM_TIMER消息,还没来得及处理,又到了定时的时刻,那么两条WM_TIMER消息会被合并成一条。

 

所以,应用程序不能依靠定时器来保证某件事情必须在规定的时刻被处理,另外,也不能依赖对定时器消息计数来确定已经过去了多少时间。

 

 

2、定时器的使用

//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 "定时器例子"

FONT 9 "宋体"

{

    ICON ICO_1, IDC_SETICON, 8, 9, 18, 21

    LTEXT "计数:", -1, 35, 16, 25, 10

    LTEXT "", IDC_COUNT, 62, 16, 40, 10

}

 

对资源的定义读者现在一定不会陌生了,这个文件中定义了两个图标和一个对话框,对话框中定义了一个图标框和两个文本框,其中的一个文本框中的文字为空,这是以后显示每秒一次的计数值用的。

 

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

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 数据段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

                .data?

hInstance       dd      ?

hWinMain        dd      ?

dwCount         dd      ?

idTimer         dd      ?

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 代码段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

                .code

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 定时器过程

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_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

 

这个程序的基本结构非常简单,就是一个标准的对话框程序而已,在WM_INITDIALOG中用SetTimer申请了3个定时器,并在WM_CLOSE消息中用KillTimer撤销这3个定时器。

 

申请一个定时器使用SetTimer函数,函数的使用方法如下:

invoke SetTimer, hWnd, nIDEvent, uElapse, lpTimerFunc

hWnd参数是WM_TIMER消息发往的窗口句柄;nIDEvent参数是一个用户指定的任意整数,用来标识一个程序中的多个定时器;uElapse是时间周期,以ms为单位,这个参数是必须指定的;lpTimerFunc是定时器过程,在下面的内容中有详细介绍。如果定时器建立成功的话,函数的返回值是定时器的标识符。

 

撤销定时器的函数是KillTimer,该函数的使用方法是:

invoke KillTimer, hWnd, uIDEvent

参数hWnduIDEvent就是建立定时器时使用的数值。

使用SetTimer函数的方法有两种,第一种方法是要求WindowsWM_TIMER消息发往指定的窗口过程,这时候lpTimerFunc必须为NULL,如例子中的:

invoke SetTimer, hWnd, ID_TIMER1, 250, NULL

invoke SetTimer, hWnd, ID_TIMER2, 2000, NULL

这两个句子设置了两个标识分别为ID_TIMER1ID_TIMER2的定时器,定时周期分别为250ms2s。在窗口过程收到WM_TIMER消息的时候,wParam中是用SetTimer建立定时器时使用的标识uIDEvent,所以程序可以建立一个分支,通过判断wParam来处理不同的定时器引起的WM_TIMER的消息。在例子中,当wParamID_TIMER1的时候更换图标框中的图标,是ID_TIMER2的时候用MessageBeep函数来发出一声“嘟”的声音。如果要撤销用这种方法建立的定时器,那么只需要用建立时的hWnduIDEvent参数简单地调用KillTimer就可以了。

 

还有一种使用定时器的方法,那就是要求Windows在时间到的时候调用指定的定时器过程,而不是某个窗口过程,那么只需要指定lpTimerFunc参数,如例子中的:

invoke SetTimer, NULL, NULL, 1000, addr _ProcTimer

这句语句要求系统把定时器消息发送到_ProcTimer定时器过程中去,但是,这时候没有参数用来指定定时器标识,到最后如何用KillTimer撤销这个定时器呢?答案是SetTimer函数会返回一个标识,程序可以保存这个标识并在KillTimer函数中使用。

 

当然,这种用法中的定时器标识也可以自己指定,但这时候一定要同时指定hWnd,虽然这个hWnd没有实际的用途,如果hWndNULL,那么即使指定了定时器标识,这个标识也会被忽略,如:

invoke SetTimer, hWnd, ID_TIMER3, 1000, addr _ProcTimer

这个语句定义了一个标识为ID_TIMER3、消息发往_ProcTimer子程序的定时器。

 

定时器过程是如下定义的:

TimerProc      proc        hWnd, uMsg, idEvent, dwTime

Windows回调定时器过程的时候会有4个参数,uMsg总是WM_TIMERhWndidEvent是窗口句柄和定时器标识。由于有idEvent参数,所以我们同样可以把多个定时器消息指向同一个定时器过程中,并且根据idEvent参数构建一个分支来处理不同定时器引发的消息。

 

程序中还可能遇到一种情况:当在SetTimer中指定的定时器标识已经存在会怎样呢?答案是Windows会用新的参数代替老的定时器参数,函数执行以后,这个标识的定时器消息将以新的时间周期发送。

 

注意:例子程序的窗口过程中把WM_TIMER的消息的处理代码放在第一个分支上,这是对程序的简单优化,把频繁发生的消息放到前面可以使程序少执行一系列的比较指令,像WM_CREATEWM_DESTROY等仅发生一次的消息可以放到分支的最后面。

 

 

3、取Windows时间

“定时器”这个词很容易让人联想到时钟,但是在前面介绍过,定时器是不能用来构造时钟的,定时器用于时钟程序中只能是用在定时刷新屏幕这个功能上,要得到系统的时间还是要靠别的方法。

 

Win32编程中,和获得系统时间相关的函数有3个:

invoke GetLocalTime, lpSystemTime

invoke GetSystemTime, lpSystemTime

invoke GetTickCount

 

它们之间的区别是:

GetTickCout返回的是本次Windows启动以来的ms数,得到的时间数值直接在eax中返回,由于这是一个32位的整数,可以表示的范围是1~ffffffffms,所以当Windows连续运行49.7天以后,计数器会清零并重新开始。

 

GetLocalTime返回当前的时间,GetSystemTime返回当前的格林威治标准时间,这两个函数返回的时间数据包括年、月、日、时、分、秒、毫秒以及星期,数据比较多,所以无法放在eax中返回,应用程序需要预先设置一个SYSTEMTIME结构的缓冲区,并将缓冲区地址lpSystemTime当参数传递给函数,函数会把时间数据返回到这个缓冲区中。

 

SYSTEMTIME结构的定义如下:

SYSTEMTIME STRUCT

       wYear                   WORD           ?      ;

       wMonth                WORD           ?      ;

       wDayOfWeek        WORD           ?      ;星期,0=星期日,1=星期一,……

       wDay                    WORD           ?      ;

       wHour                  WORD           ?      ;

       wMinute                WORD           ?      ;

       wSecond               WORD           ?      ;

       wMilliseconds        WORD           ?      ;毫秒

SYSTEMTIME ENDS

需要注意的是,结构中的字段全部是word类型的,而Win32程序中用的往往是dword型变量,所以在使用这些数据之前往往要先把它们转换为dword类型,用movzx指令就可以很方便地完成这个工作,如movzx eax, stSystemTime.wYearwYear字段扩展到32位后放在eax中。

 

和获得系统时间的函数相对应,可以用下面的两个函数设置系统时间:

invoke SetLocalTime, lpSystemTime

invoke SetSystemTime, lpSystemTime

同样,SetLocalTime中的参数代表本地时间,SetSysTime中的参数代表格林威治标准时间,在调用函数之前,要把需要设置的时间放到一个SYSTEMTIME结构中并把结构地址当做参数传递给Windows

 

posted on 2010-09-23 11:40 luqingfei 阅读(1936) 评论(0)  编辑 收藏 引用 所属分类: Win32汇编程语言序设计


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

<2011年11月>
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

统计

留言簿(6)

随笔分类(109)

随笔档案(105)

Blogers

Game

Life

NodeJs

Python

Useful Webs

大牛

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜