使用事务处理Windows注册表操作

在进行Windows注册表的批量修改时,通常希望所有的修改要么全部成功,要么全部失败。不希望因为修改不完整而导致应用程序无法正常工作,或者无法成功卸载。在之前的文章中,解释了注册表和文件访问的事务处理。本文将提供一个实际用例,展示这种技术如何发挥作用。

最近,在编写一个COM服务器的代码,用于从注册类列表中注销自己。由于一个打字错误,事情出了差错。这并不是一件小事。过程包括两个部分。第一部分应该删除一个注册表子树,第二部分是一个依赖于第一部分的单独注册表操作。不幸的是,由于打字错误,删除了HKEY_CURRENT_USER,这居然成功了。第二个更改由于打字错误而失败。如果使用了事务,那么除了花费5分钟寻找打字错误之外,什么也不会发生。现在,破坏了用户配置文件,这花费了相当多的时间来恢复。

因此,认为这是一个提供健壮的Win32辅助函数来绑定几个操作,并同时展示事务处理能力的好地方。

使用代码

代码的用例是想要删除子键下的所有注册表键和值,然后是子键本身。为此,创建了辅助函数w32_RegDeleteTreeTransacted。

LSTATUS w32_RegDeleteTreeTransacted( HKEY hKeyRoot, LPCTSTR subKey, bool deleteSubKey, HANDLE transaction = INVALID_HANDLE_VALUE);

参数列表相当明显。想要删除注册表键(通常是命名键之一,如HKEY_CURRENT_USER)子键下的所有内容。前两个参数涵盖了这一点。deleteSubKey参数指定是否也需要删除子键本身。事务参数是可选的。有几种用例,操作是更大一组更改的一部分,可能由一个总体事务覆盖。使用事务参数,这个更改可以集成到那个总体事务中。

输入验证

//The root cannot be NULL if (hKeyRoot == NULL) return ERROR_INVALID_PARAMETER; //Do not accidentally delete a root if ((hKeyRoot == HKEY_CLASSES_ROOT || hKeyRoot == HKEY_CURRENT_CONFIG || hKeyRoot == HKEY_CURRENT_USER || hKeyRoot == HKEY_LOCAL_MACHINE || hKeyRoot == HKEY_USERS) && subKey == NULL) return ERROR_INVALID_PARAMETER; //If the subkey itself needs to be deleted, it cannot be null if (deleteSubKey && subKey == NULL) return ERROR_INVALID_PARAMETER;

显然,子键的根不能是NULL。如果根是一个众所周知的注册表键,那么子键不能是NULL。如果为子键提供NULL给基础API RegDeleteTree,那么它将忽略子键,而是删除根下的所有内容。虽然这在技术上是可能的,但想不到一个删除根下所有内容是有效解决方案的问题。最后,正如提到的,虽然不指定子键是合法的,如果根已经是一个先前打开的注册表键,不能删除一个没有提供的子键。

事务管理

//If an overall transaction was supplied, we use that. //If not, we use a local one. HANDLE localTransaction = INVALID_HANDLE_VALUE; if (transaction == INVALID_HANDLE_VALUE) { localTransaction = w32_CreateTransaction(); if (localTransaction == INVALID_HANDLE_VALUE) return GetLastError(); } else localTransaction = transaction; // .... if (transaction == INVALID_HANDLE_VALUE) { //there was a local transaction if (status != ERROR_SUCCESS) { RollbackTransaction(localTransaction); CloseHandle(localTransaction); return GetLastError(); } else { CommitTransaction(localTransaction); CloseHandle(localTransaction); return NO_ERROR; } }

正如提到的,事务参数是可选的。如果没有提供,这些更改将由一个本地事务覆盖,该事务在函数调用的开始处创建。在最后,根据输入值决定做什么。如果有总体事务,那么什么也不做,将决定留给控制总体事务的一方。否则,根据错误状态提交或回滚。

执行工作

