【问题标题】:Why does RegSetValueEx work even when I break the rule about accounting for NUL termination in the length?为什么即使我违反了关于在长度上考虑 NUL 终止的规则,RegSetValueEx 仍然有效?
【发布时间】:2013-05-13 21:51:37
【问题描述】:

我有一个将 calc.exe 添加到启动的简单程序:

#include <windows.h>
#include <tchar.h>

int main(){
    _tprintf(TEXT("Adding calc.exe to SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run...\n"));

    HKEY hRegRunKey;
    LPCTSTR lpKeyName = TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run");
    LPCTSTR lpKeyValue = TEXT("Calculator");

    LPCTSTR lpProgram = TEXT("C:\\WINDOWS\\system32\\calc.exe");
    DWORD cchProgram = _tcslen(lpProgram);

    _tprintf(TEXT("Path: %s. \n"), lpProgram);
    _tprintf(TEXT("Length: %d. \n"), cchProgram);

    if(RegOpenKeyEx( HKEY_LOCAL_MACHINE, lpKeyName, 0, KEY_SET_VALUE, &hRegRunKey) == ERROR_SUCCESS){
        if(RegSetValueEx(hRegRunKey, lpKeyValue, 0, REG_SZ, (const BYTE *)lpProgram, cchProgram * sizeof(TCHAR)) != ERROR_SUCCESS){
            _tprintf(TEXT("ERROR: Can't set key value.\n"));
            exit(1);
        }else{
            _tprintf(TEXT("Key has been added sucessfully.\n"));
        }
    }

    Sleep(5000);
    RegCloseKey(hRegRunKey);
}

对我来说,c/c++/WIN32API 的世界仍然充满了谜团……所以我没有几个问题。

1.当我定义字符串时,它会自动以 null 终止吗?

LPCTSTR lpProgram = TEXT("C:\\WINDOWS\\system32\\calc.exe");

或者应该这样做:

LPCTSTR lpProgram = TEXT("C:\\WINDOWS\\system32\\calc.exe\0");

2。在我的代码中,RegSetValueEx 的最后一个参数设置为正确的值吗?

来自MSDN - RegSetValueEx function页面:

cbData [in] lpData指向的信息的大小 参数,以字节为单位。如果数据的类型为 REG_SZ、REG_EXPAND_SZ 或 REG_MULTI_SZ, cbData 必须包含终止 null 的大小 一个或多个字符。

cchProgram 设置为 28 个字符没有空终止。在我的系统上(因为我认为是 UNICODE?)cchProgram * sizeof(TCHAR) = 56。

我不应该将它设置为 58 以添加空终止吗?


当我运行这个程序时,如上所述,没有任何修改,我将通过 Modify binary date 检查注册表中的计算器值,我得到:

43 00 3A 00 5C 00 57 00 C.:.\.W.
49 00 4E 00 44 00 4F 00 I.N.D.O.
57 00 53 00 5C 00 73 00 W.S.\.s.
79 00 73 00 74 00 65 00 y.s.t.e.
6D 00 33 00 32 00 5C 00 m.3.2.\.
63 00 61 00 6C 00 63 00 c.a.l.c.
2E 00 65 00 78 00 65 00 ..e.x.e.
00 00                   ..

它的 58 个字节包括空终止。我很困惑:/

更新

在计算 cbData 时,通过在字符串长度上加 1 来计算 NULL 字符,得到的结果与不加它完全相同。

cchProgram * sizeof(TCHAR) 生成与 (cchProgram + 1) * sizeof(TCHAR)

相同的数据条目

提供小于字符串长度的值不会添加 NULL 字节并复制给定的字节数。

27 * sizeof(TCHAR) 作为 cbData 产生的:

43 00 3A 00 5C 00 57 00 C.:.\.W.
49 00 4E 00 44 00 4F 00 I.N.D.O.
57 00 53 00 5C 00 73 00 W.S.\.s.
79 00 73 00 74 00 65 00 y.s.t.e.
6D 00 33 00 32 00 5C 00 m.3.2.\.
63 00 61 00 6C 00 63 00 c.a.l.c.
2E 00 65 00 78 00       ..e.x.

我在一些旧的 XP 上,服务包天知道是什么,我不知道其他版本的 windows 会如何处理它。

【问题讨论】:

  • 您在主题中提供的信息的应用应该是标签,这意味着您在这里没有发布任何主题信息。请edit您的问题并这样做。标签信息属于标签;这里的标记系统非常好,不需要任何帮助。谢谢。
  • @Ken White 你能帮我吗?我找不到比这个更好的标题了。我认为堆栈溢出可以让人们选择编辑其他人的帖子。更改它以获得更准确的标题,我一定会接受它。
  • @Kocur4d:把你的标题变成了一个主题摘要,我希望这适合你。
  • @Ben Voigt 谢谢。
  • @Kocur4d:Ben 打败了我。 :-) 您的标题需要描述您的问题主题(标签中不包含的信息,并提供有关您所问内容的信息)。标签将问题分类为一般主题(在本例中为winapic++),以便熟悉这些主题的人看到它们,以便将来的读者在搜索中正确找到它们。

标签: c++ c string winapi registry


