Windows端监听注册表事件

  • 前言
  • 解决方案
    1. C# API 层面
    2. Win32 API 层面

前言

最近项目迎来了文档编辑需求,文档编辑完成上传Server,由于是跨模块交互,了解到SDK与Office插件之间的交互是通过注册表,简要来说就是编辑动作的发生场景是在我们的应用Application.process,一旦触发编辑操作,SDK会往注册表的某个位置动态创建一个Key,然后往创建的Key下写入文件的LocalPath,Office插件读取目标项中的路径加载文件,进行编辑。编辑完成删除目标项。

所以编辑完成的动作改由以目标注册表项被删除的动作表征。接下来的重心就是完成对注册表的监听【由于其他原因,不能采用直接监听Office进程或者与Office进程建立强连接来通信的方式】。

  • 简单说明
    1. Office的文档格式有三种:PPT,WORD,EXCEL,经研究发现Office插件加载不同文档的方式不同,部分是多文档结构,部分是单文档结构,给直接监听进程增加了难度。
    2. 建立强连接,耦合性会增加,也不是好的解决方案。

解决方案

如果能从C#层面入手,那会一定程度减低问题的复杂度

1. C# API solution

研究的第一步就是从网上扒拉看是否有类似的Demo,或者去官网查看相应的文档。一般来说,前面部分是前奏,后面的部分辅助之,相辅相成问题解决事半功倍。

#1.1 Demo Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Management;
using Microsoft.Win32;
using System.Collections.ObjectModel;

namespace ConsoleApp1
{
    public class RegistryWatcher : ManagementEventWatcher, IDisposable
    {
        static ReadOnlyCollection<RegistryKey> supportedHives = null;

        /// <summary> 
        /// Changes to the HKEY_CLASSES_ROOT and HKEY_CURRENT_USER hives are not supported 
        /// by RegistryEvent or classes derived from it, such as RegistryKeyChangeEvent.  
        /// </summary> 
        public static ReadOnlyCollection<RegistryKey> SupportedHives
        {
            get
            {
                if (supportedHives == null)
                {
                    RegistryKey[] hives = new RegistryKey[]
                    {
                        Registry.LocalMachine,
                        Registry.CurrentUser,
                        Registry.CurrentConfig
                    };
                    supportedHives = Array.AsReadOnly<RegistryKey>(hives);
                }
                return supportedHives;
            }
        }

        public RegistryKey Hive { get; private set; }
        public string KeyPath { get; private set; }
        public RegistryKey KeyToMonitor { get; private set; }

        public event EventHandler<RegistryKeyChangeEventArgs> RegistryKeyChangeEvent;

        /// <exception cref="System.Security.SecurityException"> 
        /// Thrown when current user does not have the permission to access the key  
        /// to monitor. 
        /// </exception>  
        /// <exception cref="System.ArgumentException"> 
        /// Thrown when the key to monitor does not exist. 
        /// </exception>  
        public RegistryWatcher(RegistryKey hive, string keyPath)
        {
            this.Hive = hive;
            this.KeyPath = keyPath;

            // If you set the platform of this project to x86 and run it on a 64bit  
            // machine, you will get the Registry Key under  
            // HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node when the key path is 
            // HKEY_LOCAL_MACHINE\SOFTWARE 
            this.KeyToMonitor = hive.OpenSubKey(keyPath);

            if (KeyToMonitor != null)
            {
                //           WqlEventQuery query = new WqlEventQuery(
                //"SELECT * FROM RegistryTreeChangeEvent WHERE " +
                //"Hive = 'HKEY_LOCAL_MACHINE'" +
                //@"AND RootPath = 'SOFTWARE\\Classes\\NextLabs.Handler.1\\Shellex' ");
                // Construct the query string. 
                string queryString = string.Format(@"SELECT * FROM RegistryTreeChangeEvent  
                   WHERE Hive = '{0}' AND RootPath = '{1}' ", this.Hive.Name, this.KeyPath);

                WqlEventQuery query = new WqlEventQuery
                {
                    QueryString = queryString,
                    EventClassName = "RegistryTreeChangeEvent",
                    WithinInterval = new TimeSpan(0, 0, 0, 1)
                };
                this.Query = query;

                this.EventArrived += new EventArrivedEventHandler(RegistryWatcher_EventArrived);
            }
            else
            {
                string message = string.Format(
                    @"The registry key {0}\{1} does not exist",
                    hive.Name,
                    keyPath);
                throw new ArgumentException(message);
            }
        }

        void RegistryWatcher_EventArrived(object sender, EventArrivedEventArgs e)
        {
            if (RegistryKeyChangeEvent != null)
            {
                // Get RegistryKeyChangeEventArgs from EventArrivedEventArgs.NewEvent.Properties. 
                RegistryKeyChangeEventArgs args = new RegistryKeyChangeEventArgs(e.NewEvent);

                // Raise the event handler.  
                RegistryKeyChangeEvent(sender, args);
            }
        }

        /// <summary> 
        /// Dispose the RegistryKey. 
        /// </summary> 
        public new void Dispose()
        {
            base.Dispose();
            if (this.KeyToMonitor != null)
            {
                this.KeyToMonitor.Dispose();
            }
        }
    }

