2

I am trying to write&read from the windows register:

Writting:

std::string path = "c:\\"
LPCTSTR str_data = TEXT(path.c_str());
auto size = static_cast<DWORD>(strlen(str_data));
LONG setRes = RegSetValueEx(*key, TEXT("DumpFolder"), 0, REG_EXPAND_SZ, (LPBYTE)str_data, size);

Reading:

char str_data[1028];
DWORD keyType;
DWORD size;
auto sk = TEXT("SOFTWARE\\Microsoft\\Windows\\Windows Error reporting\\LocalDumps");
auto status = RegGetValue(HKEY_LOCAL_MACHINE, sk, TEXT("DumpFolder"), RF_RT_REG_EXPAND_SZ, &keyType, str_data, &size);

Writing appears to work fine, at least it looks fine in regedit.exe.

Reading fails with ERROR_INVALID_PARAMETER = 87. If I change RF_RT_REG_EXPAND_SZ to RRF_RT_ANY, it works in debug mode, but still fails in release with error code ERROR_MORE_DATA = 234. I tried:

std::string path = "c:\\";
path = path + "\0"  (it should be null terminated anyway

but it doesn't help

UPDATE

First of all, thanks for answers, I understand the thing a little better now. Unfortunately, I am still unable to read the string successfully.

Here is the test example combined from the answer below:

HKEY registry_key;
LPCTSTR sk = "SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting";

// open registry key
auto openRes = RegOpenKey(HKEY_CURRENT_USER, sk, &registry_key);

// set default dump options
HKEY default_key;
auto createRes = RegCreateKey(registry_key, "LocalDumps", &default_key);
if (createRes != ERROR_SUCCESS) {
    auto b = createRes;
}    

std::string path = "c:\\";
LONG setRes = RegSetValueExA(default_key, "DumpFolder", 0, REG_EXPAND_SZ, (LPCBYTE)path.c_str(), path.size() + 1);

std::string str_data;
DWORD size = 0;
const char *sak = "SOFTWARE\\Microsoft\\Windows\\Windows Error reporting\\LocalDumps";
auto status = RegGetValueA(HKEY_CURRENT_USER, sak, "DumpFolder", RRF_RT_REG_EXPAND_SZ, NULL, NULL, &size);
if ((status == ERROR_SUCCESS) && (size > 1)) {
    str_data.resize(size - 1);
    status = RegGetValueA(HKEY_CURRENT_USER, sk, "DumpFolder", RRF_RT_REG_EXPAND_SZ, NULL, &str_data[0], &size);
}

Writing again works fine (checked in regedit, and the return error code). On the other hand, reading the size of string register sets the size to 0 and returns error code 87 = ERROR_INVALID_PARAMETER.

Apparently, I am still missing something. (the project is set to multy-byte character set)

SOLUTION

After fixing things proposed by the answers below, the following code worked for me:

#include <Windows.h>
#include <string>
#include <iostream>

#define reg_type HKEY_LOCAL_MACHINE

void main() {

    const std::string reg_path = "Software\\Microsoft\\Windows\\Windows Error Reporting\\LocalDumps";
    const std::string dump_folder = "DumpFolder";
    const std::string path = "c:\\";

    // WRITING
    HKEY default_key;
    auto status = RegCreateKeyExA(reg_type, reg_path.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &default_key, NULL);
    if (status != ERROR_SUCCESS) {
        std::cout << "Creating key failed.";
        return;
    }
    status = RegSetValueExA(default_key, dump_folder.c_str(), 0, REG_EXPAND_SZ, (LPCBYTE)path.c_str(), path.size() + 1);
    if (status != ERROR_SUCCESS) {
        std::cout << "Setting key value failed.";
        return;
    }

    // READING
    std::string str_data;
    DWORD size = 0;
    status = RegGetValueA(default_key, "", dump_folder.c_str(), RRF_NOEXPAND | RRF_RT_REG_EXPAND_SZ, NULL, NULL, &size);
    if ((status == ERROR_SUCCESS) && (size > 1)){
        str_data.resize(size - 1);
        status = RegGetValueA(default_key, "", dump_folder.c_str(), RRF_NOEXPAND | RRF_RT_REG_EXPAND_SZ, NULL, &str_data[0], &size);
        std::cout << "Successfully read key value: " << str_data;
    } else {
        std::cout << "Unable to retrive value. Error: " << status;
    }

    RegCloseKey(default_key);
}

I found, that RegGetValueA should be called with a

RRF_NOEXPAND | RRF_RT_REG_EXPAND_SZ 

flag, which appears strange, but is described in header where it is defined, so I guess it is correct. If using only

RRF_RT_REG_EXPAND_SZ

error 87 occurs ERROR_INVALID_PARAMETER.

GpG
  • 502
  • 5
  • 11
  • 2
    Modern Win32 programming is always using ``UNICODE`` and ``_UNICODE`` so you should be using wide-character strings. The old ``TCHAR`` and ``TEXT`` macro stuff was last useful when we still cared about Windows 95... – Chuck Walbourn Jan 24 '18 at 17:56
  • Another thing to keep in mind is that you can *read* from HKLM without administrator rights, you can't write it. – Chuck Walbourn Jan 24 '18 at 17:57
  • 4
    You did not initialize the size for reading. ***it works in debug mode, but still fails in release with error code*** Probably because uninitialized stack data is filled with 0xcc in debug mode but no initialization is done in release. `size` should be initialized to the size of your buffer. – drescherjm Jan 24 '18 at 18:01
  • Please produce a [MCVE] instead of small code snippets. – tambre Jan 24 '18 at 18:03
  • 1
    You didn't set the size variable. It's an in/out argument to RegGetValue. – Adrian McCarthy Jan 24 '18 at 22:50
  • `LPCTSTR str_data = TEXT(path.c_str());` The `TEXT` macro is for string literals and `path.c_str()` is not a string literal. The fact that this compiles tells me the compiler is in ANSI mode rather than Unicode mode. This isn't the cause of your problem, but it is a problem if you ever want to use Unicode. – Adrian McCarthy Jan 24 '18 at 22:52

1 Answers1

6

On the writing side:

std::string uses char elements, but TCHAR maps to either char or wchar_t depending on whether your code is compiled with UNICODE defined or not.

The TEXT() macro only works with compile-time literals, you can't use it with runtime data. TEXT(path.c_str()) is an invalid type-cast, and won't even compile if UNICODE is enabled.

You are clearly working with char data, so you should be using the char-based API functions instead of the TCHAR-based functions.

You are also not following one of the most important rules of RegSetValueEx():

For string-based types, such as REG_SZ, the string must be null-terminated. With the REG_MULTI_SZ data type, the string must be terminated with two null characters... The size of the information pointed to by the lpData parameter, in bytes. If the data is of type REG_SZ, REG_EXPAND_SZ, or REG_MULTI_SZ, cbData must include the size of the terminating null character or characters.

std::string::c_str() returns a pointer to null-terminated data, but you are not including the null terminator when reporting the size of the data you are writing to the Registry. RegGetValue() knows how to deal with that mistake, but RegGetValueEx() does not. You might not be the only person to ever read the value, so make sure you include the null terminator properly.

Try this instead:

std::string path = "c:\\";
LONG setRes = RegSetValueExA(*key, "DumpFolder", 0, REG_EXPAND_SZ, (LPCBYTE)path.c_str(), path.size()+1);

On the reading side:

You are getting errors because you are not telling RegGetValue() how large your str_data buffer is. You have to set your size variable to the size of str_data, in bytes, before you pass it in.

Try this instead:

char str_data[1028];
DWORD size = sizeof(str_data);
DWORD dwFlags = RRF_RT_REG_EXPAND_SZ;
// NOTE: when using RRF_RT_REG_EXPAND_SZ, RRF_NOEXPAND is *required* prior to Windows 8.1!
auto status = RegGetValueA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\Windows Error reporting\\LocalDumps", "DumpFolder", RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND, NULL, str_data, &size);

Alternatively:

std:string str_data;
DWORD size = 0;
const char *sk = "SOFTWARE\\Microsoft\\Windows\\Windows Error reporting\\LocalDumps";
// NOTE: when using RRF_RT_REG_EXPAND_SZ, RRF_NOEXPAND is *required* prior to Windows 8.1!
const DWORD dwFlags = RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND;
auto status = RegGetValueA(HKEY_LOCAL_MACHINE, sk, "DumpFolder", dwFlags, NULL, NULL, &size);
if ((status == ERROR_SUCCESS) && (size > 1))
{
    str_data.resize(size-1);
    status = RegGetValueA(HKEY_LOCAL_MACHINE, sk, "DumpFolder", dwFlags, NULL, &str_data[0], &size);
}

UPDATE: your new code fails because you have introduced new bugs.

You are using legacy Registry functions that are meant for 16bit apps. You need to use RegOpenKeyEx/RegCreateKeyEx instead of RegOpenKey/RegCreateKey, and then you can specify only the specific access rights that you actually need (create subkeys, set values, read values, etc). Even better, RegCreateKeyEx() creates missing keys for you, so you don't need to manually open a parent key separately just to create a new subkey.

Also, you changed HKEY_LOCAL_MACHINE to HKEY_CURRENT_USER, but not consistently. Some of your steps use one root, other steps use the other root. You are not able to read back the value you are writing because you are not reading from the same key you wrote to.

Try this instead:

LPCSTR sk = "SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting\\LocalDumps";
HKEY default_key;
auto status = RegCreateKeyExA(HKEY_LOCAL_MACHINE, sk, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &default_key, NULL); 
if (status == ERROR_SUCCESS)
{
    std::string path = "c:\\";
    status = RegSetValueExA(default_key, "DumpFolder", 0, REG_EXPAND_SZ, (LPCBYTE)path.c_str(), path.size() + 1);
    RegCloseKey(default_key);
}

LPCSTR sk = "SOFTWARE\\Microsoft\\Windows\\Windows Error reporting\\LocalDumps";
std::string str_data;
DWORD size = 0;
// NOTE: when using RRF_RT_REG_EXPAND_SZ, RRF_NOEXPAND is *required* prior to Windows 8.1!
const DWORD dwFlags = RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND;
auto status = RegGetValueA(HKEY_LOCAL_MACHINE, sk, "DumpFolder", dwFlags, NULL, NULL, &size);
if ((status == ERROR_SUCCESS) && (size > 1))
{
    str_data.resize(size - 1);
    status = RegGetValueA(HKEY_LOCAL_MACHINE, sk, "DumpFolder", dwFlags, NULL, &str_data[0], &size);
}

On the other hand, when you have to make multiple API calls to read a value (ie, to query the size, then query the data), you should explicitly open the parent key first:

const char *sk = "SOFTWARE\\Microsoft\\Windows\\Windows Error reporting\\LocalDumps";
std:string str_data;
HKEY default_key;
auto status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, sk, 0, KEY_QUERY_VALUE, &dumps_key); 
if (status == ERROR_SUCCESS)
{
    DWORD size = 0;
    // NOTE: when using RRF_RT_REG_EXPAND_SZ, RRF_NOEXPAND is *required* prior to Windows 8.1!
    const DWORD dwFlags = RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND;

    status = RegGetValueA(default_key, "", "DumpFolder", dwFlags, NULL, NULL, &size);
    if ((status == ERROR_SUCCESS) && (size > 1))
    {
        str_data.resize(size-1);
        status = RegGetValueA(default_key, "", "DumpFolder", dwFlags, NULL, &str_data[0], &size);
    }

    RegCloseKey(default_key);
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    thanks for great answer, unfortunately I am still getting an error -> I edited the original question. – GpG Jan 25 '18 at 12:04
  • the last problem was calling RegGetValueA with RRF_RT_REG_EXPAND_SZ, instead with RRF_NOEXPAND | RRF_RT_REG_EXPAND_SZ, which triggers 87 error. I added the final working example. Thank you again for your help! – GpG Jan 25 '18 at 21:17
  • @GpG: That error is a [known bug in Windows 7 and earlier](https://stackoverflow.com/a/47097895/65863), `RRF_RT_REG_EXPAND_SZ` must be used with `RRF_NOEXPAND`. That was fixed in Windows 8.1 so `RRF_NOEXPAND` is optional, as it should have been all along. – Remy Lebeau Jan 25 '18 at 22:17
  • @GpG: in your "final solution", you need to check if `RegOpenKeyExA()` (why not `RegCreateKeyExA()`?) succeeds before you call `RegSetValueExA()`. DO NOT use `KEY_ALL_ACCESS`, it only works for admins. Non-admins don't have write access to HKLM. You should NEVER open a key for reading and writing at the same time. Open a key for writing, write what you need, close the key. Open a key for reading, read what you need, close the key. You will have less errors that way. – Remy Lebeau Jan 25 '18 at 22:18