C++ 带有重复功能的日历闹钟类
CAlarmClock 是一个可以生成重复异步闹钟的 C++ 类
引言
我最近开始开发我应用程序中需要的一个部分,该部分需要一个由时钟驱动的调度器,以便在一天、一周或一个月的特定时间执行用户需要的各种任务。因此,我决定尽可能使这个时钟日历闹钟组件具有可移植性,因为我想在两个不同的应用程序中使用它。由于我尝试使这个新类至少能在两个不同的程序中使用,所以将其在线共享并没有增加多少额外工作。希望其他人也能从中受益。
这个C++类具有以下功能,以便允许用户
- 设置一个闹钟,它将在稍后异步地提醒应用程序。
- 使用任意时间间隔指定一个重复闹钟,每N天一次、每周的特定几天一次,或每月特定的一天一次。
- 保证闹钟精度在几毫秒之内,并且所有后续重复闹钟都具有相同的精度。
背景
为了测量时间,CAlarmClock
类使用FILETIME
结构。据称它在100纳秒内是精确的,但计算机硬件不允许测量如此小的单位。实际上精度远小于100纳秒,但无论精度如何,它只与实际进行时间测量的代码一样精确。我不是Windows API中各种最精确延迟函数方面的专家。我知道有两种测量时间的方法:对我来说非常流行的是SetTimer()
方法,以及Windows多媒体API函数timeSetEvent()
。据我所读(在文档中),timeSetEvent()
非常精确。我选择使用timeSetEvent
作为主计时循环的时间基准。
Using the Code
Visual Studio项目需要进行以下更改才能使用CAlarmClock
类
- 在属性 | 链接器 | 输入 | 附加依赖项中,为发布和调试版本在列表中添加名为"Winmm.lib"的库。
- 属性 | C/C++ | 代码生成 | 运行时库应设置为使程序使用多线程运行时。包含的示例应用程序分别使用/MD和/MDd进行发布和调试版本。
#include "AlarmClock.h"
.- 将以下文件添加到您的项目中:"AlarmClock.cpp"和"AlarmClock.h"。
CAlarmClock
类具有以下public
成员函数
SetAlarm()
将调用者的闹钟日期和时间存储到对象的内部闹钟变量中,并将闹钟内部设置为ARMED
状态并开始计时。当到达闹钟日期和时间时,将触发调用者的触发器。这些函数有九种重载。可以接受三种不同的输入类型的时间:FILETIME
(本地)、SYSTEMTIME
(本地)和整数。在各种重载中使用了三种信号类型:回调、窗口消息和事件。SetAlarmAndWait()
的作用类似于Windows的Sleep()
函数。它可能不适用于长时间延迟,但它可以使对象在某些情况下更易于使用。一旦闹钟开始,就没有办法中止它,因为线程被占用以等待闹钟触发。但是,如果您确实想这样使用,另一个线程可以调用Abort()
。我建议不要使用SetAlarmAndWait()
,除非是需要短时间延迟的最基本计时循环。此外,重复闹钟不能与此函数一起使用。Abort()
停止当前闹钟并将对象置于休眠状态。IsArmed()
允许调用者询问时钟是否已设置并处于武装状态,还是处于空闲状态并等待发出另一个SetAlarm()
。如果闹钟已设置但尚未到达闹钟时间,IsArmed
将返回TRUE
。在所有闹钟重复触发并耗尽后,IsArmed
最终将返回FALSE
。SetRepeat()
提供了安排一次或多次自动重复所需的信心。请参阅REPEAT_PARMS
结构(在AlarmClock.h中)以了解如何指定重复闹钟事件。为了防止CAlarmClock
对象混淆,设置REPEAT_PARMS
的方式有严格的要求。因此,SetRepeat()
会验证REPEAT_PARMS
参数的内容。注意:最好在调用SetAlarm()
之前调用SetRepeat()
,但这不是必需的。我没有测试过在SetAlarm()
之后调用SetRepeat()
是否有效,但只要您的闹钟时间不过期,它就应该有效。ResetRepeats()
在未来重复闹钟发生之前取消它们。下一个已经计划重复的闹钟仍将在到达闹钟时间时发生。Abort()
是取消尚未发生的计划闹钟的唯一方法。GetAlarmTime()
允许您从对象检索下一个闹钟时间。当对象处于ARMED
状态时(请参阅IsArmed
),结果FILETIME
的内容才有效。- 新函数:以下
SetRepeat_xxx
函数取代了SetRepeat
函数。使用这些新函数消除了了解REPEAT_PARMS
结构的需要。出于兼容性原因,仍然支持SetRepeat
和GetRepeat
,但建议使用(下文)新的SetRepeat_xxx
函数。 SetRepeat_Interval()
取代了SetRepeat()
,允许程序员设置基于间隔的重复闹钟:(天、小时、分钟、秒和毫秒)。SetRepeat_Monthly()
取代了SetRepeat()
,允许程序员设置基于月份某一天的重复闹钟。SetRepeat_Weekdays()
取代了SetRepeat()
,允许程序员设置基于一个或多个工作日的重复闹钟。注意:所有
SetRepeat_xxx
函数都有一个参数(long nCount)
,该参数表示要使用的重复次数。零表示永远重复。SetRepeatUntil()
GetRepeatUntil()
注意:
RepeatUntil
函数优先于重复计数和永远重复设置。换句话说,调用RepeatUntil
将导致忽略重复计数和永远重复设置。要替换计数,请再次使用新的计数或零(表示永远)调用SetRepeat_xxxx
。
此类的所有函数都不是虚拟的。如果需要进行子类化,只需在基类中将函数声明为virtual
即可。我将其全部保留为非虚拟的,因为我还没有理由自己从这个类派生一个类。
如果需要多个闹钟,请创建多个CAlarmClock
对象,并根据需要将它们全部保存在数组中。
关注点
所有时间单位都被视为本地时间,而不是UTC。如果需要UTC,则需要修改类以处理该格式,并且如果要了解本地时间,则需要将其转换为本地时间。这引出了一个问题:如果这个日历闹钟在不同于我的时区(PST -8)的地方工作不正常,请告诉我,以便我可以在我的开发机上设置为该时区并尝试修复它。或者,更好的做法是,进行修改并提供代码给我,以便我可以更新文章中的代码。
该类使用一个工作线程来处理时间测量。工作线程将使用调用者的选择(事件、消息或回调)在闹钟事件期间通知调用者。如果您选择使用回调函数来处理闹钟事件,您需要确保您的回调函数不会花费太多时间,以便工作线程能够恢复时间测量。您不应从回调函数中调用CAlarmClock
类的任何成员函数,因为这会导致死锁。
我的Visual Studio .NET 2003 Pro版本中存在一个bug,它会导致DateTimePicker
控件在每次我对资源编辑器中的对话框进行任何编辑时,都会自动切换回其默认的日期模式而不是时间模式。现在,控件将在运行时更新,通过修改OnInitDialog
中的控件样式位来强制其成为时间格式。
REPEAT_PARMS
结构中的永远重复标志优先于重复计数器。
测试应用程序在可执行文件所在的路径(或快捷方式的启动路径)中生成一个日志文件。日志文件用于帮助测试闹钟重复。实时测试日历闹钟单元需要很长时间,因此我别无选择,只能在测试中采取捷径,并对所有内容是否有效做出一些假设。如果您发现任何问题,请给我发电子邮件,或者如果您觉得问题足够严重需要告诉大家,您可以随意在本文章的论坛上发布此bug。
新功能
- 除了“永远重复”和“重复n次”之外,还有一个新的重复结束条件。它是“重复直到日期”。
REPEAT_PARMS
中的tRepeatUntil
字段表示您希望重复结束的日期/时间。该字段是一个名为UFT
的联合体,可以轻松地从FileTime
转换为ULONGLONG
。初始化此字段的一种方法是使用SYSTEMTIME
结构作为模板。填写空白,然后调用SystemTimeToFileTime
并将结果存储在REPEAT_PARMS::tRepeatUntil
中,或者您现在可以调用SetRepeatUntil
(新的,如下所述)并将其传递给SYSTEMTIME
的指针。 - 我添加了一些新函数,消除了手动填写
REPEAT_PARMS
的需要。它们是:SetRepeat_Interval
、SetRepeat_Monthy
、SetRepeat_Weekdays
和SetRepeatUntil
。现在,您只需调用其中一个即可设置重复参数,CAlarmClock
类将自行管理REPEAT_PARMS
的内部副本。如果需要,您仍然可以像以前一样使用SetRepeat
函数。 REPEAT_PARMS
被更改,因此消除了Repeat_Days
模式。要重复天数,现在您可以使用Repeat_Interval
模式并填写REPEAT_PARMS::dd
字段(或调用等效函数SetRepeat_Interval
)。
历史
- 05/07/12: 文章提交
- 06/03/17: 添加了功能并修复了一些bug