    public class RegistryKeyChangeEventArgs : EventArgs
    {
        public string Hive { get; set; }
        public string KeyPath { get; set; }
        public uint[] SECURITY_DESCRIPTOR { get; set; }
        public DateTime TIME_CREATED { get; set; }

        public RegistryKeyChangeEventArgs(ManagementBaseObject arrivedEvent)
        {

            // Class RegistryKeyChangeEvent has following properties: Hive, KeyPath,  
            // SECURITY_DESCRIPTOR and TIME_CREATED. These properties could get from 
            // arrivedEvent.Properties. 
            this.Hive = arrivedEvent.Properties["Hive"].Value as string;
            this.KeyPath = arrivedEvent.Properties["RootPath"].Value as string;

            // The property TIME_CREATED is a unique value that indicates the time  
            // when an event is generated.  
            // This is a 64-bit FILETIME value that represents the number of  
            // 100-nanosecond intervals after January 1, 1601. The information is in 
            // the Coordinated Universal Time (UTC) format.  
            this.TIME_CREATED = new DateTime(
                (long)(ulong)arrivedEvent.Properties["TIME_CREATED"].Value,
                DateTimeKind.Utc).AddYears(1600);
        }
    }
}
#1.2 Demo Test
 class Program
    {
        static void Main(string[] args)
        {
            try
            {
                //HKEY_LOCAL_MACHINE\SOFTWARE\TestForRegistryEvent
                //Hive_LocalMachine
                RegistryKey hive1 = RegistryWatcher.SupportedHives[0];
                var keyPath1 = @"SOFTWARE\\TestForRegistryEvent_HKLM";

                //HKEY_CURRENT_USER\TestForRegistryEvent_HKCU
                //Hive_CurrentUser
                RegistryKey hive2 = RegistryWatcher.SupportedHives[1];
                var keyPath2 = @"TestForRegistryEvent_HKCU";
                
                RegistryWatcher watcher = new RegistryWatcher(hive1, keyPath1);
                watcher.RegistryKeyChangeEvent += OnRegistryKeyEventChanged;
                watcher.Start();

                // Do something while waiting for events. In your application,
                // this would just be continuing business as usual.
                System.Threading.Thread.Sleep(50000);
                watcher.Stop();
            }
            catch(ManagementException e)
            {
                Console.WriteLine("An error occured: "+e.Message);
                Console.ReadKey();
            }
        }

        static void OnRegistryKeyEventChanged(object sender, RegistryKeyChangeEventArgs e)
        {
            string newEventMessage = string.Format(@"{0} The key {1}\{2} changed",
               e.TIME_CREATED.ToLocalTime(), e.Hive, e.KeyPath);

            Console.WriteLine(newEventMessage);
            Console.ReadKey();
        }
    }
#1.3 Test result

我准备了两组测试数据:

  1. HKLM,针对测试目标HKEY_LOCAL_MACHINE\SOFTWARE\TestForRegistryEvent,在监测的Key下增加和删除value。
Registry_HKLM
监测结果_HKLM.png
  1. HKCU,针对测试目标HKEY_CURRENT_USER\TestForRegistryEvent_HKCU,在监测的Key下增加和删除value。


