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




