Getting the value of OpenSavePidlMRU in the registry - c++

I'm trying to get the last opened directory by an open file dialog, and it seems that we can get it by first retrieving the key's name that contains the path using the first BYTE of the MRUListEx key, then the path can be obtained by reading the value of this key's name.
MRUListEx key can be find at: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU.
The problem is that I don't know how to properly fill the "SHITEMID" structure. (it doesn't let me access the index, and it throws a Memory access violation). I don't even know if the code below is valid in any points.
Sorry, the code is very dirty for the moment but I'll revamps it when I finally find what causes these errors.
void MyClass::OnDocumentSave(bool showSaveDlg)
{
// Do something that is not relevant here...
try {
HKEY hKey = NULL;
std::wstring regPath = LR"(Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU\)";
LSTATUS statusCode = RegOpenKeyExW(HKEY_CURRENT_USER, regPath.data(), 0, KEY_QUERY_VALUE, &hKey);
if (statusCode != ERROR_SUCCESS)
throw std::exception(std::string("Unable to open the specified registry key, sys err code: ") + std::to_string(statusCode));
BYTE data[MAX_PATH];
DWORD bufferSize = MAX_PATH;
statusCode = RegGetValueW(hKey, L"", L"MRUListEx", RRF_RT_REG_BINARY, NULL, &data, &bufferSize);
if (statusCode != ERROR_SUCCESS)
throw std::runtime_error(std::string("Failed at RegGetValue() Sys error code: ") + std::to_string(statusCode));
// Please note that the buffer has intentionally a fixed size here and everything is
// simplified for readability, but it uses dynamic memory allocation in the real code to
// handle errors such as ERROR_MORE_DATA
BYTE* pathData[512];
bufferSize = 512;
DWORD type = 0; // In case it matters, the returned value is 3
statusCode = RegGetValueW(hKey, L"", std::to_wstring(data[0]).c_str(), RRF_RT_REG_BINARY, &type, &pathData, &bufferSize);
if (statusCode != ERROR_SUCCESS)
throw std::runtime_error(std::string("Failed at RegGetValue() Sys error code: ") + std::to_string(statusCode));
// I don't know how to fill this structure, the documentation is very minimal,
// and I don't understand it.
int offset = sizeof(APPNAME);
SHITEMID shellIDList[2]{
// Throw a memory access violation at 0x*****, the debugger can't seems to
// get anything in pathData.
{ sizeof(USHORT) + sizeof(pathData), *pathData[0 + offset] },
{ 0, 0 } };
ITEMIDLIST idl{ shellIDList[0] };
// This is supposed give me the last path that was opened by a File Picker.
SHGetPathFromIDListW(&idl, initialDir.data());
}
catch (std::exception& e) {
// Silently set the initial directory to a hard-coded path instead of getting the registry value.
}
}

You don't seem to understand how types and simple arrays work! BYTE* pathData[512]; is not a 512 byte buffer. Use BYTE pathData[512];.
After reading into pathData, call SHGetPathFromIDList((PCIDLIST_ABSOLUTE) pathData, ...);.
That being said, that ComDlg32 key is undocumented and I don't think it stores a pidl so even if your code is corrected it is not going to work.
EnumMRUListW is a documented function you can call but it is not going to help you decode the data.
It looks to me like the location might be prefixed with the name of the .exe so as a minimum you would have to skip (lstrlenW(pathData)+1)*2 bytes before you find the real data...