【解决方案1】:

1:是的,它将被空终止,无需\0

双引号字符串 (") 是文字常量,其类型实际上是以空字符结尾的字符数组。因此,双引号之间的字符串文字总是在末尾自动附加空字符 ('\0')。

2:_tcslen() 不包括空终止符。可以加sizeof(TCHAR)添加。

该值仍然有效的原因可能是因为即使输入不正确,Windows 也会尝试保持稳健。它可能会自动为您附加空终止符。但是,由于文档说您必须包含空终止符,它可能并不总是附加它。

【讨论】:

  • lpProgram 不是字符串文字。它是一个指向字符串文字的指针。而sizeof lpProgram 是指针的大小。 -1
  • Windows 强大的观点是一个可悲的现实。如果您阅读 The Old New Thing,就会看到所有需要与损坏的应用程序保持和平的死代码,这真是太可怕了。
  • 我同意,试图保持向后兼容性一定很痛苦。我特别讨厌在 MS 的 AppCompat 部分工作。
【解决方案2】:

当我定义字符串时,它会自动以 null 结尾吗?

字符串字面量是以 null 结尾的,是的。 "Hello"其实就是{'H', 'e', 'l', 'l', 'o', '\0'}

在我的代码中,RegSetValueEx 的最后一个参数是否设置为正确的值?

你是对的,你需要空终止符。如果字符串文字很短,则更简单的方法是sizeof(TEXT("C:\\WINDOWS\\system32\\calc.exe")),因为sizeof("Hello") 是6;它包括空终止符,但在大多数情况下,您将需要您的变量,并且必须将您从字符串字符计数函数获得的长度加一,因为它们不包括空终止符。

Ben Voigt 在下面提出了一个很好的观点,const TCHAR[] program = TEXT("text"); 可以与调用中的文字 (sizeof(program)) 以相同的方式使用,但是当您想在代码中更改更少的位置时,它更易于维护,这对于任何实际项目都是必须的,而不是一个非常小的测试,甚至可以增长。

最后,有两件事你应该尽早摆脱:

  1. 匈牙利符号:不要这样做。它已经过时而且毫无意义。

  2. TCHAR:只要将宽字符串与任何 Windows API 函数一起使用即可。

您所做的绝对正确的是检查函数调用是否存在错误。您不会相信有多少问题可以通过检查失败并在文档中使用GetLastError 来解决。


既然你问过你应该如何使用 C++ 工具,这里有一种方法,有一些更改对使用 C++ 更有意义:

#include <windows.h>

int main(){
    //R means raw string literal. Note one backslash
    std::cout << R"(Adding calc.exe to SOFTWARE\Microsoft\Windows\CurrentVersion\Run...)" << '\n';

    const WCHAR[] keyName = LR"(SOFTWARE\Microsoft\Windows\CurrentVersion\Run)");

    std::cout << "Enter program name: ";
    std::wstring keyValue;        
    if (!std::getline(std::wcin, keyValue)) {/*error*/}

    std::cout << "Enter full program path: ";
    std::wstring program;
    if (!std::getline(std::wcin, program)) {/*error*/}

    std::wcout << "Path: " << program << ".\n";
    std::cout << "Length: " << program.size() << ".\n";

    HKEY runKey;
    if(RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyName, 0, KEY_SET_VALUE, &runKey)) {/*error*/}

    if(RegSetValueExW(runKey, keyValue.c_str(), 0, REG_SZ, reinterpret_cast<const BYTE *>(program.c_str()), (program.size() + 1) * 2)) {
        std::cout << "ERROR: Can't set key value.\n";
        return 1;
    }

    if (RegCloseKey(runKey)) {/*error*/}

    std::cout << "Key has been added successfully.\n";

    std::cout << "Press enter to continue..."    
    std::cin.get();
}

使用 C++ 习惯用法的更好方法是至少有一个 RegKey RAII 类,该类在其析构函数中调用 RegCloseKey 并为您节省工作量。至少可以这样使用:

RegKey key(HKEY_LOCAL_MACHINE, keyName, KEY_SET_VALUE);
RegSetValueExW(key, ...); //could have implicit or explicit conversion, fill in the ...
//RegCloseKey called when key goes out of scope

【讨论】:

  • cch 前缀实际上非常有用,因为它会立即告诉正在阅读程序的人这是字符数,而不是字节数。我在这段代码中看到的其他前缀完全没用。
  • @BenVoigt,我个人会使用 prgmLen 之类的东西。老实说,我可能会使用std::wstring 及其size() 函数来代替将其存储为变量。
  • prgmLen 不会告诉我重要的事情——它是字节还是字符。 cch 是 GOOD Hungarian(又名 Apps Hungarian)。 lp 是 BAD Hungarian(又名 System Hungarian)。
  • @BenVoigt,好吧,绝对是字符与字节。我的脑海中默认使用没有指示来表示字符,但这对其他人来说绝对不是显而易见的。
  • 与史蒂夫相同的抱怨:lpProgram 不是字符串文字。它是一个指向字符串文字的指针。而sizeof lpProgram 是指针的大小,而不是字符串的长度。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-14
  • 1970-01-01
相关资源
最近更新 更多