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