If you're going for undocumented stuff, here is a code that can dump an MRU list like the LastVisitedPidlMRU one:
// the Shell object that can read MRU lists
static GUID CLSID_MruLongList = { 0x53bd6b4e,0x3780,0x4693,{0xaf,0xc3,0x71,0x61,0xc2,0xf3,0xee,0x9c} };
typedef enum MRULISTF
{
MRULISTF_USE_MEMCMP = 0x0,
MRULISTF_USE_STRCMPIW = 0x1,
MRULISTF_USE_STRCMPW = 0x2,
MRULISTF_USE_ILISEQUAL = 0x3,
};
typedef int (__stdcall *MRUDATALISTCOMPARE)(const BYTE*, const BYTE*, int);
MIDL_INTERFACE("00000000-0000-0000-0000-000000000000") // unknown guid but we don't care
IMruDataCompare : public IUnknown
{
public:
virtual HRESULT CompareItems(const BYTE*, int, const BYTE*, int) = 0;
};
MIDL_INTERFACE("d2c22919-91f5-4284-8807-58a2d64e561c")
IMruDataList2 : public IUnknown
{
public:
virtual HRESULT InitData(UINT uMax, MRULISTF flags, HKEY hKey, LPCWSTR pszSubKey, MRUDATALISTCOMPARE pfnCompare) = 0;
virtual HRESULT AddData(const BYTE* pData, DWORD cbData, DWORD* pdwSlot) = 0;
virtual HRESULT InsertData(const BYTE*, DWORD cbData, int* piIndex, DWORD* pdwSlot) = 0;
virtual HRESULT FindData(const BYTE* pData, DWORD cbData, int* piIndex) = 0;
virtual HRESULT GetData(int iIndex, BYTE* pData, DWORD cbData) = 0;
virtual HRESULT QueryInfo(int iIndex, DWORD* pdwSlot, DWORD* pcbData) = 0;
virtual HRESULT Delete(int iIndex) = 0;
virtual HRESULT InitData2(UINT uMax, MRULISTF flags, HKEY hKey, LPCWSTR pszSubKey, IMruDataCompare* pfnCompare) = 0;
};
int main()
{
CoInitialize(NULL);
{
CComPtr<IMruDataList2> mru;
if (SUCCEEDED(mru.CoCreateInstance(CLSID_MruLongList)))
{
const int max = 100; // get max 100 entries
mru->InitData(max, MRULISTF_USE_MEMCMP, HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\LastVisitedPidlMRU\\", nullptr);
for (auto i = 0; i < max; i++)
{
DWORD slot;
DWORD size;
// get size
if (FAILED(mru->QueryInfo(i, &slot, &size)))
continue;
// get data
// note beginning is a LPWSTR containing exe data
auto data = (LPBYTE)_alloca(size);
if (FAILED(mru->GetData(i, data, size)))
continue;
// the rest is a PIDL
auto pidl = (LPCITEMIDLIST)(data + (lstrlen((LPWSTR)data) + 1) * 2);
// get the shell item
CComPtr<IShellItem> item;
if (SUCCEEDED(SHCreateItemFromIDList(pidl, IID_PPV_ARGS(&item))))
{
// get its path
CComHeapPtr<WCHAR> path;
item->GetDisplayName(SIGDN::SIGDN_DESKTOPABSOLUTEPARSING, &path);
wprintf(L"Executable: %s LastVisited: %s\n", data, path);
}
}
}
}
CoUninitialize();
}
PS: I'm using ATL's smart classes

I finally figured out how to create an ITEMIDLIST structure, and it all works well.
Explanations:
After some test, I've concluded that this value is not necessarily updated. The application that uses a file picker needs to write to the registry to reflect changes.
For example, VS Community 2022 / VS Code updates this value, but the new Microsoft Notepad does not (not sure for the old one). Apparently, .NET applications seem to be updated automatically, though.
First, we obviously must open the key at Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU.
The stored data value we need is on one of the values, which has a number as a name.
The first byte of the MRUListEx value lies the last path.
HKEY hKey = NULL;
std::wstring regMRUPath = LR"(Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU\)";
LSTATUS statusCode = RegOpenKeyExW(HKEY_CURRENT_USER, regMRUPath.data(), 0, KEY_QUERY_VALUE, &hKey);
if (statusCode != ERROR_SUCCESS)
// Handle error
BYTE data[MAX_PATH];
DWORD bufferSize = MAX_PATH;
statusCode = RegGetValueW(hKey, L"", L"MRUListEx", RRF_RT_REG_BINARY, NULL, &data, &bufferSize);
if (statusCode != ERROR_SUCCESS)
// Handle error
std::wstring keyName = std::to_wstring(data[0]);
Now that we have the value, we can get its data.
DWORD reqBuffSize = 0;
statusCode = RegQueryValueExW(hKey, keyName.data(), NULL, NULL, NULL, &reqBuffSize);
if (statusCode != ERROR_SUCCESS)
// maybe the specified key is invalid or missing.
BYTE pathData[reqBuffSize];
statusCode = RegGetValueW(hKey, L"", keyName.data(), RRF_RT_REG_BINARY, NULL, &pathData, &reqBuffSize);
if (statusCode != ERROR_SUCCESS)
// maybe the specified key is invalid or missing.
We have another problem: The path is not directly accessible. The executable name is prefixed from the real path, so we must remove it.
Actually, it's very easy. The delimiter between the executable name and the real path is \0\0\0.
const size_t offset = (lstrlenW(reinterpret_cast<LPCWSTR>(pathData)) * sizeof(wchar_t))
+ (2 * sizeof(wchar_t));
The obtained value is not human-readable, but it actually stores a PIDL, so we can get it with SHGetPathFromIDList. The ITEMIDList is not very well documented on the Microsoft's website, but here's a way to create one of these things:
IMalloc* pMalloc;
if (FAILED(SHGetMalloc(&pMalloc)))
// handle error, failed to get a pointer to the IMalloc interface.
PIDLIST_ABSOLUTE pIdl = (PIDLIST_ABSOLUTE)pMalloc->Alloc((reqBuffSize - offset) + 1);
pIdl->mkid.cb = (sizeof(USHORT) + (reqBuffSize - offset));
memcpy_s(pIdl->mkid.abID, (reqBuffSize - offset) + 1, pathData + offset, (reqBuffSize - offset) + 1);
PIDLIST_ABSOLUTE pNext = ILGetNext(pIdl);
if (!pNext)
// handle error, failed to get a pointer to the next item
pNext->mkid.cb = 0;
initialDir.reserve(MAX_PATH * 2);
The ITEMIDList structure has two members: cb and abID. cb is the size of a USHORT + the size of the data, and abID is a pointer to the data itself.
The last ITEMIDList struct must have cb set to zero to mark the end. ILGetNext gives us a pointer to the next element.
// Finally, return the path!
std::wstring retValue;
retValue.reserve(MAX_PATH);
if (FALSE == SHGetPathFromIDList(pIdl, initialDir.data())) {
// oh no! free the allocated memory and throw an exception
}
pMalloc->Free(pIdl);
It's not as hard as it seems to be at the beginning, but if you want to get this value for use with a file picker, it's recommended to use COM instead since it's easier and less error-prone. Thank tou all for tour response and have a good day!
If you want to get the path from a specified extension, see the OpenSavePidlMRU key. It works the same, but it requires some little adjustments.

Related

C++ Win32 Getting a registry key

const char* Launcher::GetProjectName()
{
PVOID data;
LPDWORD pcbData;
HKEY OpenResult;
LSTATUS status = RegOpenKeyEx(HKEY_CURRENT_USER, L"Environment", NULL, KEY_READ, &OpenResult);
if (status != ERROR_SUCCESS)
{
LOG(ERROR) << "Could not found registry key 'Environment'";
}
else
{
LPCWSTR project_name_key = L"project_name";
DWORD data_type;
WCHAR value[255];
PVOID pvData = value;
DWORD size = sizeof(value);
status = RegGetValue(OpenResult, NULL, project_name_key, RRF_RT_ANY, &data_type, pvData, &size);
if (status != ERROR_SUCCESS)
{
LOG(ERROR) << "Could not found registry value 'project_name'";
}
else
{
switch (data_type)
{
case REG_DWORD:
wprintf(L"Value data: %x\n", *(DWORD*)pvData);
break;
case REG_SZ:
wprintf(L"Value data: %s\n", (PWSTR)pvData);
}
}
RegCloseKey(OpenResult);
}
return 0;
}
I'm trying to get this registry key I made named "project_name" and return it as a char* or a std::string. However, I'm getting garbage data in pvData. What am I doing wrong here? I've seen some other stackoverflow posts and tried to replicate their setup as well but nothing is working. My entire goal here is to retrieve an environment variable using the windows registry.
I think there's a mismatch of ANSI and Unicode expectations. Your code is likely compiling for ANSI, but you're passing a wide-char buffer. Let's just explicitly call the A version of the Registry functions so you can stay in the ANSI string space.
Instead of this:
WCHAR value[255];
PVOID pvData = value;
DWORD size = sizeof(value);
status = RegGetValue(OpenResult, NULL, project_name_key, RRF_RT_ANY, &data_type, pvData, &size);
Use this:
char value[255];
DWORD size = sizeof(value);
status = RegGetValueA(OpenResult, NULL, project_name_key, RRF_RT_ANY, &data_type, value, &size);
Then, return a std::string as follows:
Declare your function to return a string, not a pointer:
const std::string Launcher::GetProjectName()
Then simply return value as a string;
return std::string(value);

wxWidgets iterate through registry entries

I am making a simple Application to collect and represent data over bluetooth serial COM port and I don't want to hard-code the COM ports. So I want to enumerate the COM ports by looking up HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM. However I'm fairly new to both wxWidgets and win32 registry terminology(I still don't understand what's supposed to be the "name" and "value" in wxWidgets documentation, they don't work as expected). What I want is to iterate through all the COMM ports and add them to a drop down list. This doesn't work:
wxRegKey regKey(wxRegKey::HKLM,"HARDWARE\\DEVICEMAP\\SERIALCOMM");
size_t subkeys;
long k = 1;
regKey.Open(wxRegKey::Read);
regKey.GetKeyInfo(&subkeys, NULL, NULL, NULL);
wxString key_name;
regKey.GetFirstValue(key_name, k);
m_drop_down->Append(key_name);
for (int i = 0; i < subkeys; i++) {
regKey.GetNextValue(key_name, k);
m_drop_down->Append(key_name);
}
regKey.Close();
It only appends one \Device\BthModem2 to the dropdown list. I would be grateful if someone cleared the terminologies up and tell me how I should go about making it work. For reference, here's what that registry entry looks to me, I want COM3, COM4, COM5, COM6 appended to the drop down list:
If you want to get the "value" (the thing in the right column), you can declare a function like this:
wxString GetStringData(const wxRegKey& regKey, const wxString& valueName)
{
wxRegKey::ValueType vtype = regKey.GetValueType(valueName);
wxString data;
if ( vtype == wxRegKey::Type_String )
{
regKey.QueryValue(valueName, data, true);
}
return data;
}
To get the complete list of them from the registry entry, you can iterate over the values in the registry entry like this:
while(regKey.GetNextValue(key_name, k)) {
wxString data = GetStringData(regKey,valueName);
if ( !data.IsEmpty() )
{
m_drop_down->Append(key_name);
}
}
GetNextValue will return false when there are no more values to be fetched breaking out of the loop.
This isn't really complete since this won't distinguish between a registry value whose type isn't string and a registy value whose type is a string but with empty data, but it should be clear how to modify the function and/or the iteration if it's necessary to make that distinction.
According to MSDN:Enumerating Registry Subkeys and RegGetValue, RegGetValue retrieves the type and data for the specified registry value.
Code:
int main()
{
long ret;
unsigned long reg_index = 0;
DWORD dwValSize = MAX_VALUE_NAME;
DWORD dwDataSize = MAX_VALUE_NAME;
LPTSTR lpValname = new TCHAR[MAX_VALUE_NAME]{};
LPTSTR lpDataname = new TCHAR[MAX_VALUE_NAME]{};
HKEY hkey;
int result = RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("SOFTWARE\\Microsoft"), 0, KEY_READ, &hkey);
if (result == ERROR_SUCCESS) {
while ((ret = RegEnumValue(hkey, reg_index++, lpValname, &dwValSize, NULL, NULL, NULL, NULL)) != ERROR_NO_MORE_ITEMS)
{
if ((ret == ERROR_SUCCESS) || (ret == ERROR_MORE_DATA)) {
_tprintf(TEXT("(%d) %s"), reg_index, lpValname);
DWORD type = 0;
if (RegGetValue(hkey, 0,lpValname, RRF_RT_ANY, &type, lpDataname, &dwDataSize) == ERROR_SUCCESS)
{
_tprintf(TEXT("(Value data %s)\n"), lpDataname);
}
}
dwValSize = MAX_VALUE_NAME;
dwDataSize = MAX_VALUE_NAME;
ZeroMemory(lpDataname,sizeof(TCHAR)* dwDataSize);
ZeroMemory(lpValname,sizeof(TCHAR)* dwValSize);
}
RegCloseKey(hkey);
}
return TRUE;
}

Getting Connection-State from Bluetooth Low Energy Device from Device Manager

i'm developing on a Bluetooth Low Energy Device and i need to see in code if the device is connected or not.
First thing i noticed was that there is in the Devicemanager a Attribute "Verbunden"-> English: Connected and it says true or false if my device is connected or not. So i need to read that Attribute in my program.
What i have tried till now:
Getting all Devices with SetupDiGetClassDevs
Getting the FriendlyName with SetupDiGetDeviceRegistryProperty
Searching for my Device with the name.
That works.
Now i wanted to get that Connected-Attribute but i didn't find out what i have to use at SetupDiGetDeviceRegistryProperty.
SetupDiGetDeviceRegistryProperty is described here https://msdn.microsoft.com/en-us/library/windows/hardware/ff551967(v=vs.85).aspx
Maybe someone knows what is the right value for Property.
My Code:
int get_device_info( void )
{
HDEVINFO hDevInfo;
SP_DEVINFO_DATA DeviceInfoData;
DWORD i;
FILE * devices = fopen("devices.txt", "a+");
GUID AGuid;
//GUID can be constructed from "{xxx....}" string using CLSID
CLSIDFromString(TEXT(TO_SEARCH_DEVICE_UUID), &AGuid);
GUID BluetoothInterfaceGUID = AGuid;
// Create a HDEVINFO with all present devices.
hDevInfo = SetupDiGetClassDevs(&BluetoothInterfaceGUID,
0, // Enumerator
0,
DIGCF_ALLCLASSES | DIGCF_PRESENT);
if (hDevInfo == INVALID_HANDLE_VALUE)
{
// Insert error handling here.
return 1;
}
// Enumerate through all devices in Set.
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
for (i=0;SetupDiEnumDeviceInfo(hDevInfo,i,
&DeviceInfoData);i++)
{
DWORD DataT;
LPTSTR buffer = NULL;
DWORD buffersize = 0;
//
// Call function with null to begin with,
// then use the returned buffer size (doubled)
// to Alloc the buffer. Keep calling until
// success or an unknown failure.
//
// Double the returned buffersize to correct
// for underlying legacy CM functions that
// return an incorrect buffersize value on
// DBCS/MBCS systems.
//
while (!SetupDiGetDeviceRegistryProperty(
hDevInfo,
&DeviceInfoData,
SPDRP_FRIENDLYNAME,
//SPDRP_DEVICEDESC,
//SPDRP_CAPABILITIES,
&DataT,
(PBYTE)buffer,
buffersize,
&buffersize))
{
if (GetLastError() ==
ERROR_INSUFFICIENT_BUFFER)
{
// Change the buffer size.
if (buffer) LocalFree(buffer);
// Double the size to avoid problems on
// W2k MBCS systems per KB 888609.
buffer = (wchar_t *)LocalAlloc(LPTR,buffersize * 2);
}
else
{
// Insert error handling here.
break;
}
}
if(buffer)
{
if( strcmp("Name of Device",AnsiString(buffer).c_str())==0)
{
fprintf(devices,"Result:[%s]",AnsiString(buffer).c_str());
if (buffer) LocalFree(buffer);
}
}
}
if ( GetLastError()!=NO_ERROR &&
GetLastError()!=ERROR_NO_MORE_ITEMS )
{
// Insert error handling here.
return 1;
}
// Cleanup
SetupDiDestroyDeviceInfoList(hDevInfo);
fclose(devices);
return 0;
}
Instead of using SetupDiEnumDeviceInfo, you would try:
1. using SetupDiEnumDeviceInterfaces
2. using SetupDiGetDeviceInterfaceProperty
3. using SetupDiGetDeviceInterfacePropertyKeys to get a list of all Property Keys available for the interface
4. using SetupDiGetDeviceProperty and/or SetupDiGetDeviceRegistryProperty
Instead of using SPDRP_XXX constants, you would use DEVPROP, as defined in 'devpkey.h' ...
Below are a few examples taken from the log of a test prog I wrote to discover the whole thing:
DEVPROPNAME: DEVPKEY_DeviceInterface_Bluetooth_DeviceAddress
DEVPROPGUID: {2BD67D8B-8BEB-48D5-87E0-6CDA3428040A}
DEVPROPPID: 1
DEVPROPTYPE: DEVPROP_TYPE_STRING
Value: c026df001017
DEVPROPNAME: DEVPKEY_Device_Children
DEVPROPGUID: {4340A6C5-93FA-4706-972C-7B648008A5A7}
DEVPROPPID: 9
DEVPROPTYPE: DEVPROP_TYPE_STRING_LIST
Value:
BTHLEDevice\{00001800-0000-1000-8000-00805f9b34fb}_c026df001017\8&2fd07168&1&0001
BTHLEDevice\{00001801-0000-1000-8000-00805f9b34fb}_c026df001017\8&2fd07168&1&0008
BTHLEDevice\{00001809-0000-1000-8000-00805f9b34fb}_c026df001017\8&2fd07168&1&000c
BTHLEDevice\{0000180f-0000-1000-8000-00805f9b34fb}_c026df001017\8&2fd07168&1&0010
BTHLEDevice\{0000180a-0000-1000-8000-00805f9b34fb}_c026df001017\8&2fd07168&1&0014
BTHLEDevice\{00001523-1212-efde-1523-785feabcd123}_c026df001017\8&2fd07168&1&0019
On a second subject, you are 'working' on the 'device' itself ( SetupDiGetClassDevs(&BluetoothInterfaceGUID...) [and then working on the \BTHLE\ tree in Registry].
After listing all GattServices of this device and getting their uuids, you could restart that iteration on the device_guid itself SetupDiGetClassDevs(&GattServiceGUID...) [and then working on the \BTHLEDevice\ tree in Registry].
Now, to answer your question, I'm still searching myself :) But I'm not really sure:
1) that it is a working (dynamic) information to know the connection state
2) that it is a 'Property' you can access by the above methods
I have found out a solution.
GUID AGuid;
//GUID can be constructed from "{xxx....}" string using CLSID
CLSIDFromString(TEXT(TO_SEARCH_DEVICE_UUID), &AGuid);
GUID BluetoothInterfaceGUID = AGuid;
// Create a HDEVINFO with all present devices.
hDevInfo = SetupDiGetClassDevs(&BluetoothInterfaceGUID,
0, // Enumerator
0,
DIGCF_ALLCLASSES | DIGCF_PRESENT);//DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);//DIGCF_ALLCLASSES | DIGCF_PRESENT);
if (hDevInfo == INVALID_HANDLE_VALUE)
{
// Insert error handling here.
return 1;
}
// Enumerate through all devices in Set.
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
for (i=0;SetupDiEnumDeviceInfo(hDevInfo,i,
&DeviceInfoData);i++)
{
DWORD DataT;
LPTSTR buffer = NULL;
LPTSTR buffer1 = NULL;
DWORD buffersize = 0;
while (!SetupDiGetDeviceRegistryProperty( // Get Name
hDevInfo,
&DeviceInfoData,
SPDRP_FRIENDLYNAME,
&DataT,
(PBYTE)buffer,
buffersize,
&buffersize))
{
if (GetLastError() ==
ERROR_INSUFFICIENT_BUFFER)
{
// Change the buffer size.
if (buffer) LocalFree(buffer);
// Double the size to avoid problems on
// W2k MBCS systems per KB 888609.
buffer = (wchar_t *)LocalAlloc(LPTR,buffersize * 2);
}
else
{
// Insert error handling here.
break;
}
}
{
if(strcmp("Your Device",AnsiString(buffer).c_str())==0) //Found your device
{
//########
DEVPROPTYPE ulPropertyType;
DWORD dwSize;
ULONG devst;
// memset(devst,0,sizeof(devst));
bool err = SetupDiGetDeviceProperty( //Checking Connection State
hDevInfo,
&DeviceInfoData,
&DEVPKEY_Device_DevNodeStatus, //Connected(0x02000000)
&ulPropertyType,
(BYTE *) &devst,
sizeof(devst),
&dwSize,
0);
DWORD error;
error = GetLastError();
if (devst &0x02000000) {
//"Status: Getrennt "
}
else
{
//"Status: Verbunden"
}
Hope this snippet helps.

How to call DeviceIoControl to retrieve the amount of memory it needs?

I'm trying to call DeviceIoControl(IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS) API, as shown here, but I need it to first "tell me" how much memory it needs (unlike the code I linked to.)
So I call it as such:
//First determine how much data do we need?
BYTE dummyBuff[1];
DWORD bytesReturned = 0;
if(!::DeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize,
dummyBuff, sizeof(dummyBuff), &bytesReturned, NULL))
{
//Check last error
int nError = ::GetLastError();
if(nOSError == ERROR_INSUFFICIENT_BUFFER ||
nOSError == ERROR_MORE_DATA)
{
//Alloc memory from 'bytesReturned' ...
}
}
but it always returns error code 87, or ERROR_INVALID_PARAMETER and my bytesReturned is always 0.
So what am I doing wrong?
The instructions for getting all disk volume extents are documented under the VOLUME_DISK_EXTENTS structure:
When the number of extents returned is greater than one (1), the error code ERROR_MORE_DATA is returned. You should call DeviceIoControl again, allocating enough buffer space based on the value of NumberOfDiskExtents after the first DeviceIoControl call.
The behavior, if you pass an output buffer, that is smaller than sizeof(VOLUME_DISK_EXTENTS) is also documented at IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS control code:
If the output buffer is less than sizeof(VOLUME_DISK_EXTENTS), the call fails, GetLastError returns ERROR_INSUFFICIENT_BUFFER, and lpBytesReturned is 0 (zero).
While this explains the returned value in lpBytesReturned, it doesn't explain the error code 87 (ERROR_INVALID_PARAMETER)1).
The following code will return the disk extents for all volumes:
VOLUME_DISK_EXTENTS vde = { 0 };
DWORD bytesReturned = 0;
if ( !::DeviceIoControl( hDevice, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0,
(void*)&vde, sizeof(vde), &bytesReturned, NULL ) )
{
// Check last error
int nError = ::GetLastError();
if ( nError != ERROR_MORE_DATA )
{
// Unexpected error -> error out
throw std::runtime_error( "DeviceIoControl() failed." );
}
size_t size = offsetof( VOLUME_DISK_EXTENTS, Extents[vde.NumberOfDiskExtents] );
std::vector<BYTE> buffer( size );
if ( !::DeviceIoControl( hDevice, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0,
(void*)buffer.data(), size, &bytesReturned, NULL ) )
{
// Unexpected error -> error out
throw std::runtime_error( "DeviceIoControl() failed." );
}
// At this point we have a fully populated VOLUME_DISK_EXTENTS structure
const VOLUME_DISK_EXTENTS& result =
*reinterpret_cast<const VOLUME_DISK_EXTENTS*>( buffer.data() );
}
else
{
// Call succeeded; vde is populated with single disk extent.
}
Additional references:
Why do some structures end with an array of size 1?
offsetof Macro
1) At a guess I would assume, that BYTE[1] begins at a memory address, that is not sufficiently aligned for the alignment requirements of VOLUME_DISK_EXTENTS.
Following #IInspectable's advice, here's what I came up with for a more general case:
BYTE* DeviceIoControl_Dynamic(HANDLE hDevice, DWORD dwIoControlCode, DWORD dwszCbInitialSuggested, LPVOID lpInBuffer, DWORD nInBufferSize, DWORD* pncbOutDataSz)
{
//Calls DeviceIoControl() API by pre-allocating buffer internally
//'dwIoControlCode' = control code, see DeviceIoControl() API
//'dwszCbInitialSuggested' = suggested initial size of the buffer in BYTEs, must be set depending on the description of 'dwIoControlCode'
//'lpInBuffer' = input buffer, see DeviceIoControl() API
//'nInBufferSize' = size of 'lpInBuffer', see DeviceIoControl() API
//'pncbOutDataSz' = if not NULL, receives the size of returned data in BYTEs
//RETURN:
// = Data obtained from DeviceIoControl() API -- must be removed with delete[]!
// = NULL if error -- check GetLastError() for info
BYTE* pData = NULL;
int nOSError = NO_ERROR;
DWORD ncbSzData = 0;
if((int)dwszCbInitialSuggested > 0)
{
//Initially go with suggested memory size
DWORD dwcbMemSz = dwszCbInitialSuggested;
//Try no more than 10 times
for(int t = 0; t < 10; t++)
{
//Reserve mem
ASSERT(!pData);
pData = new (std::nothrow) BYTE[dwcbMemSz];
if(!pData)
{
//Memory fault
nOSError = ERROR_NOT_ENOUGH_MEMORY;
break;
}
//And try calling with that size
DWORD bytesReturned = 0;
if(::DeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize,
pData, dwcbMemSz, &bytesReturned, NULL))
{
//Got it
ncbSzData = bytesReturned;
nOSError = NO_ERROR;
break;
}
//Check last error
nOSError = ::GetLastError();
//Knowing how badly Windows drivers are written, don't rely on the last error code!
//Alloc more memory (we'll just "wing it" on the amount)
dwcbMemSz += 1024;
//Free old mem
delete[] pData;
pData = NULL;
}
}
else
{
//Bad initial size
nOSError = ERROR_INVALID_MINALLOCSIZE;
}
if(pncbOutDataSz)
*pncbOutDataSz = ncbSzData;
::SetLastError(nOSError);
return pData;
}
and then to call it, say for IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS:
DWORD bytesReturned;
VOLUME_DISK_EXTENTS* p_vde = (VOLUME_DISK_EXTENTS*)DeviceIoControl_Dynamic(hDsk,
IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, sizeof(VOLUME_DISK_EXTENTS), NULL, NULL, &bytesReturned);
which can be later used as such:
//Ensure that driver returned the correct data
if(p_vde &&
offsetof(VOLUME_DISK_EXTENTS, Extents[p_vde->NumberOfDiskExtents]) <= bytesReturned)
{
//All good
for(int x = 0; x < p_vde->NumberOfDiskExtents; x++)
{
DWORD diskNumber = p_vde->Extents[x].DiskNumber;
//...
}
}
//Remember to free mem when not needed!
if(p_vde)
{
delete[] (BYTE*)p_vde;
p_vde = NULL;
}
You are getting error code ERROR_INVALID_PARAMETER when you have invalid parameter, like its name says. In your case it should be bad handle because all others looks fine, if we expect that dwIoControlCode argument is IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, lpInBuffer and nInBufferSize are ignored.
In insufficient buffer you will get another error code mentioned in above comments.
Lets check what is saying documentation:
DeviceIoControl can accept a handle to a specific device. For example, to open a handle to the logical drive A: with CreateFile, specify \.\a:. Alternatively, you can use the names \.\PhysicalDrive0, \.\PhysicalDrive1, and so on, to open handles to the physical drives on a system.
In other words, when you open handle with "C:\" instead of "\\.\c:" argument in CreateFile and use it in DeviceIoControl, the result is ERROR_INVALID_PARAMETER.

