1

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?

cburn11
  • 91
  • 4

1 Answers1

0

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...

Anders
  • 97,548
  • 12
  • 110
  • 164
  • I had assumed FindWindowSW would marshal the pidl, but is this what you are suggesting? `auto size = ILGetSize(pidl); auto psf_array = SafeArrayCreateVector(VT_UI1, 0, size); memcpy(psf_array->pvData, pidl, size); VARIANT vtLoc; vtLoc.vt = VT_ARRAY | VT_UI1; vtLoc.parray = psf_array;` – cburn11 Apr 04 '18 at 21:51
  • Yes that looks correct. How could COM know how to marshal? Wrong variant type and a casted pointer, all PIDL information is gone way before the function call. – Anders Apr 04 '18 at 21:54
  • Unfortunately, that still returns S_FALSE. The more verbose `VARIANT vtLoc, vtArray; vtLoc.vt = VT_VARIANT | VT_BYREF; vtLoc.pvarVal = &vtArray; vtArray.vt = VT_ARRAY | VT_UI1; vtArray.parray = psf_array;` also returns S_FALSE. – cburn11 Apr 04 '18 at 22:01
  • I am not sure why it would be odd to assume FindWindowSW would handle marshaling. Other winapi functions like Set/Get WindowText quietly marshal character arrays across processes. – cburn11 Apr 04 '18 at 22:03
  • COM knows how to marshal a VARIANT but it looks at the VARIANTS type and there is no type for a PIDL which is why it needs to be a plain byte array. – Anders Apr 04 '18 at 22:21
  • If it still fails then you might want to try a debugger, FindWindowSW calls a helper function called VariantToIDList and if this fails there is a problem with the marshaling. If it works then the problem is elsewhere, for example that the pidls do not match byte for byte even if they represent the same path. It will call ILIsEqualXYZ and you can see if that is where it fails. – Anders Apr 04 '18 at 22:26
  • Undocumented but on Windows 8 at least you can also pass a path as VT_BSTR and a CSIDL_* value as VT_I2. – Anders Apr 05 '18 at 21:40