注册表监听避免消息丢失
Jacksing

对于注册表的变化监听,Windows 通过 RegNotifyChangeKeyValue 函数来实现。这个函数的原型如下所示:

1
2
3
4
5
6
7
8
9

LONG RegNotifyChangeKeyValue(
HKEY hKey,
BOOL bWatchSubtree,
DWORD dwNotifyFilter,
HANDLE hEvent,
BOOL fAsynchronous
);

在该函数中,由 hEventfAsynchronous 来控制同步或者异步。如果 fAsynchronousTRUE,那么函数就会立即返回,否则会一直等待注册表的变化。总的来说,这个函数比较简单,但是有一个特点,就是 只会通知一次。使用其来完成注册表监听这个事的操作流程一般都是:开始监听 -> 收到通知 -> 处理通知 -> 重新建立监听。

但是这里就存在一个让笔者感到非常困惑的地方,为什么要设计为只通知一次呢?如果在收到通知后,重新建立监听前,这段时间内发生了变化,理论上是没办法知道的。在笔者看来,这是一个比较大的问题。

仔细啃了下文档之后,发现微软其实是提供了解决方案的。在这种需要重复监听的场景下,可以重复使用 hKey 参数,无需每次监听都去创建新的。而后利用这个参数,在监听建立的时,如果已经发生了注册表的变更,不论是同步还是异步,都会立即返回结果。这样就可以避免消息丢失的问题。

这种设计某种程度而言,就是做了一个消息的压缩处理。当注册表被频繁更改的时候,使用这个接口能够确保获取到最终结果,但是中途的变更可能会丢失。这个问题在实际使用中需要注意。

此外,还有一个地方需要额外注意。一旦 hKey 所对应的项被删了,那么还想重新建立监听的话,就需要重新创建 hKey。对于注册表项来说,其生命周期在被删除的那一刻就彻底终结了,所有指向这个项的 handle 都会全部失效。此时即便重新建立一个同名的项,那也是别的项了。必须等到重新创建一个新的项,才能重新建立监听。而监听新建项这个时,只能通过其父节点来完成。不存在的项都无法使用 RegOpenKeyEx 来打开并监听。

实现方案

开发一个注册表监听的服务,通常的做法是监听到变化之后,再触发回到函数通知给调用者。但是,一旦这个处理逻辑耗时比较长,在一些频繁变更的注册表项上,就会出现消息被压缩的问题。为了尽可能减少这类现象,笔者的实现方案是拆分监听和处理两个事到不同的线程中去。监听线程只负责监听,而处理线程则负责处理。这样可以避免监听线程因为处理消息而阻塞,导致消息丢失的问题。

但是这个方案还是存在着一个比较麻烦的问题。两个线程之间的操作必然涉及到同步问题,一旦处理线程占用着锁并且操作耗时太久,一样会导致原先的问题。所以在这个地方,我们必须摈弃锁的使用。

为了实现这个需求,笔者在项目里借用了 Windows 的窗口消息队列。监听线程通过 PostMessage 函数将消息和变化的项发送到处理线程的消息队列中,而处理线程则通过常规的操作方式取出这一消息,再针对性地进行处理即可。这样就避免了锁的使用,也避免了消息丢失的问题。这样的方式下,能最大限度避免消息压缩,确保每个变更事件都能通知到调用方。

参考链接:

RegNotifyChangeKeyValue function

Why does Reg­Notify­Change­Key­Value stop notifying once the key is deleted

  • 本文标题:注册表监听避免消息丢失
  • 本文作者:Jacksing
  • 创建时间:2024-04-08 13:08:27
  • 本文链接:https://wzzzx.github.io/Windows/handle_registry_monitor_message_lose/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论