连续调用 RegGetValue return 同一字符串的两个不同大小

Successive calls to RegGetValue return two different sizes for the same string

在某些代码中,我使用 Win32 RegGetValue() API 从注册表中读取字符串。

我调用前面提到的API两次:

  1. 第一次调用的目的是获得合适的大小为字符串分配目标缓冲区。

  2. 第二次调用 将字符串 从注册表中读取到该缓冲区中。

奇怪的是,我发现两次调用之间 RegGetValue() returns 不同大小 值。

特别是,第二次调用返回的大小值比第一次调用小两个字节(相当于一个wchar_t)。

值得注意的是,与实际字符串长度兼容的大小值是第二次调用返回的值(这个对应实际字符串长度,包括终止符NUL).
但我不明白为什么第一个调用 returns 比那个大两个字节(一个 wchar_t)。

附上程序输出截图和Win32 C++可编译重现代码。


重现源代码

#include <windows.h>
#include <iostream>
#include <string>
#include <vector>
using namespace std;


void PrintSize(const char* const message, const DWORD sizeBytes)
{
    cout << message << ": " << sizeBytes << " bytes (" 
         << (sizeBytes/sizeof(wchar_t)) << " wchar_t's)\n";
}


int main()
{
    const HKEY key = HKEY_LOCAL_MACHINE;
    const wchar_t* const subKey = L"SOFTWARE\Microsoft\Windows\CurrentVersion";
    const wchar_t* const valueName = L"CommonFilesDir";

    //
    // Get string size
    //
    DWORD keyType = 0;
    DWORD dataSize = 0;
    const DWORD flags = RRF_RT_REG_SZ;
    LONG result = ::RegGetValue(
        key, 
        subKey,
        valueName, 
        flags, 
        &keyType, 
        nullptr, 
        &dataSize);
    if (result != ERROR_SUCCESS)
    {
        cout << "Error: " << result << '\n';
        return 1;
    }
    PrintSize("1st call size", dataSize);
    const DWORD dataSize1 = dataSize; // store for later use


    //
    // Allocate buffer and read string into it
    //
    vector<wchar_t> buffer(dataSize / sizeof(wchar_t));
    result = ::RegGetValue(
        key, 
        subKey,
        valueName, 
        flags, 
        nullptr, 
        &buffer[0], 
        &dataSize);
    if (result != ERROR_SUCCESS)
    {
        cout << "Error: " << result << '\n';
        return 1;
    }
    PrintSize("2nd call size", dataSize);

    const wstring text(buffer.data());
    cout << "Read string:\n";
    wcout << text << '\n';
    wcout << wstring(dataSize/sizeof(wchar_t), L'*')  << "  <-- 2nd call size\n";
    wcout << wstring(dataSize1/sizeof(wchar_t), L'-') << "  <-- 1st call size\n"; 
}

操作系统: Windows 7 64-bit with SP1


编辑

我偶然在示例重现代码中读到的特定注册表项似乎引起了一些混淆。
所以,让我澄清一下,我从注册表中读取该密钥就像 test。这是 not 生产代码,我 notthat 特定密钥感兴趣。随时向注册表添加一个简单的测试密钥和一些测试字符串值。
对困惑感到抱歉。

RegGetValue()RegQueryValueEx() 更安全,因为如果字符串值还没有空终止符,它会人为地将空终止符添加到字符串值的输出中。

第一次调用 returns 数据大小加上额外空终止符的空间,以防实际数据尚未以空终止。我怀疑RegGetValue()在这个阶段没有看真实数据,它只是无条件地data size + sizeof(wchar_t)以确保安全。

(36 * sizeof(wchar_t)) + (1 * sizeof(wchar_t)) = 74

第二次调用returns读取的实际数据的实际大小。只有在必须人为添加一个时,该大小才会包括额外的空终止符。在这种情况下,您的数据在路径中有 35 个字符,并且存在一个真正的空终止符(行为良好的应用程序应该这样做),因此不需要添加额外的空终止符。

((35+1) * sizeof(wchar_t)) + (0 * sizeof(wchar_t)) = 72

现在,话虽如此,您真的不应该首先直接从注册表中读取以获取 CommonFilesDir 路径(或任何其他系统路径)。您应该改用 SHGetFolderPath(CSIDL_PROGRAM_FILES_COMMON) or SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon)。让 Shell 为您处理注册表。这在 Windows 个版本中是一致的,因为注册表设置可能会从一个版本移动到另一个版本,并且会考虑每个用户路径与系统全局路径。这些是首先引入 CSIDL API 的主要原因。