对RegGetValue的连续调用为同一个字符串返回两种不同的大小

Mr.*_*C64 4 c++ string registry winapi

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

我将上述API调用两次:

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

  2. 第二个调用将字符串从注册表读入该缓冲区.

奇怪的是,我发现在两次调用之间RegGetValue()返回不同的大小值.

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

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

附有程序输出和Win32 C++可编译repro代码的屏幕截图.

RegGetValue()返回的不同大小值


Repro源代码

#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"; 
}
Run Code Online (Sandbox Code Playgroud)

操作系统:带有SP1的Windows 7 64位


编辑

我碰巧在示例repro代码中读到的特定注册表项似乎引起了一些混淆.
所以,让我澄清一下,我从注册表中读取该密钥作为测试.这不是生产代码,我对那个特定的密钥不感兴趣.随意添加一个简单的测试密钥到注册表与一些测试字符串值. 对困惑感到抱歉.

Rem*_*eau 9

RegGetValue()更安全,RegQueryValueEx()因为如果它没有空终止符,它会在字符串值的输出中人为地添加一个空终止符.

如果实际数据尚未以空值终止,则第一个调用将返回数据大小加上额外空终止符的空间.我怀疑RegGetValue()在这个阶段没有看到真实的数据,它只是无条件data size + sizeof(wchar_t)地保证安全.

(36 * sizeof(wchar_t)) + (1 * sizeof(wchar_t)) = 74
Run Code Online (Sandbox Code Playgroud)

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

((35+1) * sizeof(wchar_t)) + (0 * sizeof(wchar_t)) = 72
Run Code Online (Sandbox Code Playgroud)

现在,说到这一点,你真的不应该直接从注册表中读取以获取CommonFilesDir路径(或任何其他系统路径).你应该使用SHGetFolderPath(CSIDL_PROGRAM_FILES_COMMON)或SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon)代替.让壳牌为您处理注册表.这在Windows版本中是一致的,因为注册表设置可以从一个版本移动到另一个版本,以及考虑每用户路径与系统全局路径.这些是CSIDL首先引入API的主要原因.


Mr.*_*C64 0

这篇博文(发布于 2024 年 2 月 14 日)澄清了这个问题:

\n

老新事物 - 返回所需缓冲区大小的函数通常返回上限,而不是严格边界

\n
\n

Windows 中有许多函数属于三阶段操作的一部分:

\n
    \n
  1. 请求接收某些数据所需的缓冲区大小。
  2. \n
  3. 分配该大小的缓冲区。
  4. \n
  5. 使用该缓冲区再次调用该函数。
  6. \n
\n

当您请求所需的缓冲区大小时,当您请求将数据放入缓冲区时,函数返回的值大于从步骤 3 获得的实际值的情况并不少见。

\n
\n
\n

[\xe2\x80\xa6] 鉴于调用者无论如何都必须为大小改变做好准备,\xe2\x80\x9chow 我需要多大的缓冲区吗\xe2\x80\x9d 调用可能会返回高估的值所需的大小,因为这将允许对数据的第二次调用成功(假设数据尚未更改)。给出高估值通常比给出精确值容易得多。

\n
\n

我认为官方 MSDN 文档应该使用该信息进行更新。

\n