- 前言
-
解决方案
- C# API 层面
- Win32 API 层面
前言
最近项目迎来了文档编辑需求,文档编辑完成上传Server,由于是跨模块交互,了解到SDK与Office插件之间的交互是通过注册表,简要来说就是编辑动作的发生场景是在我们的应用Application.process,一旦触发编辑操作,SDK会往注册表的某个位置动态创建一个Key,然后往创建的Key下写入文件的LocalPath,Offlice插件读取目标项中的路径加载文件,进行编辑。编辑完成删除目标项。
所以编辑完成的动作改由以目标注册表项被删除的动作表征。任务改由监听注册表【由于其他原因,不能采用直接监听Office进程或者与Office进程建立强连接来通信的方式】。
-
简单说明
- Office的文档格式有三种:PPT,WORD,EXCEL,经研究发现Office插件加载不同文档的方式不同,部分是多文档结构,部分是单文档结构,给直接监听进程增加了难度。
- 建立强连接,耦合性会增加,也不是好的解决方案。
解决方案
如果能从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
我准备了两组测试数据:
- HKLM,针对测试目标HKEY_LOCAL_MACHINE\SOFTWARE\TestForRegistryEvent,在监测的Key下增加和删除value。
-
HKCU,针对测试目标HKEY_CURRENT_USER\TestForRegistryEvent_HKCU,在监测的Key下增加和删除value。
Registry_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)
网友评论