RegOpenKeyEx returns ERROR_NOACCESS

I am trying to read a registy key under windows 7 x64 using the following code:
static void ReadRegistryKey(HKEY hkey, TCHAR* path)
{
HKEY hkey2;
TCHAR value[MAX_PATH];
TCHAR data[4096];
const DWORD dataLength = 4096 * sizeof(TCHAR);
const DWORD valueLength = MAX_PATH+1;
DWORD returnval;
DWORD type = 0;
HLOCAL mem = LocalAlloc(LPTR, 260);
char * pc = (char*)mem;
pc++;
wchar_t* pwc = (wchar_t*)pc;
lstrcpy(pwc, path);
// Does key exist?
returnval = RegOpenKeyEx(hkey, pwc, 0 , KEY_READ | KEY_WOW64_64KEY, &hkey2);
if(returnval == ERROR_SUCCESS)
{
int i = 0;
while(returnval == ERROR_SUCCESS)
{
DWORD actualLength = dataLength;
DWORD actualValueLength = valueLength;
returnval = RegEnumValueW( hkey2,
i,
value,
&actualValueLength,
NULL,
&type,
(LPBYTE)data,
&actualLength
);
if(returnval == ERROR_NO_MORE_ITEMS)
{
_tprintf(_T("NO MORE KEYS FOUND in %s\n"), path);
break;
}
if(returnval == ERROR_SUCCESS)
{
// STUFF
}
}
}
}
When I use KEY_READ | KEY_WOW64_32KEY I get the values stored under the 32Bit registry but when I use the code above trying to read the "normal" 64bit registy I get the error code 0x3e6 (ERROR_NOACCESS)
The way i call the method:
ReadRegistryKey(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run");
What can I do to read the 64bit registry values?
Thanks
I think the allocation and pointer arithmetic of pwc is causing the problem. Pass in the path directly into the RegOpenKeyEx function.
It's also worth noting that the lstrcpy will cause a buffer overflow if path is longer than 260 bytes. Instead use StringCchCopy in Windows to give a string copy that will only copy up to the number of bytes available in the destination buffer.