Win32编程3-注册表读取,创建,修改
注册表的话原本打算做成用户输入整个路径然后直接读取,但这样的话似乎没有什么难度,于是做成了用列表显示每一个子键以及对应的值,并能逐级深入。效果图如下。中间的框是子键列表,右边的框是键值列表。
首先是根键
由于根键只有五个,所以干脆弄成单选框,当用户点击提交按钮时,会调用函数返回被选中的值
int IfChecked()
{
for (int i = 0; i < 5; i++)
{
if (SendMessage(HkeysBtn[i], BM_GETCHECK, NULL, NULL) == BST_CHECKED)
{
Hkey = Hkeys[i];
return i;
}
}
return 0;
}
HkeysBtn[i]是一个数组,储存了五个单选框的句柄,然后调用SendMessage(HkeysBtn[i], BM_GETCHECK, NULL, NULL)判断是否被选中。点击提交按钮后会触发下列函数
res = IfChecked();
result = GetRegChild(RegPathStatic, Hkeys[res], res, 0, "");
ValueResult = GetRegChild(RegPathStatic, Hkeys[res], res, 2, TEXT(""));
SetWindowText(HkeyValueEdit, ValueResult);
SetWindowText(HkeyEdit, result);
GetRegChild函数,第一个参数为注册表路径的句柄,用来最后输出当前注册表路径,Hkeys是一个全局数组,里面存放的是根键,
vector<HKEY> Hkeys = { HKEY_CLASSES_ROOT, HKEY_CURRENT_CONFIG, HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE, HKEY_USERS };
res就是选中的根键序号,倒数第二个参数是标记,在不同的地方调用这个函数标记不同,执行的操作也不同,最后一位是深入注册表时追加的路径。Flag0和1都是用来枚举子键用的,0是打开根键时进行的操作,1是深入目录进行的操作,2是枚举键值时进行的操作。
枚举注册表需要先用RegCreateKeyEx(Hkey, TEXT(""), 0, NULL, 0, p, NULL, &hKey, NULL)
打开对应的注册表键,Hkey就是根键,TEXT(“”)表示空,意思就是打开根键,然后再用RegEnumKeyEx(hKey, i, (LPWSTR)KeyName, &lang, NULL, NULL, NULL, NULL)
枚举子键,而在编辑框里显示也比较简单,就是用一个CString拼接起所有的子键,通过\r\n进行换行,将最终结果返回,然后显示在编辑框里。
temppath是一个全局字符串,储存的是注册表路径里剔除了根键剩下的值。RegResult和RegValueResult是一个全局数组,储存的是每一个子键下的子子键,或者是每一个子键的值。这样用户在输入想要深入的目录时,可以从中获取具体的子键名称从而拼接到temppath上,然后深入并拼接结果字符串,同时清空结果数组,储存新的结果。
CString GetRegChild(HWND hwnd, HKEY Hkey, int n, BYTE Flag, CString AddPath)
{
BOOL isWOW64;
REGSAM p = KEY_READ;
IsWow64Process(GetCurrentProcess(), &isWOW64); //判断环境是否为WOW64
if (isWOW64)
p |= KEY_WOW64_64KEY;
HKEY hKey;//存放打开的注册表键
TCHAR KeyName[MAX_PATH] = { 0 };//用来保存枚举值或子键的临时数组
int i = 0;
CString result;//最终结果
DWORD lang = 256;
LONG re, RequestRes;
CString num;//序号
if (Flag == 0)
{
path = CSHkeys[n];
if (RegCreateKeyEx(Hkey, TEXT(""), 0, NULL, 0, p, NULL, &hKey, NULL) != ERROR_SUCCESS) {
//失败
;
} //打开键
while (RegEnumKeyEx(hKey, i, (LPWSTR)KeyName, &lang, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
{
++i;
lang = 256;
num.Format(TEXT("%d"), i);//将int转换为CString,方便拼接
result += num;
result += ". ";
result += KeyName;
result += "\r\n";
RegResult.push_back(KeyName);
}
}
else if (Flag == 1)
{
RegResult.clear();
if (temppath == "")
{
temppath += AddPath;
}
else
{
temppath += "\\";
temppath += AddPath;
}
path += "\\";
path += AddPath;
if (RegCreateKeyEx(Hkey, temppath, 0, NULL, 0, p, NULL, &hKey, NULL) != ERROR_SUCCESS) {
//失败
;
} //打开键
while (RequestRes = RegEnumKeyEx(hKey, i, (LPWSTR)KeyName, &lang, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
{
++i;
lang = 256;
num.Format(TEXT("%d"), i);
result += num;
result += ". ";
result += KeyName;
result += "\r\n";
RegResult.push_back(KeyName);
}
}
else if (Flag == 2)
{
RegValueResult.clear();
DWORD type;
LONG res = RegOpenKeyEx(Hkeys[n], temppath, 0, KEY_ALL_ACCESS | KEY_WOW64_32KEY, &hKey);
if (res != ERROR_SUCCESS) {
MessageBox(hwnd, TEXT("打开失败"), TEXT(""), MB_OK);
return TEXT("");
}
while (RegEnumValue(hKey, i, (LPWSTR)KeyName, &lang, NULL, &type, NULL, NULL) == ERROR_SUCCESS)
{
++i;
lang = 256;
num.Format(TEXT("%d"), i);
result += num;
result += ". ";
result += KeyName;
result += "\r\n";
RegValueType.push_back(type);
RegValueResult.push_back(KeyName);
}
}
SetWindowText(hwnd, path);
return result;
}
这是用户输入想要进入的下一级目录后执行的函数。
GetWindowNum是对用户输入的值进行判断是否为空然后将对应字符串转换成整形并返回。并调用GetRegChild(RegPathStatic, Hkeys[res], res, 1, RegResult[num - 1])
返回新的字符串结果并显示在编辑框内,这样就能继续深入注册表。
null = TEXT("在此输入下一级目录的序号");
num = GetWindowNum(hwnd, NumInput, null);
if (num >= 0 && num <= RegResult.size())
{
result = GetRegChild(RegPathStatic, Hkeys[res], res, 1, RegResult[num - 1]);
SetWindowText(HkeyEdit, result);
ValueResult = GetRegChild(RegPathStatic, Hkeys[res], res, 2, TEXT(""));
SetWindowText(HkeyValueEdit, ValueResult);
}
枚举键值与枚举子键差不多,都是先打开注册表键,然后用RegEnumValue枚举对应的值,就是调用的函数不同,对于结果的处理与枚举子键一样,这里就不多赘述。
接下来是创建子键或键值,是在当前路径下创建,就是在左上static框里显示的路径,path是带有根键的注册表路径。点击创建按钮后会显示一个新窗口
与之前创建新窗口原理一致,就不再赘述了。
当用户点击提交时,对应处理如下,最外层if
里是判断是要创建子键或者键值,然后获取用户输入的内容并判空,如果不为空则调用CreateReg(hwnd, KeyValueName, KeyValue, RegType[res], 1)
,参数依次是,主窗口句柄,子键或键值名,当创建子键时KeyValue为空,否则就是键值,RegType是要创建的键值类型,目前只写了四类,vector<unsigned long> RegType = { REG_SZ, REG_MULTI_SZ, REG_BINARY, REG_DWORD };
,通过下拉列表菜单选择,并根据int res = SendMessage(RegTypeBox, CB_GETCURSEL, 0, 0);
获取选择项。
if (SendMessage(Reg1, BM_GETCHECK, NULL, NULL) == BST_CHECKED)
{
CString null = "输入子键名";
InputSize = GetWindowTextLength(RegNameEdit1);
ChildKeyName = new wchar_t[InputSize + 1];
GetWindowText(RegNameEdit1, ChildKeyName, InputSize + 1);
if (wcscmp(ChildKeyName, null) == 0)
{
MessageBox(hwnd, TEXT("请输入子键名"), TEXT("提示"), MB_OK);
break;
}
CreateReg(hwnd, ChildKeyName, TEXT(""), 0, 0);
}
else if (SendMessage(Reg2, BM_GETCHECK, NULL, NULL) == BST_CHECKED)
{
CString null = "输入键值名";
InputSize = GetWindowTextLength(RegNameEdit2);
KeyValueName = new wchar_t[InputSize + 1];
GetWindowText(RegNameEdit2, KeyValueName, InputSize + 1);
if (wcscmp(KeyValueName, null) == 0)
{
MessageBox(hwnd, TEXT("请输入键值名"), TEXT("提示"), MB_OK);
break;
}
InputSize = GetWindowTextLength(RegNameEdit3);
KeyValue = new wchar_t[InputSize + 1];
GetWindowText(RegNameEdit3, KeyValue, InputSize + 1);
null = "请输入键值值";
if (wcscmp(KeyValue, null) == 0)
{
MessageBox(hwnd, TEXT("请输入键值值"), TEXT("提示"), MB_OK);
break;
}
int res = SendMessage(RegTypeBox, CB_GETCURSEL, 0, 0);
CreateReg(hwnd, KeyValueName, KeyValue, RegType[res], 1);
}
else
{
MessageBox(hwnd, TEXT("请选择要创建的类型"), TEXT("提示"), MB_OK);
}
接着是创建子键或值,创建子键时调用RegCreateKeyEx,如果子键存在则打开,不存在则创建,temppath为空时则是在根键下创建子键。
创建键值时则是先打开对应子键,然后通过RegSetValueEx直接写入,也没有根据不同类型做不同处理,就是一股脑写入,但在后面修改键值时实现了,这里也懒得改了。
void CreateReg(HWND hwnd, CString Name, CString Value, unsigned long num, BYTE Flag)
{
CString temp;
HKEY hKey; //创建注册项
if (Flag == 0)
{
if (temppath == "")
{
temp = Name;
}
else
{
temp = temppath + "\\" + Name;
}
LONG result = RegCreateKeyEx(Hkey, (LPCTSTR)temp, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS | KEY_WOW64_32KEY, NULL, &hKey, NULL);
if (result != ERROR_SUCCESS)
{
MessageBox(hwnd, TEXT("创建失败"), TEXT("提示"), MB_OK);
return;
if (hKey != 0)
{
RegCloseKey(hKey);
}
}
MessageBox(hwnd, TEXT("创建成功"), TEXT("提示"), MB_OK);
SendMessage(hwnd, WM_CLOSE, NULL, NULL);
}
else if (Flag == 1)
{
long result = RegOpenKeyEx(Hkey, (LPCTSTR)temppath, 0, KEY_ALL_ACCESS | KEY_WOW64_32KEY, &hKey);
if (result != ERROR_SUCCESS)
{
MessageBox(hwnd, TEXT("打开失败"), TEXT("提示"), MB_OK);
return;
}
DWORD Length = Value.GetLength();
result = RegSetValueEx(hKey, (LPCTSTR)Name, 0, num, (LPBYTE)&Value, Length);
if (ERROR_SUCCESS != result)
{
MessageBox(hwnd, TEXT("创建失败"), TEXT("提示"), MB_OK);
if (hKey != 0)
{
RegCloseKey(hKey);
}
return;
}
MessageBox(hwnd, TEXT("创建成功"), TEXT("提示"), MB_OK);
SendMessage(hwnd, WM_CLOSE, NULL, NULL);
}
}
在右下用户输入想读取的键值后,会打开一个新窗口,如图
创建窗口原理与上述也一致,页面排版就不多说了,就是CreateWindow函数反复使用,在创建新窗口前会调用QueryRegValue查询键值名称,类型和值,具体解释都在注释里,这里不多叙述,DWORD类型是最简单的,就没写注释,每一种类型都是通过RegGetValue先获取值的大小,然后在读取值并用对应类型接收,最后转为CString类型,并赋值给r,这是全局CString。
值得一提的是RegGetValu是微软最新的用来读取值的api,比之前要先打开键,再获取值要方便不少,参数的含义依次是根键,路径,值的名称(这里使用结果数组储存,详见上面)然后是键的类型,nullptr那项不用管,想要了解的话可以自己搜一下,然后是用来存放结果的指针,和结果的大小。
void QueryRegValue(int n)
{
HKEY hKey = NULL;
ValueName1 = RegValueResult[n];
GetValueType(n);
if (RegValueType[n] == REG_DWORD)
{
DWORD data{};
DWORD dataSize = sizeof(data);
LONG retCode = RegGetValue(Hkey, temppath, RegValueResult[n], RRF_RT_REG_DWORD, nullptr, &data, &dataSize);
RegValueRes = data;
r.Format(TEXT("%u"), RegValueRes);
}
else if (RegValueType[n] == REG_SZ)
{
r = "";
DWORD dataSize{};
//获取用于存储字符串值的输出缓冲区的大小
LONG retCode = RegGetValue(Hkey,temppath,RegValueResult[n],RRF_RT_REG_SZ,nullptr,nullptr,&dataSize);
wstring data;
//为输出字符串分配所需大小
data.resize(dataSize / sizeof(wchar_t));
//写入实际的字符串数据
retCode = RegGetValue(Hkey, temppath, RegValueResult[n], RRF_RT_REG_SZ, nullptr, &data[0], &dataSize);
/*如果成功,RegGetValue 会在 dataSize 变量中写入实际结果字符串大小(以字节为单位)。
必须根据此大小重设 wstring 对象的大小。由于 dataSize 是以字节为单位,
因此在处理 wstring 时最好转换成相应的 wchar_t 计数:*/
DWORD stringLengthInWchars = dataSize / sizeof(wchar_t);
/*dataSize 包括输出字符串的终止符 NUL。
不过,由于 wstring 对象已经以 NUL 结尾,
因此必须注意避免读取字符串出现虚假的双 NUL 终止符。必须删除*/
stringLengthInWchars--;
data.resize(stringLengthInWchars);
r = data.c_str();
}
else if (RegValueType[n] == REG_MULTI_SZ)
{
DWORD dataSize{};
LONG retCode = RegGetValue(Hkey, temppath, RegValueResult[n], RRF_RT_REG_MULTI_SZ, nullptr, nullptr, &dataSize);
vector<wchar_t> data;
data.resize(dataSize / sizeof(wchar_t));
/*以上同REG_SZ类型处理*/
/*由于 RegGetValue API 返回的大小值以字节为单位,
因此必须先将它正确转换成 wchar_t 计数,然后才能传递给 vector::resize*/
retCode = RegGetValue(Hkey, temppath, RegValueResult[n], RRF_RT_REG_MULTI_SZ, nullptr, &data[0], &dataSize);
data.resize(dataSize / sizeof(wchar_t));
/*此时,数据变量(即 vector<wchar_t>)存储以双 NUL 结尾的字符串序列。
最后一步是,解析此数据结构,并将它转换成级别更高且更便捷的 vector<wstring>*/
vector<wstring> result;
const wchar_t* currStringPtr = &data[0];
while (*currStringPtr != L'\0')
{
// 多字符串,顾名思义由几个以'\0'结尾的字符串组成,故要对每一段进行转换。
const size_t currStringLength = wcslen(currStringPtr);
// 添加到新结果数组
result.push_back(std::wstring{ currStringPtr, currStringLength });
// 移到下一个字符串
currStringPtr += currStringLength + 1;
}
//将每一段wstring拼接成一个总的CString
for (auto& a : result)
{
r += a.c_str();
r += " ";
}
}
else if (RegValueType[n] == REG_BINARY)
{
DWORD dataSize{};
CString str;
LONG retCode = RegGetValue(Hkey, temppath, RegValueResult[n], RRF_RT_REG_BINARY, nullptr, nullptr, &dataSize);
BYTE *data = new BYTE(dataSize);
/*以上同REG_SZ类型处理*/
retCode = RegGetValue(Hkey, temppath, RegValueResult[n], RRF_RT_REG_BINARY, nullptr, &data[0], &dataSize);
//将每一段wstring拼接成一个总的CString
for (int i = 0; i < dataSize; i++)
{
str.Format(TEXT("%c"), data[i]);
r += str;
r += " ";
}
}
}
然后根据用户输入进行修改,实际上就是直接覆盖。都是先获取用户输入,用 wchar_t类型的数组储存,然后进行对应处理,其中REG_BINARY比较麻烦,因为BINARY的值是类似01 10 11 11这样的,具体操作都在注释中,感觉获取用户输入内容可以提出来做个函数,但都做完了,就懒得换了。
void ChangeRegValue(HWND hwnd, HWND h)
{
HKEY hKey = NULL;
DWORD Value;
CString str;
int InputSize = GetWindowTextLength(hwnd);
DWORD res;
if (RegOpenKeyEx(Hkey, temppath, 0, KEY_SET_VALUE, &hKey) != ERROR_SUCCESS)
{
MessageBox(hwnd, TEXT("打开失败"), TEXT(""), MB_OK);
return;
}
if (RegTypeRes == REG_BINARY)
{
InputSize = GetWindowTextLength(hwnd);
wchar_t* temp = new wchar_t[InputSize + 1];
GetWindowText(hwnd, temp, InputSize);
str.Format(TEXT("%s"), temp); //获取输入内容并转换为CString
//获取将宽字符转换为多字符串所需长度
int len = WideCharToMultiByte(CP_ACP, 0, str, str.GetLength(), NULL, 0, NULL, NULL);
//将得到的长度用于构造新的字符串
unsigned char* pByte = (unsigned char*)malloc((len + 1) * sizeof(unsigned char));
if (pByte != NULL)
{
memset(pByte, 0, (len + 1) * sizeof(unsigned char));//重新设置字符串大小
//将宽字符串转换为多字符串
WideCharToMultiByte(CP_ACP, 0, str.GetBuffer(), str.GetLength(), (LPSTR)pByte, len, NULL, NULL);
*(pByte + len + 1) = '\0';//在最后补\0
}
RegSetValueEx(Hkey, ValueName1, NULL, RegTypeRes, pByte, len);
}
else if (RegTypeRes == REG_SZ)
{
InputSize = GetWindowTextLength(hwnd);
wchar_t* temp = new wchar_t[InputSize+2];
GetWindowText(hwnd, temp, InputSize+2);
//直接获取字符串并写入
res = RegSetValueEx(Hkey, ValueName1, NULL, RegTypeRes, (const BYTE*)temp, 256);
}
else if (RegTypeRes == REG_MULTI_SZ)
{
InputSize = GetWindowTextLength(hwnd);
wchar_t* temp = new wchar_t[InputSize + 1];
GetWindowText(hwnd, temp, InputSize + 1);
str = temp;
//获取输入内容并将其中的空格替换为换行符
str.Replace(TEXT(" "), TEXT("\r\n"));
//再将CString转为wchar_t字符串
temp = str.GetBuffer();
res = RegSetValueEx(Hkey, ValueName1, NULL, RegTypeRes, (CONST BYTE*)temp, str.GetLength()*2);
}
else if (RegTypeRes == REG_DWORD)
{
InputSize = GetWindowTextLength(hwnd);
wchar_t* temp = new wchar_t[InputSize + 1];
GetWindowText(hwnd, temp, InputSize+1);
str = temp;
//将wchar_t字符串转换为DWORD类型
Value = _tcstoul(str, NULL, 16);
res = RegSetValueEx(Hkey, ValueName1, NULL, RegTypeRes, (CONST BYTE*) & Value, sizeof(DWORD));
}
if (res != ERROR_SUCCESS)
{
MessageBox(hwnd, TEXT("修改失败"), TEXT(""), MB_OK);
}
else
{
MessageBox(hwnd, TEXT("修改成功"), TEXT(""), MB_OK);
}
SendMessage(h, WM_CLOSE, NULL, NULL);
}
至此读取,创建,修改注册表的内容就结束了,上述操作经过测试都是可用的,但没有做太多的非法输入处理,主要是懒,而且也用不上。毕竟不是大规模使用,就是自己练练手。明天说一下进程类的操作,相对之下算是非常简单了。