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

时间:2015-03-24 00:58:28

标签: c++ string winapi registry

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

我将上述API调用两次:

  1. 第一次调用的目的是获取正确的大小以为字符串分配目标缓冲区。

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

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

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

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

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

    Different size values returned by 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"; 
    }
    

    操作系统:Windows 7 64位SP1;


    修改

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

1 个答案:

答案 0 :(得分:9)

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

如果实际数据尚未终止,则第一个调用返回数据大小加空间以获得额外的空终止符。我怀疑RegGetValue()在此阶段没有查看真实数据,只是无条件data size + sizeof(wchar_t)是安全的。

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

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

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

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