⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 006.txt

📁 会变语言实现的一些程序
💻 TXT
字号:
 6.1 定时器简介/6.2 定时器的使用

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

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

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

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

读者可以在所附光盘的Chapter06\Timer目录中找到一个例子,运行Timer.exe以后出现的界面如图6.1所



这个例子程序中共定义了3个定时器,第1个以250 ms为周期更换对话框上的图标;第2个以1s为单位进行计数并把结果显示在对话框上;第3个以2s为单位驱动扬声器发出“嘟嘟”的响声。为了验证WM_TIMER消息的级别,读者可以在运行中按住标题栏的“关闭”按钮不放,就可以发现3个定时器全部停止了,然后将鼠标移出“关闭”按钮并释放,定时器会重新工作,但对话框上的计数结果在定时器停止的期间并没有补上去,也就是说,在这期间,WM_TIMER消息被全部丢弃了。

6.2  定时器的使用

下面以Timer程序为例说明定时器的使用方法,这个程序的资源脚本文件定义如下:

#include   

#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

;********************************************************************



6.2 定时器的使用(2)

  .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

参数hWnd和uIDEvent就是建立定时器时使用的数值。

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

  invoke  SetTimer,hWnd,ID_TIMER1,250,NULL   (例1)

  invoke  SetTimer,hWnd,ID_TIMER2,2000,NULL

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

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

invoke  SetTimer,NULL,NULL,1000,addr _ProcTimer (例2)

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

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

invoke  SetTimer,hWnd,ID_TIMER3,1000,addr _ProcTimer   (例3)

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

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

TimerProc proc   hwnd,uMsg,idEvent,dwTime

Windows回调定时器过程的时候会有4个参数,uMsg总是WM_TIMER,hwnd和idEvent是例3用法中指定的hWnd和定时器标识,如果是例2的用法,那么hwnd就是NULL,而idEvent就是SetTimer返回的由Windows定义的定时器标识。由于有idEvent参数,所以我们同样可以把多个定时器消息指向同一个定时器过程中,并且根据idEvent参数构建一个分支来处理不同定时器引发的消息。

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

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



 



6.3 取Windows时间


很多读者看到“定时器”这个词的时候往往就联想到时钟,笔者也曾是如此,但是经过6.2节的介绍后就可以发现,定时器是不能用来构造时钟的,定时器用于时钟程序中只能是用在定时刷新屏幕这个功能上,要得到系统的时间还是要靠别的方法。

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

  invoke  GetLocalTime,lpSystemTime

  invoke  GetSystemTime,lpSystemTime

  invoke  GetTickCount

它们之间的区别是:

GetTickcount返回的是本次Windows启动以来的ms数,得到的时间数值直接在eax中返回,由于这是一个32位的整数,可以表示的范围是1~ffffffffh ms,所以当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.wYear将wYear字段扩展到32位后放到eax中。

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

invoke  SetLocalTime,lpSystemTime

invoke  SetSystemTime,lpSystemTime

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

 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -