How to pass std::string value to RegSetValueEx() in C++ - c++

I am writing a program to edit the windows registry key by C++, but when I try to pass a string value to library function RegSetValueEx(), there is a file start with TEXT() which could only be hardcode value in it.
Parts of my code:
string region;
string excelserver_type;
string keyname = region + excelserver_type;
if (RegSetValueEx(key64, TEXT("XXXXXXXXX"), 0, REG_SZ, (LPBYTE)TEXT("XXXXXXXXXX"), 100) != ERROR_SUCCESS)
{
RegCloseKey(key);
cout << "Unable to set registry value in HKEY_LOCAL_MACHINE\\Software" << endl;
}
When I try to replace "XXXXXXXX" by keyname, it gives me an error. How do I pass value of keyname in RegSetValueEx()?

You need to use the std::wstring type instead. This will give you a wide (Unicode) string, based on the wchar_t type, which is how Windows stores strings internally.
Then, you can simply use the c_str() member function to retrieve a pointer to a C-style string, and pass this directly to the RegSetValueEx function. The size() member function gives you the length of the string, which you can pass as the cbData parameter, except for two caveats:
cbData expects the length of the string to include the terminating NUL character, so you will need to add 1 to the length returned by size().
cbData expects the size of the string in bytes, not the number of characters, so for a wide string, you will need to multiply the value returned by size() by the length of a wchar_t.
bool SetStringValue(HKEY hRegistryKey,
const std::wstring& valueName,
const std::wstring& data)
{
assert(hRegistryKey != nullptr);
return (RegSetValueExW(hRegistryKey,
valueName.c_str(),
0,
REG_SZ,
(LPBYTE)(data.c_str()),
(data.size() + 1) * sizeof(wchar_t)) == ERROR_SUCCESS);
}
If you absolutely have to use narrow (ANSI) strings (and you shouldn't, because you're interfacing directly with the operating system here, not working with user data), you can do the same thing but explicitly call the ANSI version of RegSetValueEx, which has an A suffix. Here, you still need to add 1 to the length, but the size in bytes is equivalent to the number of characters, so no scaling is necessary.
bool SetStringValue_ANSI(HKEY hRegistryKey,
const std::string& valueName,
const std::string& data)
{
assert(hRegistryKey != nullptr);
return (RegSetValueExA(hRegistryKey,
valueName.c_str(),
0,
REG_SZ,
(LPBYTE)(data.c_str()),
data.size() + 1) == ERROR_SUCCESS);
}
Internally, RegSetValueExA will convert the string to Unicode and then perform the same task as RegSetValueExW.

Related

Issue with converting a filesystem path to a const BYTE*

What I'm trying to achieve is getting my program executed on Windows startup using the Registry. When I try to put the file location, the compiler complains it cannot convert from filesystem::path to const BYTE*. I have no idea how to fix this, since I'm a beginner when it comes to C++. I have provided the code below:
HKEY newValue;
RegOpenKey(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", &newValue);
RegSetValueEx(newValue, "myprogram", 0, REG_SZ, fs::temp_directory_path().append(filename), sizeof("tes3t")); // This line is the issue. fs::temp_directory_path().append(filename)
RegCloseKey(newValue);
return 0;
EXCEPTION: No suitable conversion function from "std:filesystem::path" to "const BYTE *" exists
Per the RegSetValueExA() documentation, the function does not accept a std::filesystem::path object. That is what the error message is complaining about.
LSTATUS RegSetValueExA(
HKEY hKey,
LPCSTR lpValueName,
DWORD Reserved,
DWORD dwType,
const BYTE *lpData, // <-- here
DWORD cbData
);
The 5th parameter takes a const BYTE* pointer to a null-terminated C-style string. The 6th parameter takes the number of characters in the string, including the null terminator:
lpData
The data to be stored.
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.
Note lpData indicating a null value is valid, however, if this is the case, cbData must be set to '0'.
cbData
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::filesystem::path does not have an implicit conversion to const BYTE*, hence the compiler error. You need to explicitly convert the path to a std::string or std::wstring first (prefer the latter, since the Registry stores strings as Unicode internally), before you can then save that string value to the Registry, eg:
// using std::string...
HKEY newValue;
// don't use RegOpenKey()! It is provided only for backwards compatibility with 16bit apps...
if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_SET_VALUE, &newValue) == 0)
{
// this may lose data for non-ASCII characters!
std::string s = fs::temp_directory_path().append(filename).string();
// this will convert the ANSI string to Unicode for you...
RegSetValueExA(newValue, "myprogram", 0, REG_SZ, reinterpret_cast<LPCBYTE>(s.c_str()), s.size()+1);
RegCloseKey(newValue);
}
return 0;
// using std::wstring...
HKEY newValue;
// don't use RegOpenKey()! It is provided only for backwards compatibility with 16bit apps...
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_SET_VALUE, &newValue) == 0)
{
// no data loss here!
std::wstring s = fs::temp_directory_path().append(filename).wstring();
// no ANSI->Unicode conversion is performed here...
RegSetValueExW(newValue, L"myprogram", 0, REG_SZ, reinterpret_cast<LPCBYTE>(s.c_str()), (s.size()+1) * sizeof(WCHAR));
RegCloseKey(newValue);
}
return 0;
The WinAPI functions do not take arguments of std::filesystem::path type so you need to convert it to a const BYTE* somehow.
This is one example:
std::string fullpath = (fs::temp_directory_path() / filename).string();
RegSetValueEx(
newValue,
"myprogram",
0,
REG_SZ,
reinterpret_cast<LPCBYTE>(fullpath.c_str()), // returns a "const char*" then cast
fullpath.size() + 1 // + 1 for null terminator
);

Registry is setting 1 char

My code is setting 1 char of the registry name and value.
bool setRegValue(std::wstring valueName, std::wstring valueToSet)
{
HKEY key=NULL;
if (get_HKEY_LOCAL_MACHINE(&key) && (key!=NULL))
{
if (RegSetValueEx((HKEY)key, (LPCSTR)valueName.c_str(), 0, REG_SZ, (const BYTE*)valueToSet.c_str(), (valueToSet.size() +1)*sizeof(wchar_t)) != ERROR_SUCCESS)
{
RegCloseKey((HKEY)key);
log.error("Failed to SET the registry value. Name : "+ std::string(valueName.begin(), valueName.end())+" Value : "+ std::string(valueToSet.begin(), valueToSet.end()));
RegCloseKey(key);
return false;
}
log.info("Successfully SET the registry value. Name : " + std::string(valueName.begin(), valueName.end()) + " Value : " + std::string(valueToSet.begin(), valueToSet.end()));
RegCloseKey(key);
return true;
}
return false;
}
And, this is the calling method.
bool setServiceAsClient()
{
if (setRegValue(L"ServiceType", L"Client"))
return true;
return false;
}
Its setting the registry name as S
And, value as C
When a wide-char string is seen as a 1-char string, that's a symptom that you're providing a wide-char string where a multi-byte string is expected.
Indeed we see the error here: (LPCSTR)valueName.c_str() (where valueName is a std::wstring).
LPCSTR is const char *, whereas wstring::c_str() returns const wchar_t *.
So L"ServiceType" is seen as "S\0e\0r\0v\0i\0c\0e\0T\0y\0p\0e\0", which becomes simply "S"
There are 2 solutions possible:
Use std::string instead of std::wstring (and remove the L from strings like L"ServiceType"). This solution is not recommended, since the Win32 API internally is Unicode.
Change project settings from Multi-byte to Unicode Character Set and remove the casting to LPCSTR (if you do need to cast, use LPTSTR instead, which always matches project character set settings).
See Working with Strings - Win32 API for more details.

Set Registry Value to a Wide Character String (WCHAR) in C++

I'm trying to add a wide character string to registry in C++. The problem is that the RegSetValueEx() function does not support wide chars, it only supports BYTE type (BYTE = unsigned char).
WCHAR myPath[] = "C:\\éâäà\\éâäà.exe"
RegSetValueExA(HKEY_CURRENT_USER, "MyProgram", 0, REG_SZ, myPath, sizeof(myPath)); // error: cannot convert argument 5 from WCHAR* to BYTE*
And please don't tell me I should convert WCHAR to BYTE because characters such as é and â can't be stored as 8 bit characters.
I'm sure this is possible because I tried opening regedit and adding a new key with value C:\\éâäà\\éâäà.exe and it worked. I wonder how other programs can add themselves to startup on a Russian or Chinese computer.
Is there another way to do so? Or is there a way to format wide character path using wildcards?
Edit: The Unicode version of the function RegSetValueExW() only changes the type of the second argument.
You are calling RegSetValueExA() when you should be calling RegSetValueExW() instead. But in either case, RegSetValueEx() writes bytes, not characters, that is why the lpData parameter is declared as BYTE*. Simply type-cast your character array. The REG_SZ value in the dwType parameter will let RegSetValueEx() know that the bytes represent a Unicode string. And make sure to include the null terminator in the value that you pass to the cbData parameter, per the documentation:
cbSize [in]
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.
For example:
WCHAR myPath[] = L"C:\\éâäà\\éâäà.exe";
RegSetValueExW(HKEY_CURRENT_USER, L"MyProgram", 0, REG_SZ, (LPBYTE)myPath, sizeof(myPath));
Or:
LPCWSTR myPath = L"C:\\éâäà\\éâäà.exe";
RegSetValueExW(HKEY_CURRENT_USER, L"MyProgram", 0, REG_SZ, (LPCBYTE)myPath, (lstrlenW(myPath) + 1) * sizeof(WCHAR));
That being said, you should not be writing values to the root of HKEY_CURRENT_USER itself. You should be writing to a subkey instead, eg:
WCHAR myPath[] = L"C:\\éâäà\\éâäà.exe";
if (RegCreateKeyEx(HKEY_CURRENT_USER, L"Software\\MyProgram", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL) == 0)
{
RegSetValueExW(hKey, L"MyValue", 0, REG_SZ, (LPBYTE)myPath, sizeof(myPath));
RegCloseKey(hKey);
}
It seems to me you're trying to use the narrow/non-wide-char version of that function, which will only support ASCII. How about trying RegSetValueExW? Maybe you should also look up how the Windows API tries to supports ASCII and UNICODE as transparently as possible.
Edit: The Unicode version of the function RegSetValueExW() only changes the type of the second argument.
No it does not.
REG_SZ: A null-terminated string. This will be either a Unicode or an ANSI string, depending on whether you use the Unicode or ANSI functions.
From here:
https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types

Why obtained MachineGuid looks not alike a GUID but like Korean?

I created a simple function:
std::wstring GetRegKey(const std::string& location, const std::string& name){
const int valueLength = 10240;
auto platformFlag = KEY_WOW64_64KEY;
HKEY key;
TCHAR value[valueLength];
DWORD bufLen = valueLength*sizeof(TCHAR);
long ret;
ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, location.c_str(), 0, KEY_READ | platformFlag, &key);
if( ret != ERROR_SUCCESS ){
return std::wstring();
}
ret = RegQueryValueExA(key, name.c_str(), NULL, NULL, (LPBYTE) value, &bufLen);
RegCloseKey(key);
if ( (ret != ERROR_SUCCESS) || (bufLen > valueLength*sizeof(TCHAR)) ){
return std::wstring();
}
std::wstring stringValue(value, (size_t)bufLen - 1);
size_t i = stringValue.length();
while( i > 0 && stringValue[i-1] == '\0' ){
--i;
}
return stringValue;
}
And I call it like auto result = GetRegKey("SOFTWARE\\Microsoft\\Cryptography", "MachineGuid");
yet string looks like
㤴ㄷ㤵戰㌭㉣ⴱ㔴㍥㤭慣ⴹ㍥摢㘵〴㉡ㄵ\0009ca9-e3bd5640a251
not like RegEdit
4971590b-3c21-45e3-9ca9-e3bd5640a251
So I wonder what shall be done to get a correct representation of MachineGuid in C++?
RegQueryValueExA is an ANSI wrapper around the Unicode version since Windows NT. When building on a Unicode version of Windows, it not only converts the the lpValueName to a LPCWSTR, but it will also convert the lpData retrieved from the registry to an LPWSTR before returning.
MSDN has the following to say:
If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, and
the ANSI version of this function is used (either by explicitly
calling RegQueryValueExA or by not defining UNICODE before including
the Windows.h file), this function converts the stored Unicode string
to an ANSI string before copying it to the buffer pointed to by
lpData.
Your problem is that you are populating the lpData, which holds TCHARs (WCHAR on Unicode versions of Windows) with an ANSI string.
The garbled string that you see is a result of 2 ANSI chars being used to populate a single wchar_t. That explains the Asian characters. The portion that looks like the end of the GUID is because the print function blew past the terminating null since it was only one byte and began printing what is probably a portion of the buffer that was used by RegQueryValueExA before converting to ANSI.
To solve the problem, either stick entirely to Unicode, or to ANSI (if you are brave enough to continue using ANSI in the year 2014), or be very careful about your conversions. I would change GetRegKey to accept wstrings and use RegQueryValueExW instead, but that is a matter of preference and what sort of code you plan on using this in.
(Also, I would recommend you have someone review this code since there are a number of oddities in the error checking, and a hard coded buffer size.)

convert std::string to const BYTE* for RegSetValueEx()

I have a function that gets a std::string. That function calls
RegSetValueEx
the 5th parameter is the value of the registry value and expects a variable of type const BYTE*.
So I have to convert the std::string to const BYTE* and also give the length of the resulting array as the 6th parameter.
I have found a way to do it, but it feels ugly and I don't really understand what is going on. Here is a slimmed down version of that function:
void function(const std::string& newValue)
{
HKEY keyHandle;
if(RegOpenKeyEx(HKEY_CLASSES_ROOT, TEXT("some key"),0,KEY_ALL_ACCESS,&keyHandle) == ERROR_SUCCESS)
{
std::wstring wNewValue;
wNewValue.assign(newValue.begin(),newValue.end());
if (RegSetValueEx(keyHandle, TEXT("some value"), NULL, REG_SZ, (const BYTE*)(LPCTSTR)(wNewValue.c_str()), wNewValue.size()*2)==ERROR_SUCCESS)
{
//do something
}
RegCloseKey(keyHandle);
}
}
As you can see, i first make a wide string (UNICODE is defined), then use a double cast, and for the length i have to do *2, else it will only set half of the input string.
Is this form of cast the normal/best way to do it?
Why the * 2, what would be a better way?
void function(const std::string& newValue)
{
HKEY keyHandle;
if(RegOpenKeyEx(HKEY_CLASSES_ROOT, TEXT("some key"),0,KEY_ALL_ACCESS,&keyHandle) == ERROR_SUCCESS)
{
if (RegSetValueExA(keyHandle, "some value", NULL, REG_SZ, (const BYTE*)newValue.c_str(), newValue.size() + 1)==ERROR_SUCCESS)
{
//do something
}
RegCloseKey(keyHandle);
}
}
I removed the part where you convert your string to a wstring, instead you'll be using the ANSI version of RegSetValueEx explicitly.
quote from RegSetValueEx remarks in MSDN:
If dwType is the REG_SZ, REG_MULTI_SZ,
or REG_EXPAND_SZ type and the ANSI
version of this function is used
(either by explicitly calling
RegSetValueExA or by not defining
UNICODE before including the Windows.h
file), the data pointed to by the
lpData parameter must be an ANSI
character string. The string is
converted to Unicode before it is
stored in the registry.
Also note that the cbData parameter should include the size of the null termination aswell.
The * 2 is because RegSetValueEx wants to know the number of bytes to write und each char (wchar_t) in a wstring is two bytes wide. So the resulting byte-array has twice the size!
Shouldn't it be wNewValue.size()*2+2 ? The +2 for the null character?
MSDN says: 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.
You could also copy the unicode string into a byte array:
LPWSTR pData = L"SampleGrabber";
int dwSize = wcslen(pData)*sizeof(TCHAR);
BYTE slump[256];
memset((void*) slump, 0, 256*sizeof(BYTE));
memcpy((void*) slump, (const void *) pData, dwSize*sizeof(BYTE));
globit = RegSetValueEx(hKey, NULL, 0, REG_SZ, (const BYTE*) slump, dwSize);
If you had the misfortune to be writing code for a WINCE 5.0 device and it lacked part of the regedit API.