I am trying to get the IDispatch * of an open explorer window using IShellWindows::FindWindowSW; however, I cannot seem to coax the method to return anything other than S_FALSE.
The code I am using is basically:
OleInitialize(nullptr);
CComPtr<IShellWindows> spWindows;
auto hr = spWindows.CoCreateInstance(CLSID_ShellWindows);
auto pidl = ILCreateFromPath(L"C:\\temp");
VARIANT vtLoc;
vtLoc.vt = VT_VARIANT | VT_BYREF;
vtLoc.pbVal = (BYTE *) pidl;
CComVariant vtEmpty;
long lhwnd;
CComPtr<IDispatch> spdisp;
hr = spWindows->FindWindowSW(&vtLoc, &vtEmpty,
SWC_EXPLORER, &lhwnd, SWFO_NEEDDISPATCH | SWFO_INCLUDEPENDING,
&spdisp);
Yes, I am sure there is an explorer window open with the location "C:\temp".
Slightly modifying the code from A big little program: Monitoring Internet Explorer and Explorer windows, part 1: Enumeration which enumerates over all registered windows and examines their locations (which is what I assume FindWindowSW does internally anyway) replicates the function. Which is basically what the answer by Victoria does.
bool ImageViewerMainWindow::GetFolderViewFromPath(const WCHAR * szPath, IFolderView2 ** ppfv) {
if( !m_spWindows ) return false;
if( !szPath ) return false;
if( !ppfv ) return false;
*ppfv = nullptr;
CComPtr<IUnknown> spunkEnum;
HRESULT hr = m_spWindows->_NewEnum(&spunkEnum);
if( S_OK != hr ) return false;
CComQIPtr<IEnumVARIANT> spev(spunkEnum);
for( CComVariant svar; spev->Next(1, &svar, nullptr) == S_OK; svar.Clear() ) {
if( svar.vt != VT_DISPATCH ) continue;
CComPtr<IShellBrowser> spsb;
hr = IUnknown_QueryService(svar.pdispVal, SID_STopLevelBrowser, IID_PPV_ARGS(&spsb));
if( S_OK != hr ) continue;
CComPtr<IShellView> spsv;
hr = spsb->QueryActiveShellView(&spsv);
if( S_OK != hr ) continue;
CComQIPtr<IPersistIDList> sppidl(spsv);
if( !sppidl ) continue;
CComHeapPtr<ITEMIDLIST_ABSOLUTE> spidl;
hr = sppidl->GetIDList(&spidl);
if( S_OK != hr ) continue;
CComPtr<IShellItem> spsi;
hr = SHCreateItemFromIDList(spidl, IID_PPV_ARGS(&spsi));
if( S_OK != hr ) continue;
CComHeapPtr<WCHAR> pszLocation;
hr = spsi->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &pszLocation);
if( S_OK != hr ) continue;
if( wcscmp(pszLocation, szPath) != 0 ) continue;
hr = spsv->QueryInterface(IID_PPV_ARGS(ppfv));
if( hr != S_OK ) continue;
return true;
}
return false;
}
But has anyone successfully used FindWindowSW to obtain an IDispatch * to an explorer window registered with IShellWindows?
I think MSDN is wrong, you cannot just assign the PIDL to the VARIANT because IShellWindows is out of process and the PIDL will not be marshaled correctly.
The correct way to do this is to get the size with ILGetSize and then call SafeArrayCreateVector to create a VT_UI1 SAFEARRAY and memcpy the PIDL data into the array. Set the VARIANT type to VT_ARRAY | VT_UI1 and parray to the SAFEARRAY you created. I believe the InitVariantFromBuffer helper function will do most of the work for you (Vista+).
ULONG cb = ILGetSize(pidl);
SAFEARRAY *psa = SafeArrayCreateVector(VT_UI1, 0, cb);
if (!psa) return;
memcpy(psa->pvData, pidl, cb);
V_VT(&vtLoc) = VT_ARRAY | VT_UI1, V_UNION(&vtLoc, parray) = psa;
hr = pSW->FindWindowSW(&vtLoc, &vtEmpty, SWC_EXPLORER, &hWnd, SWFO_NEEDDISPATCH | SWFO_INCLUDEPENDING, &pDisp);
printf("%#x %p %d\n", hr, pDisp, hWnd);
It seemed to work correctly when I did this but I would still prefer to use the enumeration method so you can call IShellFolder::CompareIDs instead of ILIsEqual* called by FindWindowSW. This assumes you don't care about the SWC_* value.
If you still want to follow the docs and use VT_VARIANT | VT_BYREF then you have to add a pointless indirection where one VARIANT points to another VARIANT and this VARIANT is the SAFEARRAY...
Related
I'm fetching the selected printer tray from a WIN32 call to PrintDlgEx(). This seems to work successfully most of the time, but recently I added a new printer to my machine (a DYMO LabelWriter 450) and it caused my simple software to fail.
Upon investigation, the call to DeviceCapabilities() for DC_BINS is returning 4294967295, while all of the other printers I've tested so far return single digit bin counts.
My first inclination is to omit the bin name when the bin count is greater than a given threshold (say... 20?), but I don't love this solution.
Is there a known reason that a printer would return the max UNSIGNED INT value for this? Is it just poorly written drivers, or is there an alternate meaning? Or perhaps I totally misunderstand the intended value.
If I have to write an arbitrary cap I will, but I'd like to better understand why this situation exists. Clearly, this printer doesn't have billions of different printer trays.
Here's an MRE:
HINSTANCE hinst = GetModuleHandle(NULL);
HRESULT hResult;
PRINTDLGEX pdx = {0};
LPPRINTPAGERANGE pPageRanges = NULL;
HWND hWndOwner = GetForegroundWindow();
if(!hWndOwner){
hWndOwner = GetDesktopWindow();
}
// Allocate an array of PRINTPAGERANGE structures.
pPageRanges = (LPPRINTPAGERANGE) GlobalAlloc(GPTR, 10 * sizeof(PRINTPAGERANGE));
if(!pPageRanges){
return wprintf(L"{\"error\": \"%s\"}", GetLastError()); // "Your computer does not have enough memory to complete this operation:"
}
// Initialize the PRINTDLGEX structure.
pdx.lStructSize = sizeof(PRINTDLGEX);
pdx.hwndOwner = hWndOwner;
pdx.hDevMode = NULL;
pdx.hDevNames = NULL;
pdx.hDC = NULL;
pdx.Flags = PD_RETURNDC | PD_COLLATE;
pdx.Flags2 = 0;
pdx.ExclusionFlags = 0;
pdx.nPageRanges = 0;
pdx.nMaxPageRanges = 10;
pdx.lpPageRanges = pPageRanges;
pdx.nMinPage = 1;
pdx.nMaxPage = 1000;
pdx.nCopies = 1;
pdx.hInstance = 0;
pdx.lpPrintTemplateName = NULL;
pdx.lpCallback = NULL;
pdx.nPropertyPages = 0;
pdx.lphPropertyPages = NULL;
pdx.nStartPage = START_PAGE_GENERAL;
pdx.dwResultAction = 0;
// Invoke the Print property sheet.
hResult = PrintDlgEx(&pdx);
DEVMODE * myDevMode = (DEVMODE *)GlobalLock(pdx.hDevMode);
DWORD binCount = DeviceCapabilities((CHAR*)myDevMode->dmDeviceName, nullptr, DC_BINS, nullptr, nullptr);
DWORD binNameCount = DeviceCapabilities((CHAR*)myDevMode->dmDeviceName, nullptr, DC_BINNAMES, nullptr, nullptr);
wprintf(L"\"binCount\":\"%lu\",", binCount);
wprintf(L"\"binNameCount\":\"%lu\",", binNameCount);
DeviceCapabilities() returns a signed int, not an unsigned DWORD.
The unsigned value 4294967295 is hex 0xFFFFFFFF, which is the same numeric value as a signed -1.
Per the DeviceCapabilities() documentation:
Return value
If the function succeeds, the return value depends on the setting of the fwCapability parameter. A return value of zero generally indicates that, while the function completed successfully, there was some type of failure, such as a capability that is not supported. For more details, see the descriptions for the fwCapability values.
If the function returns -1, this may mean either that the capability is not supported or there was a general function failure.
You are not accounting for the possibility of DeviceCapabilities() failing (or PrintDlgEx(), either).
Try this:
HWND hWndOwner = GetForegroundWindow();
if (!hWndOwner){
hWndOwner = GetDesktopWindow();
}
// Allocate an array of PRINTPAGERANGE structures.
LPPRINTPAGERANGE pPageRanges = (LPPRINTPAGERANGE) GlobalAlloc(GPTR, 10 * sizeof(PRINTPAGERANGE));
if (!pPageRanges){
// NOTE: GetLastError() returns DWORD, not TCHAR*! So, if you
// want to translate the error code in a human-readable string,
// use FormatMessage() instead...
return wprintf(L"{\"error\": %lu}", GetLastError());
}
// Initialize the PRINTDLGEX structure.
PRINTDLGEX pdx = {0};
pdx.lStructSize = sizeof(PRINTDLGEX);
pdx.hwndOwner = hWndOwner;
pdx.Flags = PD_RETURNDC | PD_COLLATE;
pdx.nMaxPageRanges = 10;
pdx.lpPageRanges = pPageRanges;
pdx.nMinPage = 1;
pdx.nMaxPage = 1000;
pdx.nCopies = 1;
pdx.nStartPage = START_PAGE_GENERAL;
HRESULT hResult = PrintDlgEx(&pdx);
if (hResult != S_OK)
{
GlobalFree(reinterpret_cast<HGLOBAL>(pPageRanges));
return wprintf(L"{\"error\": %d}", hResult);
}
if (pdx.dwResultAction == PD_RESULT_CANCEL)
{
GlobalFree(reinterpret_cast<HGLOBAL>(pPageRanges));
return wprintf(L"{\"error\": \"cancelled\"}");
}
DEVMODE *myDevMode = (DEVMODE*) GlobalLock(pdx.hDevMode);
int binCount = DeviceCapabilities(reinterpret_cast<TCHAR*>(myDevMode->dmDeviceName), nullptr, DC_BINS, nullptr, nullptr);
wprintf(L"\"binCount\":%d,", binCount);
int binNameCount = DeviceCapabilities(reinterpret_cast<TCHAR*>(myDevMode->dmDeviceName),
nullptr, DC_BINNAMES, nullptr, nullptr);
wprintf(L"\"binNameCount\":%d,", binNameCount);
if (binCount == -1)
{
...
}
if (binNameCount == -1)
{
...
}
...
GlobalUnlock(pdx.hDevMode);
GlobalFree(reinterpret_cast<HGLOBAL>(pPageRanges));
return ...;
I'm getting ridiculous behavior from RIDI_DEVICENAME. According to the documentation,
Return value
Type: UINT
If successful, this function returns a non-negative number indicating the number of bytes copied to pData.
If pData is not large enough for the data, the function returns -1. If pData is NULL, the function returns a value of zero. In both of these cases, pcbSize is set to the minimum size required for the pData buffer.
Call GetLastError to identify any other errors.
Ignoring the obvious problem that -1 is not a representable value in the UINT return type, it seems that the function should tell me the required size of the buffer, and if I supply a buffer of this size, the function should either succeed or at least follow its own rules for failure.
However, I'm not seeing this at all. On Windows 10, the Unicode version of the function sets pcbSize to 1 when pData is null and leaves it alone otherwise, failing in all cases. The ANSI version of the function sets pcbSize to 2 when pData is null, and otherwise doubles whatever value was passed in, and still fails.
Headers used for either version of test code:
#define WIN32_EXTRA_LEAN 1
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
#include <windows.h>
ANSI test code:
std::string GetRawInputDeviceName( HANDLE hRaw )
{
UINT numChars = 0u;
INT validChars;
validChars = static_cast<INT>(::GetRawInputDeviceInfoA(hRaw, RIDI_DEVICENAME, nullptr, &numChars));
auto lasterror = ::GetLastError();
if (lasterror != ERROR_INSUFFICIENT_BUFFER) {
std::wcerr << L"Failed to get length of name of raw input device, retcode = " << validChars << L", last error = " << lasterror << L"\n";
return {};
}
std::string name;
name.resize(numChars);
validChars = static_cast<INT>(::GetRawInputDeviceInfoA(hRaw, RIDI_DEVICENAME, &name[0], &numChars));
lasterror = ::GetLastError();
if (validChars > 0) {
name.resize(validChars);
return name;
}
else {
std::wcerr << L"Failed to get name of raw input device, retcode = " << validChars << L", last error = " << lasterror << L"\n";
return {};
}
}
Unicode test code:
std::wstring GetRawInputDeviceName( HANDLE hRaw )
{
UINT numChars = 0u;
INT validChars;
validChars = static_cast<INT>(::GetRawInputDeviceInfoW(hRaw, RIDI_DEVICENAME, nullptr, &numChars));
auto lasterror = ::GetLastError();
if (lasterror != ERROR_INSUFFICIENT_BUFFER) {
std::wcerr << L"Failed to get length of name of raw input device, retcode = " << validChars << L", last error = " << lasterror << L"\n";
return {};
}
std::wstring name;
name.resize(numChars);
validChars = static_cast<INT>(::GetRawInputDeviceInfoW(hRaw, RIDI_DEVICENAME, &name[0], &numChars));
lasterror = ::GetLastError();
if (validChars > 0) {
name.resize(validChars);
return name;
}
else {
std::wcerr << L"Failed to get name of raw input device, retcode = " << validChars << L", last error = " << lasterror << L"\n";
return {};
}
}
On Windows 10 through RDP I'm getting ERROR_INSUFFICIENT_BUFFER consistently.
On Windows 8.1 running as a local user, I get ERROR_INSUFFICIENT_BUFFER if pData is null, and when I provide a buffer I get back failure ((UINT)-1) and GetLastError() returns zero.
I've also just tried proposing a likely-large-enough buffer size, and got failures as well.
What is going on, what is the right way to get the interface path name, and do I need administrative rights or to call some other APIs first? I don't seem to be having any problems calling GetRawInputDeviceList or using RIDI_DEVICEINFO mode of GetRawInputDeviceInfo... but I need the interface path in order to go further.
Windows HID Device Name Format
https://stackoverflow.com/a/64320052/103167
the GetRawInputDeviceName have several errors in declaration / implementation / documentation
by fact more correct declare return value as signed ( LONG or INT) but not UINT
exist 3 case:
1. function return negative value (or if want -1) : this is error
case, and by design - last error must be set. but really it not
always set (implementation error).
most common errors:
pcbSize or pData point to invalid or read only memory location. usual error in this case ERROR_NOACCESS (translated from
STATUS_ACCESS_VIOLATION)
hDevice not valid handle - ERROR_INVALID_HANDLE is returned
uiCommand not valid RIDI_XXX constant - ERROR_INVALID_PARAMETER
*pcbSize is not large enough for the data - in this case *pcbSize is set to the minimum size required for the pData buffer. ERROR_INSUFFICIENT_BUFFER
again - only in this case (-1) exist sense call GetLastError();
2. function return 0 this possible only in case when pData is NULL.
*pcbSize is set to the minimum size required for the pData buffer.
3. function return positive value ( > 0) this mean that this count of
bytes (in case RIDI_PREPARSEDDATA or RIDI_DEVICEINFO ) or
characters (in case RIDI_DEVICENAME) written to buffer
so documentation is wrong here:
pcbSize
[in, out]
Pointer to a variable that contains the size, in bytes, of the data in
pData.
in case RIDI_DEVICENAME in characters
so already visible very serious problems with design (type of return value - unsigned) and mixed bytes/characters. many different cases.
but then exist critical error in implementation. in begin of function handle hDevice converted to pointer.
PDEVICEINFO pDeviceInfo = HMValidateHandle(hDevice, TYPE_DEVICEINFO);
(if 0 returned - we got -1 on exit with ERROR_INVALID_HANDLE).
in DEVICEINFO exist UNICODE_STRING ustrName - this name and copied to user mode
switch (uiCommand) {
case RIDI_DEVICENAME:
/*
* N.b. UNICODE_STRING counts the length by the BYTE count, not by the character count.
* Our APIs always treat the strings by the character count. Thus, for RIDI_DEVICNAME
* only, cbOutSize holds the character count, not the byte count, in spite of its
* name. Confusing, but cch is the way to be consistent.
*/
cbOutSize = pDeviceInfo->ustrName.Length / sizeof(WCHAR) + 1; // for Null terminator
break;
//...
}
required cbOutSize compared with cbBufferSize = *pcbSize;
and if (cbBufferSize >= cbOutSize) api begin copy operation
exist next code
case RIDI_DEVICENAME:
if (cbOutSize <= 2) { // !!!! error !!!!
retval = -1;
goto leave;
}
RtlCopyMemory(pData, pDeviceInfo->ustrName.Buffer, pDeviceInfo->ustrName.Length);
((WCHAR*)pData)[1] = '\\'; // convert nt prefix ( \??\ ) to win32 ( \\?\ )
((WCHAR*)pData)[cbOutSize - 1] = 0; // make it null terminated
break;
cbOutSize here - is (len + 1) of device name (which we not control). so if name is zero length - always -1 is returned (error #1) but last error not set ( error #2 )
of course exist and error #3 - why is device name is 0 length ? this must not be. but in case terminal service devices - (virtual mouse/ keyboard device created on UMB bus ) - exist this result.
full code for api ( in kernel)
UINT NtUserGetRawInputDeviceInfo(
HANDLE hDevice,
UINT uiCommand,
LPVOID pData,
PUINT pcbSize)
{
UINT cbOutSize = 0;
UINT cbBufferSize;
int retval = 0;
EnterCrit(0, UserMode);
UserAtomicCheck uac;
try {
ProbeForRead(pcbSize, sizeof(UINT), sizeof(DWORD));
cbBufferSize = *pcbSize;
} except (EXCEPTION_EXECUTE_HANDLER) {
UserSetLastError(RtlNtStatusToDosError(GetExceptionCode()));// ERROR_NOACCESS
retval = -1;
goto leave1;
}
EnterDeviceInfoListCrit_();
PDEVICEINFO pDeviceInfo = HMValidateHandle(hDevice, TYPE_DEVICEINFO);
if (pDeviceInfo == NULL) {
UserSetLastError(ERROR_INVALID_HANDLE);
retval = -1;
goto leave;
}
/*
* Compute the size of the output and evaluate the uiCommand.
*/
switch (uiCommand) {
case RIDI_PREPARSEDDATA:
if (pDeviceInfo->type == DEVICE_TYPE_HID) {
cbOutSize = pDeviceInfo->hid.pHidDesc->hidCollectionInfo.DescriptorSize;
} else {
cbOutSize = 0;
}
break;
case RIDI_DEVICENAME:
/*
* N.b. UNICODE_STRING counts the length by the BYTE count, not by the character count.
* Our APIs always treat the strings by the character count. Thus, for RIDI_DEVICNAME
* only, cbOutSize holds the character count, not the byte count, in spite of its
* name. Confusing, but cch is the way to be consistent.
*/
cbOutSize = pDeviceInfo->ustrName.Length / sizeof(WCHAR) + 1; // for Null terminator
break;
case RIDI_DEVICEINFO:
cbOutSize = sizeof(RID_DEVICE_INFO);
break;
default:
UserSetLastError(ERROR_INVALID_PARAMETER);
retval = -1;
goto leave;
}
if (pData == NULL) {
/*
* The app wants to get the required size.
*/
try {
ProbeForWrite(pcbSize, sizeof(UINT), sizeof(DWORD));
*pcbSize = cbOutSize;
} except (EXCEPTION_EXECUTE_HANDLER) {
UserSetLastError(RtlNtStatusToDosError(GetExceptionCode()));// ERROR_NOACCESS
retval = -1;
goto leave;
}
retval = 0;
} else {
if (cbBufferSize >= cbOutSize) {
try {
ProbeForWrite(pData, cbBufferSize, sizeof(DWORD));
switch (uiCommand) {
case RIDI_PREPARSEDDATA:
if (pDeviceInfo->type == DEVICE_TYPE_HID) {
RtlCopyMemory(pData, pDeviceInfo->hid.pHidDesc->pPreparsedData, cbOutSize);
}
break;
case RIDI_DEVICENAME:
if (cbOutSize <= 2) { // !!!!
retval = -1;
goto leave;
}
RtlCopyMemory(pData, pDeviceInfo->ustrName.Buffer, pDeviceInfo->ustrName.Length);
((WCHAR*)pData)[1] = '\\'; // make it null terminated
((WCHAR*)pData)[cbOutSize - 1] = 0; // make it null terminated
break;
case RIDI_DEVICEINFO:
{
PRID_DEVICE_INFO prdi = (PRID_DEVICE_INFO)pData;
ProbeForRead(prdi, sizeof(UINT), sizeof(DWORD));
if (prdi->cbSize != cbOutSize) {
MSGERRORCLEANUP(ERROR_INVALID_PARAMETER);
}
ProbeForWrite(prdi, sizeof(RID_DEVICE_INFO), sizeof(DWORD));
RtlZeroMemory(prdi, sizeof(RID_DEVICE_INFO));
prdi->cbSize = cbOutSize;
switch (pDeviceInfo->type) {
case DEVICE_TYPE_HID:
prdi->dwType = RIM_TYPEHID;
prdi->hid.dwVendorId = pDeviceInfo->hid.pHidDesc->hidCollectionInfo.VendorID;
prdi->hid.dwProductId = pDeviceInfo->hid.pHidDesc->hidCollectionInfo.ProductID;
prdi->hid.dwVersionNumber = pDeviceInfo->hid.pHidDesc->hidCollectionInfo.VersionNumber;
prdi->hid.usUsagePage = pDeviceInfo->hid.pHidDesc->hidpCaps.UsagePage;
prdi->hid.usUsage = pDeviceInfo->hid.pHidDesc->hidpCaps.Usage;
break;
case DEVICE_TYPE_MOUSE:
prdi->dwType = RIM_TYPEMOUSE;
prdi->mouse.dwId = pDeviceInfo->mouse.Attr.MouseIdentifier;
prdi->mouse.dwNumberOfButtons = pDeviceInfo->mouse.Attr.NumberOfButtons;
prdi->mouse.dwSampleRate = pDeviceInfo->mouse.Attr.SampleRate;
break;
case DEVICE_TYPE_KEYBOARD:
prdi->dwType = RIM_TYPEKEYBOARD;
prdi->keyboard.dwType = GET_KEYBOARD_DEVINFO_TYPE(pDeviceInfo);
prdi->keyboard.dwSubType = GET_KEYBOARD_DEVINFO_SUBTYPE(pDeviceInfo);
prdi->keyboard.dwKeyboardMode = pDeviceInfo->keyboard.Attr.KeyboardMode;
prdi->keyboard.dwNumberOfFunctionKeys = pDeviceInfo->keyboard.Attr.NumberOfFunctionKeys;
prdi->keyboard.dwNumberOfIndicators = pDeviceInfo->keyboard.Attr.NumberOfIndicators;
prdi->keyboard.dwNumberOfKeysTotal = pDeviceInfo->keyboard.Attr.NumberOfKeysTotal;
break;
}
}
break;
default:
__assume(false);
}
} except (EXCEPTION_EXECUTE_HANDLER) {
UserSetLastError(RtlNtStatusToDosError(GetExceptionCode()));// ERROR_NOACCESS
retval = -1;
goto leave;
}
retval = cbOutSize;
} else {
/*
* The buffer size is too small.
* Returns error, storing the required size in *pcbSize.
*/
retval = -1;
try {
ProbeForWrite(pcbSize, sizeof(UINT), sizeof(DWORD));
*pcbSize = cbOutSize;
UserSetLastError(ERROR_INSUFFICIENT_BUFFER);
} except (EXCEPTION_EXECUTE_HANDLER) {
UserSetLastError(RtlNtStatusToDosError(GetExceptionCode()));// ERROR_NOACCESS
retval = -1;
goto leave;
}
}
}
leave:
LeaveDeviceInfoListCrit_();
leave1:
UserSessionSwitchLeaveCrit();
return retval;
}
then GetRawInputDeviceInfoA add additional errors compare GetRawInputDeviceInfoW - the value from *pcbSize by some reason multiple on 2. but again - this error in all case.
note that DeviceName (formatted from strings returned from driver on IRP_MN_QUERY_ID have very strict restrictions:
If a driver returns an ID with an illegal character, the system will
bug check. Characters with the following values are illegal in an ID
for this IRP:
Less than or equal to 0x20 (' ')
Greater than 0x7F
Equal to 0x2C (',')
so even after covert unicode to ansi - length of device name will be the same ( all symbols < 0x80 ). so not need *2 buffer size for Ansi version.
then i already view error in your code - you call ::GetLastError(); unconditionally after GetRawInputDeviceInfoW - but returned value have sense only in case api return -1
explain for observed behavior:
for local devices api in general work correct (if no mistakes in our code)
for terminal service devices - was 0 length ustrName. as result if we pass NULL in pData - return value will be
pDeviceInfo->ustrName.Length / sizeof(WCHAR) + 1;
because pDeviceInfo->ustrName.Length == 0 - 1 will be returned inside *pcbSize
in case A version - -by mistake - 2*1==2 will be returned.
but when e pass not NULL in pData - we trap in this
if (cbOutSize <= 2) { // !!!! error !!!!
retval = -1;
goto leave;
}
so you can pass any by size buffer, anyway, because (cbOutSize <= 2) - -1 will be returned and last error not set
possible solution - at first - never use ansi version - GetRawInputDeviceInfoA
use this wrapper function.
ULONG GetRawInputDeviceInfoExW(_In_opt_ HANDLE hDevice,
_In_ UINT uiCommand,
_Inout_updates_bytes_to_opt_(*pcbSize, *pcbSize) LPVOID pData,
_Inout_ PUINT pcbSize)
{
switch (int i = GetRawInputDeviceInfoW(hDevice, uiCommand, pData, pcbSize))
{
case 0:
return ERROR_INSUFFICIENT_BUFFER;
case 1:
return ERROR_INVALID_NAME;
default:
if (0 > i)
{
return GetLastError();
}
*pcbSize = i;
return NOERROR;
}
}
example of usage: (/RTCs must be disabled )
void Demo()
{
PRAWINPUTDEVICELIST pRawInputDeviceList = 0;
UINT uiNumDevices = 0;
UINT cch, cchAllocated = 0;
union {
PVOID buf;
PWSTR name;
};
buf = 0;
while (0 <= (int)GetRawInputDeviceList(pRawInputDeviceList, &uiNumDevices, sizeof(RAWINPUTDEVICELIST)))
{
if (pRawInputDeviceList)
{
do
{
HANDLE hDevice = pRawInputDeviceList->hDevice;
ULONG dwError;
while (ERROR_INSUFFICIENT_BUFFER == (dwError =
GetRawInputDeviceInfoExW(hDevice, RIDI_DEVICENAME, name, &(cch = cchAllocated))))
{
if (cch > cchAllocated)
{
cchAllocated = RtlPointerToOffset(buf = alloca((cch - cchAllocated) * sizeof(WCHAR)),
pRawInputDeviceList) / sizeof(WCHAR);
}
else
{
__debugbreak();
}
}
if (dwError == NOERROR)
{
DbgPrint("[%p, %x %S]\n", hDevice, pRawInputDeviceList->dwType, name);
}
else
{
DbgPrint("error = %u\n", dwError);
}
} while (pRawInputDeviceList++, --uiNumDevices);
break;
}
pRawInputDeviceList = (PRAWINPUTDEVICELIST)alloca(uiNumDevices * sizeof(RAWINPUTDEVICELIST));
}
}
This code is working fine on my PC. Not sure, but it indeed could be RDP issue.
UINT result = ::GetRawInputDeviceInfoW(m_Handle, RIDI_DEVICENAME, nullptr, &size);
if (result == static_cast<UINT>(-1))
{
//PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
return false;
}
DCHECK_EQ(0u, result);
std::wstring buffer(size, 0);
result = ::GetRawInputDeviceInfoW(m_Handle, RIDI_DEVICENAME, buffer.data(), &size);
if (result == static_cast<UINT>(-1))
{
//PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
return false;
}
DCHECK_EQ(size, result);
Where are the positions of Windows desktop shortcuts stored?
I am asking about the screen positions of the icons not the actual icons themselves. I know the icons themselves are stored in various DLLs, EXEs etc. The positions are clearly stored in some non-volatile store because they persists through re-boots.
My end goal is to write an app to display, and optionally re-arrange icons on my desktop.
I know this is possible because many available apps do this (e.g., "WinTidy").
I find much talk about "Windows Shell Bags". An interesting article about these are in http://williballethin.com.forensics/shellbags, but that only addresses directories not shortcuts. These are in the registry at various places including
`HKEY_CURRENT_USER/Software/Microsoft/Windows/Shell/Bags/1/Desktop`
`HKEY_USERS/.DEFAULT/Software/Microsoft/Windows/Shell/Bags/1/Desktop`
I wrote a program to extract these but the format of the key values is incomprehensible.
Any body know where and how they are stored?
UPDATE 6/3/20
++++++++++++++++++++++++++++++++++++++
I just switched over to a Win10 64-bit machine and find the solution below no longer works. I believe because of a change in the desktop internals. I figured out how to do this. See "WIN10 ADDENDUM" at the end of this answer.
++++++++++++++++++++++++++++++++++++++
I finally figured out how to do what I want (display and re-arrange desktop icons). My original question concerned locating, reading and writing to the file where the icon info is stored, but this is not a useful approach. Here is what I learned:
Explorer.exe displays desktop items in a giant ListView covering the whole desktop with ListView items corresponding to each visible icon. At startup, Explorer reads info from some arcane file and populates the ListView. On exit, it re-writes that file from the ListView. So modifying the file would not help because it would be overwritten on exit.
The right way to manipulate desktop items is to directly manipulate items in the ListView. Any changes are immediately visible on change, and are saved on exit. To access the items, we can use several Windows messages: LVM_GETITEM, LVM_GETITEMCOUNT, LVM_GETITEMPOSITION and LVM_SETITEMPOSITION. These messages are fairly simple to use with one complication: some require pointers to parameter structures. These structures must be in Explorer's address space not my app's, so some trickery is needed. Here's how to do it. I use LVM_GETITEMPOSITION as an example, which requires a pointer to a POINT structure.
Declare a POINT structure in your app.
Allocate a mirror structure in Explorer's address space using API VirtualAllocEx().
Send LVM_GETITEMPOSITION to Explorer specifying a pointer to this structure.
Read back the result into your app's POINT using API ReadProcessMemory(). This function can read memory across different address spaces.
I have prototyped these operations and they work as I wanted. My code is quite long but I will post excerpts as soon as I clean it up.
UPDATE 10/4/2019 ------------------------------------
CODE EXCERPTS
A set of commonly used utility functions was created to make code more compact and readable. These are named "exp*()" and are included at the end. A reference can be found at http://ramrodtechnology.com/explorer. Much of the basic technique herein was shamelessly stolen from https://www.codeproject.com/Articles/5570/Stealing-Program-s-Memory
Setup
// COMMONLY USED VARS
HANDLE hProcess; // explorer process handle
HWND hWndLV; // explorer main window
// SET UP CONVENIENCE VARS
hProcess = expGetProcessHandle(); // get explorer process handle
if( !hProcess ) exit( 1 );
hWndLV = expGetListView(); // get main ListView of Desktop
if( !hWndLV ) exit( 1 );
Function to Print All Item Names
//# Process a list view window and print item names
int
printAllNames()
{
int ok,icount,indx;
LVITEM item; // point in app space
LVITEM *_pitem; // point in exp space
char text[512];
char *_ptext;
int nr,nwrite; // count of bytes read/written
printf( "\n" );
// ALLOC ITEMS IN EXP SPACE
_pitem = expAlloc( sizeof(LVITEM) );
_ptext = expAlloc( sizeof(text ) );
printf( " NAME\n" );
printf( " ==================================\n" );
icount = expGetItemCount();
for( indx=0; indx<icount; indx++ ) { // for each item in LV
// SETUP ITEM IN EXP SPACE
memset( &item, 0, sizeof(LVITEM) ); // preclear
item.iItem = indx; // index of item to read
item.iSubItem = 0; // sub index (always 0)
item.mask = LVIF_TEXT; // component to read
item.pszText = _ptext; // buffer to recv text
item.cchTextMax = sizeof(text); // size of buffer
// WRITE ITEM REQ TO EXP SPACE
ok = WriteProcessMemory( hProcess, _pitem, &item, sizeof(LVITEM), &nwrite );
// SEND MESSAGE TO GET ITEM INTO EXP SPACE
ok = SendMessage( hWndLV, LVM_GETITEM, indx, (LPARAM)_pitem );
// READ EXP TEXT INTO APP SPACE
memset( &item, 0, sizeof(LVITEM) );
ok = ReadProcessMemory( hProcess, _pitem, &item, sizeof(POINT), &nr );
ok = ReadProcessMemory( hProcess, _ptext, &text, sizeof(text), &nr );
// PRINT RESULT
printf( " %s\n", text );
}
ok = expFree( _pitem );
ok = expFree( _ptext );
return( TRUE );
//r Returns TRUE on success, FALSE on error
}
Function To Print All Item Positions
//# Process a list view window and print position
int
printAllPositions()
{
int ok,icount,indx,nr;
POINT pt; // point in app space
POINT *_ppt; // point in exp space
icount = expGetItemCount();
_ppt = expAlloc( sizeof(POINT) );
if( !_ppt ) return( FALSE );
printf( " X Y\n" );
printf( "---- ----\n" );
for( indx=0; indx<icount; indx++ ) { // for each item in LV
ok = SendMessage( hWndLV, LVM_GETITEMPOSITION, indx, (LPARAM)_ppt );
ok = ReadProcessMemory( hProcess, _ppt, &pt, sizeof(POINT), &nr );
printf( "%4d %4d\n", pt.x, pt.y );
}
ok = expFree( _ppt );
return( TRUE );
//r Returns TRUE on success
}
Function To Move Item
See 'expSetItemPosition' below. UPDATED 10/6/19
Explorer Utility Functions
// EXPLORER UTILITY FUNCTIONS
//# Allocate a block of memory in explorer space
void *
expAlloc(
int size) // size of block
{
void *p;
p = VirtualAllocEx(
hProcess,
NULL,
size,
MEM_COMMIT,
PAGE_READWRITE );
return( p );
//r Returns addr of memory in EXPLORER space or NULL on error
}
//# Free virtual memory in EXPLORER space
int
expFree(
void *p) // pointer to free
{
int ok;
ok = VirtualFreeEx( hProcess, p, 0, MEM_RELEASE );
return( ok );
//r Returns TRUE on success, else FALSE
}
static int aBiggest; // biggest area so far
static HWND hWndBiggest; // hWnd with biggest area
//# Find main list view of explorer
HWND
expGetListView()
{
//n Approach: Enumerate all child windows of desktop and find largest.
//n This will be the main explorer window.
HWND hWndDesktop;
hWndDesktop = GetDesktopWindow();
if( !hWndDesktop ) return( NULL );
aBiggest = -1; // init
hWndBiggest = NULL; // init
EnumChildWindows( hWndDesktop, CallbackDesktopChild, 0 );
return( hWndBiggest );
//r Returns hWnd of largest explorer list view
}
//# Callback for EnumChildWindows
BOOL CALLBACK CallbackDesktopChild(
HWND hWnd,
LPARAM dwUser)
{
//n Get size of child. If biggest, save hWnd.
int i,w,h,a;
char classname[MAXPATH+1];
RECT rect;
i = GetClassName( hWnd, classname, MAXPATH ); // get class
if( stricmp( classname, "SysListView32" ) ) { // not a list view?
return( TRUE ); // skip it
}
// CALC SIZE
i = GetWindowRect( hWnd, &rect );
w = rect.right - rect.left;
h = rect.bottom - rect.top;
// CHECK IF BIGGEST
a = w * h;
if( a > aBiggest ) { // is biggest?
aBiggest = a;
hWndBiggest = hWnd;
}
return( TRUE ); // TRUE to continue enumeration
}
//# Get process handle of explorer.exe
HANDLE
expGetProcessHandle()
{
//n Approach: take process snapshot and loop through to find "explorer.exe"
//n Needs tlhelp32.h and comctl32.lib
int i,stat;
PROCESSENTRY32 pe;
HANDLE hSnapshot;
char *name;
HANDLE h;
// TAKE A SNAPSHOT
hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
if( !hSnapshot ) return( NULL );
// LOOP THROUGH PROCESSES AND FIND "explorer.exe"
for( i=0;;i++ ) {
pe.dwSize = sizeof( PROCESSENTRY32 );
if( i == 0 ) stat = Process32First( hSnapshot, &pe );
else stat = Process32Next ( hSnapshot, &pe );
if( !stat ) break; // done or error?
name = pe.szExeFile;
if( !stricmp( name, "explorer.exe" ) ) { // matches?
h = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID );
return( h );
}
}
return( NULL );
//r Returns explorer process handle or NULL on error
}
//# Get count of items in explorer list view
int
expGetItemCount()
{
int count;
count = SendMessage( hWndLV, LVM_GETITEMCOUNT, 0, 0 );
return( count );
//r Returns count of item
}
//# Get position of list view icon by index
int
expGetItemPosition(
int indx, // index of item
int *x, // ptr to int to recv x
int *y) // ptr to int to recv y
{
int i,ok,icount;
char classname[MAXPATH+1];
POINT pt; // point in app space
POINT *_ppt; // point in exp space
int nr; // count of bytes read
//int w,h;
i = GetClassName( hWndLV, classname, MAXPATH );
// GET COUNT OF ITEMS IN LIST VIEW
icount = expGetItemCount();
if( indx < 0 || indx >= icount ) return( FALSE );
// ALLOC POINT IN EXP SPACE
_ppt = expAlloc( sizeof(POINT) );
if( !_ppt ) return( FALSE );
// SEND MESSAGE TO GET POS INTO EXP SPACE POINT
ok = SendMessage( hWndLV, LVM_GETITEMPOSITION, indx, (LPARAM)_ppt );
if( !ok ) return( FALSE );
// READ EXP SPACE POINT INTO APP SPACE POINT
ok = ReadProcessMemory( hProcess, _ppt, &pt, sizeof(POINT), &nr );
if( !ok ) return( FALSE );
ok = expFree( _ppt );
if( !ok ) return( FALSE );
if( x ) *x = pt.x;
if( y ) *y = pt.y;
//r Returns TRUE on success
return( TRUE );
}
//# Move item
int
expSetItemPosition(
char *name, // icon name
int x, // new x coord
int y) // new y coord
{
int ok,indx;
LPARAM lParam;
indx = expGetItemIndex( name );
if( indx < 0 ) return( FALSE );
lParam = MAKELPARAM( x, y );
ok = SendMessage( hWndLV, LVM_SETITEMPOSITION, indx, lParam );
if( !ok ) return( FALSE );
return( TRUE );
//r Returns TRUE on success
}
WIN10 ADDENDUM
6/19/20
++++++++++++++++++++++++++++++++++
Under Win10, the solution is much more complicated. You must use various COM objects and interfaces, e.g. IShellWindows, etc. (God, I hate COM). I did not create a library but rather offer a complete working program below. I compiled this using MSVC 2019. Error checking has been omitted for clarity (but you should do it).
// icons.cpp - Display (and optionally move) desktop icons
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <ShlObj.h>
#include <atlbase.h>
int
main(int argc,char** argv)
{
CComPtr<IShellWindows> spShellWindows;
CComPtr<IShellBrowser> spBrowser;
CComPtr<IDispatch> spDispatch;
CComPtr<IShellView> spShellView;
CComPtr<IFolderView> spView;
CComPtr<IShellFolder> spFolder;
CComPtr<IEnumIDList> spEnum;
CComHeapPtr<ITEMID_CHILD> spidl;
CComVariant vtLoc(CLSID_ShellWindows);
CComVariant vtEmpty;
STRRET str;
int count=0;
HRESULT hr;
long lhWnd;
// INITIALIZE COM
CoInitialize(NULL);
// GET ShellWindows INTERFACE
hr = spShellWindows.CoCreateInstance(CLSID_ShellWindows);
// FIND WINDOW
hr = spShellWindows->FindWindowSW(
&vtLoc, &vtEmpty, SWC_DESKTOP, &lhWnd, SWFO_NEEDDISPATCH, &spDispatch);
// GET DISPATCH INTERFACE
CComQIPtr<IServiceProvider>(spDispatch)->
QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&spBrowser));
spBrowser->QueryActiveShellView(&spShellView);
spShellView->QueryInterface(IID_PPV_ARGS(&spView) );
hr = spView->GetFolder(IID_PPV_ARGS(&spFolder));
// GET ENUMERATOR
spView->Items(SVGIO_ALLVIEW, IID_PPV_ARGS(&spEnum)); // get enumerator
// ENUMERATE ALL DESKTOP ITEMS
for (; spEnum->Next(1, &spidl, nullptr) == S_OK; spidl.Free()) {
// GET/PRINT ICON NAME AND POSITION
char* name;
POINT pt;
spFolder->GetDisplayNameOf(spidl, SHGDN_NORMAL, &str);
StrRetToStr(&str, spidl, &name);
spView->GetItemPosition(spidl, &pt);
printf("%5d %5d \"%s\"\n", pt.x, pt.y, name);
#define MOVE_ICON
#ifdef MOVE_ICON
// OPTIONAL: MOVE *SINGLE* SELECTED ITEM
{
if( !_stricmp(name, "ICON_NAME_TO_MOVE") ) {
PCITEMID_CHILD apidl[1] = { spidl };
int numitems = 1;
// SET pt TO NEW POSITION HERE
hr = spView->SelectAndPositionItems(numitems, apidl, &pt, 0);
}
}
#endif
count++;
}
CoUninitialize(); // release COM
fprintf(stderr, "enumerated %d desktop icons\n", count);
fprintf(stderr, "Press any key to exit...\n");
_getch();
exit(0 );
}
HKEY hKey = 0;
DWORD dwType = REG_SZ;
TCHAR buf[255] = {0};
DWORD dwBufSize = sizeof(buf);
DWORD ret;
CComboBox m_portCombo;
if( RegOpenKeyEx( HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DEVICEMAP\\SERIALCOMM"), 0, KEY_QUERY_VALUE, &hKey ) == ERROR_SUCCESS )
{
if( RegQueryValueEx( hKey, TEXT("\\Device\\Serial0"), 0, &dwType, (LPBYTE)buf, &dwBufSize ) == ERROR_SUCCESS )
{
CString str = buf;
m_portCombo.AddString(str);
}
if( RegQueryValueEx( hKey, TEXT("\\Device\\Serial1"), 0, &dwType, (LPBYTE)buf, &dwBufSize ) == ERROR_SUCCESS )
{
CString str = buf;
}
if( RegQueryValueEx( hKey, TEXT("\\Device\\Serial2"), 0, &dwType, (LPBYTE)buf, &dwBufSize ) == ERROR_SUCCESS )
{
CString str = buf;
}
if( RegQueryValueEx( hKey, TEXT("\\Device\\Serial3"), 0, &dwType, (LPBYTE)buf, &dwBufSize ) == ERROR_SUCCESS )
{
CString str = buf;
}
}
First problem: i want to change the TEXT("\\Device\\Serial3") with something like TEXT("\\Device\\Serial",%i), so i can resume all that lines of code to a for loop.Is tehre a way to accomplish this?
Second problem: if i use the m_portCombo.AddString(str); i get an Debug Assertion Failed! error, and, of course, the combobox is not populated with that registry value. Why could that happen?
First Problem: Use the CString Format() function using %d for integer.
for (int i =0 ; i<10; i++)
{
CString szPath;
szPath.Format(TEXT("\\Device\\Serial%d"),i);
// ...
}
Second Problem:
There could be many reasons this would fail. Most likely of which would be having not created the combo box yet.(It needs a window handle before it can add strings) To figure out the cause of the debug assertion, click the "retry" button on the Debug Assertion Failed window and it should jump to the code which caused the assertion. For example it might be something like:
ASSERT(GetSafeHwnd()!=NULL);
Your combo box class won't be 'subclassed' until after the first DoDataExchange is called (and any attempt to use it before that happens will ASSERT). Either wait until the base class has run OnInitDialog or do something like this:
CComboBox * pcombo = static_cast<CComboBox*>(GetDlgItem( IDC_MYCOMBO ));
pcombo->AddString( szPath );
See #TheSteve's answer for string problem.
I am trying to get a list of all the executable paths of running processes
The do-while loop (shown below) starts off and after about 90 something iterations it fails with a ERROR_INSUFFICIENT_BUFFER error. I presume thats the pBuffer, I tried with a very large buffer and it still failed. The ProcessEntry struct on the failed iteration has garbage in szExeFile. Please advise (I close the handles, not shown below)
Code:
// Retrieve a handle to the process snapshot
HANDLE hProcessSnapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
if (INVALID_HANDLE_VALUE == hProcessSnapshot) ERROR;
// Retrieve information about the first process and exit if unsuccessful
PROCESSENTRY32 oProcessEntry;
memset(&oProcessEntry, 0x00, sizeof(oProcessEntry));
oProcessEntry.dwSize = sizeof(PROCESSENTRY32);
BOOL bFileFound(Process32First(hProcessSnapshot, &oProcessEntry));
DWORD dwError;
if(!bFileFound) {
dwError = GetLastError();
if(ERROR_NO_MORE_FILES == dwError) return TPathList();
// Error
ERROR;
}
// Walk the snapshot of processes
TCHAR pBuffer[MAX_PATH];
TPathList lExecutablePaths;
do {
// Get handle to process
HANDLE hProcess(OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE,
oProcessEntry.th32ProcessID));
if(INVALID_HANDLE_VALUE == hProcess) ERROR;
if (!hProcess) continue;
// Get the module path
if(GetModuleFileNameEx(hProcess, 0, pBuffer, MAX_PATH) == 0) ERROR;
lExecutablePaths.push_back(CPath(pBuffer));
}
// Get next process
while(Process32Next(hProcessSnapshot, &oProcessEntry));
// If we ran out of files return what has been found
dwError = GetLastError();
if(ERROR_NO_MORE_FILES == dwError) return lExecutablePaths;
ERROR;
You can use like this:
CString sBuffer;
DWORD dwSize = MAX_PATH + 1, dwError = 0;
do
{
GetModuleFileName( NULL, sBuffer.GetBuffer( dwSize ), dwSize );
// Retrieve the last error. If we've succeeded ERROR_SUCCESS
// will be returned; otherwise, we'll get an ERROR_INSUFFICIENT_BUFFER
// error.
dwError = ::GetLastError( );
// Buffer may not be big enough so double its size
dwSize *= 2;
}
while( dwError == ERROR_INSUFFICIENT_BUFFER
&& dwError != ERROR_SUCCESS );
// Release the buffer (turn the string back to const)
sBuffer.ReleaseBuffer( );
I hope it will work.
Also you can prefer How can I calculate the complete buffer size for GetModuleFileName?