//Open a transacted handle and delete everything //underneath the specified key / subkey HKEY hKey = NULL; DWORD status = ERROR_SUCCESS; if (status == ERROR_SUCCESS) status = RegOpenKeyTransacted( hKeyRoot, subKey, 0, KEY_WRITE | KEY_READ, &hKey, localTransaction, NULL); if (status == ERROR_SUCCESS) status = RegDeleteTree(hKey, NULL); //Delete the subkey itself. This function requires subKey to not be NULL. if (status == ERROR_SUCCESS) { if (deleteSubKey) { status = RegDeleteKeyTransacted( hKeyRoot, subKey, KEY_WRITE | KEY_READ, 0, localTransaction, NULL); } } if (hKey != NULL) CloseHandle(hKey);

主要功能首先是打开一个注册表键并删除该键下的所有内容。RegDeleteTree API本身不是事务感知的,但这就是很酷的事情:它不需要是。如果以事务方式打开了句柄,那么对该句柄的操作将是事务性的。第二部分是删除子键本身。为此,有一个事务API。就是这样!现在有一个辅助函数,以安全和可预测的方式执行多个操作(删除子键下的树,删除子键本身)。把所有内容放在一起,这是最终实现:

//Win32Helper.h LSTATUS w32_RegDeleteTreeTransacted( HKEY hKeyRoot, LPCTSTR subKey, bool deleteSubKey, HANDLE transaction = INVALID_HANDLE_VALUE); //win32Helper.cpp LSTATUS w32_RegDeleteTreeTransacted( HKEY hKeyRoot, LPCTSTR subKey, bool deleteSubKey, HANDLE transaction) { //The root cannot be NULL if (hKeyRoot == NULL) return ERROR_INVALID_PARAMETER; //Do not accidentally delete a root if ((hKeyRoot == HKEY_CLASSES_ROOT || hKeyRoot == HKEY_CURRENT_CONFIG || hKeyRoot == HKEY_CURRENT_USER || hKeyRoot == HKEY_LOCAL_MACHINE || hKeyRoot == HKEY_USERS) && subKey == NULL) return ERROR_INVALID_PARAMETER; //If the subkey itself needs to be deleted, it cannot be null if (deleteSubKey && subKey == NULL) return ERROR_INVALID_PARAMETER; //If an overall transaction was supplied, we use that. //If not, we use a local one. HANDLE localTransaction = INVALID_HANDLE_VALUE; if (transaction == INVALID_HANDLE_VALUE) { localTransaction = w32_CreateTransaction(); if (localTransaction == INVALID_HANDLE_VALUE) return GetLastError(); } else localTransaction = transaction; //Open a transacted handle and delete everything //underneath the specified key / subkey HKEY hKey = NULL; DWORD status = ERROR_SUCCESS; if (status == ERROR_SUCCESS) status = RegOpenKeyTransacted( hKeyRoot, subKey, 0, KEY_WRITE | KEY_READ, &hKey, localTransaction, NULL); if (status == ERROR_SUCCESS) status = RegDeleteTree(hKey, NULL); //Delete the subkey itself. This function requires subKey to not be NULL. if (status == ERROR_SUCCESS) { if (deleteSubKey) { status = RegDeleteKeyTransacted( hKeyRoot, subKey, KEY_WRITE | KEY_READ, 0, localTransaction, NULL); } } if (hKey != NULL) CloseHandle(hKey); if (transaction == INVALID_HANDLE_VALUE) { //there was a local transaction if (status != ERROR_SUCCESS) { RollbackTransaction(localTransaction); CloseHandle(localTransaction); return GetLastError(); } else { CommitTransaction(localTransaction); CloseHandle(localTransaction); return NO_ERROR; } } else { //The transaction originated outside this function, so do nothing return NO_ERROR; } }

有趣的点

到现在为止,应该很清楚事务的可能性是无穷无尽的。它们真的可以使代码更加健壮,防止半执行的更改破坏配置,而不需要自己编写大量的回滚代码。写这篇文章的原因是,第一篇文章更通用,而这篇文章解决了遇到的问题。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485