RegGetValueW 返回字符串数据大小不一致

Windows API RegGetValueW 函数的原型如下:

c++LSTATUS RegGetValueW(
  [in]                HKEY    hkey,
  [in, optional]      LPCWSTR lpSubKey,
  [in, optional]      LPCWSTR lpValue,
  [in, optional]      DWORD   dwFlags,
  [out, optional]     LPDWORD pdwType,
  [out, optional]     PVOID   pvData,
  [in, out, optional] LPDWORD pcbData
);

使用该 API 获取字符串值时,是否传递 pvData 参数会影响 pcbData 输出的值大小。

在注册表 HKEY_CURRENT_USER 下创建一个名为 Test 的字符串值,内容为 abcde。使用如下代码,分别获取字符串的字节数:

c++#include <iostream>
#include <windows.h>

int main()
{
    wchar_t data[100];
    DWORD cbData = 0;

    // pvData 参数为 NULL
    if (ERROR_SUCCESS != RegGetValueW(HKEY_CURRENT_USER, 
                                      nullptr, 
                                      L"Test", 
                                      RRF_RT_REG_SZ, 
                                      nullptr, 
                                      nullptr, 
                                      &cbData)) {
        throw std::runtime_error("RegGetValueW failed");
    }
    std::cout << "Value size: " << cbData << " bytes\n";
    
    // pvData 参数不为 NULL
    if (ERROR_SUCCESS != RegGetValueW(HKEY_CURRENT_USER, 
                                      nullptr, 
                                      L"Test", 
                                      RRF_RT_REG_SZ, 
                                      nullptr, 
                                      data, 
                                      &cbData)) {
        throw std::runtime_error("RegGetValueW failed");
    }
    std::cout << "Value size: " << cbData << " bytes\n";
}

输出结果为:

Value size: 14 bytes
Value size: 12 bytes

pvData 参数为 NULL 时,获得的字符串数据大小是 14 字节,当 pvData 参数不为 NULL 时,获得的字符串数据大小是 12 字节——前者比后者多了 2 个字节,也就是一个宽字符长度。通读了一遍微软的官方文档,对此现象并没有特别说明,而各个 AI 对此的解释也是不知所云。

一种合理的推测如下:首先,注册表保存了每个字符串值占用的字节长度(含末尾 NULL 字符),但并不保证实际存储的字符串数据必然是 NULL 结尾的;其次,使用 RegGetValueW 获取字符串值类型的数据必须保证是 NULL 结尾的。当 pvData 参数为 NULL 时,RegGetValueW 不会扫描整个字符串,所以不确定注册表保存的字符串数据末尾是否包含 NULL 字符。为了保证 Buffer 大小能足够容纳包含结尾的 NULL,就在输出 pcbData 时,在保存的数据值字节长度上加上一个宽字符的长度,也就是多出来的 2 个字节。当 pvData 参数不为 NULL 时,RegGetValueW 会在复制字符串值的时候检测末尾是否为 NULL,因此得到是真实长度。

注册表实际保存数据
ValueTypeSize (bytes)Data
TestREG_SZ12abcde\0

为了证实猜想,用以下代码构造一个末尾不包含 NULL 字符的注册表字符串值:

#include <iostream>
#include <windows.h>

int main()
{
    wchar_t testData[] = L"abcdefghijk";
    int cutOffLen = 5;
    
    if (ERROR_SUCCESS != RegSetValueExW(HKEY_CURRENT_USER, 
                                        L"Test", 
                                        0, 
                                        REG_SZ, 
                                        (BYTE*)testData, 
                                        cutOffLen * sizeof(wchar_t))) {
        throw std::runtime_error("RegSetValueExW failed");
    }
}
注册表实际保存数据
ValueTypeSize (bytes)Data
TestREG_SZ10abcde

重新运行上一段代码,输出结果是:

Value size: 12 bytes
Value size: 12 bytes

两次输出字节长度一致。Q.E.D.