    Registry_HKCU
监听结果_HKCU
#1.4 测试结果分析

C#层面如果通过往System Registry Provider注册监听来接受Registry Event,只支持Hive type为HKEY_LOCAL_MACHINE。其他的几种Hive type:HKU,HKCU,HKCR,HCC并不支持。

Registry vent support 说明

2. Win32 API solution

为了寻求更通用的Solution,以便能监听任意的注册表项,所以考虑从win32层面来寻求support。通过查询文档发现API中有关注项:

Notifies the caller about changes to the attributes or contents of a specified registry key.

LSTATUS RegNotifyChangeKeyValue(
  HKEY   hKey,
  BOOL   bWatchSubtree,
  DWORD  dwNotifyFilter,
  HANDLE hEvent,
  BOOL   fAsynchronous
);
2.1 Demo Code
void SampleCppClass::RegisterForEntryPoint(int type, wchar_t *subKey) {
    DWORD dwFilter = REG_NOTIFY_CHANGE_NAME |
        REG_NOTIFY_CHANGE_ATTRIBUTES |
        REG_NOTIFY_CHANGE_LAST_SET |
        REG_NOTIFY_CHANGE_SECURITY;

    HANDLE hEvent;
    HKEY hMainKey;
    HKEY hKey;


    if (type == 0) {
        hMainKey = HKEY_CLASSES_ROOT;
    }
    else if (type == 1) {
        hMainKey = HKEY_CURRENT_USER;
    }
    else if (type == 2) {
        hMainKey = HKEY_LOCAL_MACHINE;
    }
    else if (type == 3) {
        hMainKey = HKEY_USERS;
    }
    else if (type == 4) {
        hMainKey = HKEY_CURRENT_CONFIG;
    }
    else {
        _tprintf(TEXT("Usage: notify [HKLM|HKU|HKCU|HKCR|HCC] [<subkey>]\n"));
        return;
    }

    LONG lErrorCode;
    // Open a key.
    lErrorCode = RegOpenKeyEx(hMainKey, subKey, 0, KEY_NOTIFY | KEY_QUERY_VALUE, &hKey);

    hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (hEvent == NULL)
    {
        _tprintf(TEXT("Error in CreateEvent (%d).\n"), GetLastError());
        return;
    }

    // Watch the registry key for a change of value.
    lErrorCode = RegNotifyChangeKeyValue(hKey,
        TRUE,
        dwFilter,
        hEvent,
        TRUE);
    if (lErrorCode != ERROR_SUCCESS)
    {
        if (lErrorCode == ERROR_INVALID_HANDLE) {
            _tprintf(TEXT("Error in RegNotifyChangeKeyValue The handle is invalid. (%d).\n"), lErrorCode);
            return;
        }
        _tprintf(TEXT("Error in RegNotifyChangeKeyValue (%d).\n"), lErrorCode);
        return;
    }

    // Wait for an event to occur.
    _tprintf(TEXT("Waiting for a change in the specified key...\n"));
    if (WaitForSingleObject(hEvent, INFINITE) == WAIT_FAILED)
    {
        _tprintf(TEXT("Error in WaitForSingleObject (%d).\n"), GetLastError());
        return;
    }
    else _tprintf(TEXT("\nChange has occurred.\n"));
    if (m_callback != NULL)
    {
        m_callback(m_pContext);
    }

    // Close the key.
    lErrorCode = RegCloseKey(hKey);
    if (lErrorCode != ERROR_SUCCESS)
    {
        _tprintf(TEXT("Error in RegCloseKey (%d).\n"), GetLastError());
        return;
    }

    // Close the handle.
    if (!CloseHandle(hEvent))
    {
        _tprintf(TEXT("Error in CloseHandle.\n"));
        return;
    }
}
#2.2 Demo Test

在前面的C# code中,HKCU下的监听是不支持的,所以这次针对性进行测试,针对测试目标为:HKEY_CURRENT_USER\TestForRegistryEvent_HKCU,在其中添加和删除value以对相应事件进行测试。

win32_Test_HKCU

参考链接
C#
Registering for system registry events
Monitor registry changes (CSMonitorRegistryChange)

W32
nf winreg regnotifychangekeyvalue

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容