Results 1 to 11 of 11

Thread: [RESOLVED] Using IShellWindows.Register

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,650

    Resolved [RESOLVED] Using IShellWindows.Register

    I'm trying to register a shell window. I've got a UserControl that implements the following interfaces:

    Code:
    Implements oleexpimp.IServiceProvider
    Implements oleexp.IShellFolderViewDual
    Implements oleexp.IWebBrowserApp
    Implements oleexpimp.IShellBrowser
    Implements oleexp.IOleWindow
    I'm attempting to register it as follows:

    Code:
    Private pSW As oleexp.ShellWindows
    Private lCookie As Long
    Private pDisp As Object
    
    Private Sub cmdRegister_Click()
    If (pSW Is Nothing) Then
        Set pSW = New ShellWindows
        Set pDisp = ucSW1 'ucSW1 is an instance of the UserControl that implements all those interfaces.
        pSW.Register pDisp, Form1.hWnd, SWC_EXPLORER, lCookie 'SWC_BROWSER makes no difference
        
        Debug.Print "pSW->Reg'd"
    End If
    
    End Sub
    This adds something to the list of ShellWindows, and provides a non-zero cookie. But when I enumerate them, it's like a blank object. QueryInterface can't return any interface (E_NOINTERFACE). IUnknown_QueryService does not result in entry into IServiceProvider_QueryService.

    Note that I can't seem to use a class module at all. If I use a class implementing all those, pSW.Register gives a runtime error 98 'A property or method call cannot include a reference to a private object, either as an argument or as a return value'. Regardless of whether I chance pDisp to public, or create a public 'As Class1' and set pDisp to that, or pass the class direct.

  2. #2
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,671

    Re: Using IShellWindows.Register

    Please make a small demo and attach it.

    Note that I can't seem to use a class module at all. If I use a class implementing all those, pSW.Register gives a runtime error 98 'A property or method call cannot include a reference to a private object, either as an argument or as a return value'. Regardless of whether I chance pDisp to public, or create a public 'As Class1' and set pDisp to that, or pass the class direct.
    It's because marshaling of private objects is disabled by default. You can enable it. See EnablePrivateMarshaling function here.

    BTW when you use Set pDisp = ucSW1 you get the Extender outer-object reference. To get UC instance directly use Set pDisp = ucSW1.object.

  3. #3
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    5,120

    Re: Using IShellWindows.Register

    Quote Originally Posted by The trick View Post
    See EnablePrivateMarshaling function here.
    This or try using a custom VB.Form for the initial prototype -- these seem to be marshaled easier, though still being private "classes".

    cheers,
    </wqw>

  4. #4

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,650

    Re: Using IShellWindows.Register

    Quote Originally Posted by The trick View Post
    Please make a small demo and attach it.


    It's because marshaling of private objects is disabled by default. You can enable it. See EnablePrivateMarshaling function here.

    BTW when you use Set pDisp = ucSW1 you get the Extender outer-object reference. To get UC instance directly use Set pDisp = ucSW1.object.
    Using .object worked, thanks. I'm used to calling it from inside where it would immediately give the instance.
    Register is now working and other applications calliing IShellWindows trigger my code for IServiceProvider_QueryService, IOleWindow_GetWindow, and QueryService successfully hands off a reference that allows IShellBrowser_QueryActiveShellView to be entered, but I have not yet implemented IShellView to return it there.


    Next question... I'm also having trouble with RegisterPending.

    In this post you also used [in] VARIANT * arguments, but are these not valid? Because I'm getting 'Invalid procedure call or argument' and that's the only possible cause I can imagine. MSDN says the first variant must be VT_VARIANT Or VT_BYREF, and I changed the type of the variant with my pidl to that. The other is an empty variable, which I've confirmed has VT_EMPTY (0) as it's type with CopyMemory too.

    Could VB be screwing with it somehow? VarType() only returns vbVariant. But copying vt again after that confirms it's VT_VARIANT Or VT_BYREF.

    Code:
    If (pSW Is Nothing) Then
        Set pSW = New ShellWindows
        If pidl Then
            CoTaskMemFree pidl
            pidl = 0
        End If
        pidl = ILCreateFromPathW(StrPtr(App.Path))
        vrpidl = pidl
        Dim vtype As Integer
        vtype = VT_VARIANT Or VT_BYREF
        CopyMemory ByVal VarPtr(vrpidl), ByVal VarPtr(vtype), 2&
        
        Dim vtcheck As Integer
        CopyMemory ByVal VarPtr(vtcheck), ByVal VarPtr(vrpidl), 2&
        Debug.Print "vt=" & VarType(vrpidl) & ",target=" & CStr(VT_VARIANT Or VT_BYREF) & ",check=" & vtcheck
        CopyMemory ByVal VarPtr(vtcheck), ByVal VarPtr(vrpidl), 2&
        Debug.Print "vt=" & VarType(vrpidl) & ",target=" & CStr(VT_VARIANT Or VT_BYREF) & ",check=" & vtcheck
        
        pSW.RegisterPending App.ThreadID, vrpidl, vre, SWC_EXPLORER, lCookie 'Invalid Procedure Call or Argument
        pSW.Register ucSW1.object, Form1.hWnd, SWC_EXPLORER, lCookie
        
        Debug.Print "pSW->Reg'd lCookie=" & lCookie
    End If
    With
    Code:
            HRESULT RegisterPending([in] long lThreadId,
                             [in] VARIANT* pvarloc,     // will hold pidl that is being opened.
                             [in] VARIANT* pvarlocRoot, // Optional root pidl
                             [in] int swClass,
                             [out]long *plCookie);
    as the definition in the TLB.

    Edit: I'm attaching a copy but some of these interfaces aren't declared in the public versions on my typelib so note the included ones are the current development build.
    Attached Files Attached Files
    Last edited by fafalone; Jan 17th, 2022 at 06:27 AM.

  5. #5
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    5,120

    Re: Using IShellWindows.Register

    Built-in VarType function clears VT_BYREF flag on retval for sure.

    If you want ot keep VT_BYREF flag on assignment (e.g. on vLocal = vParam for vLocal to keep VT_BYREF flag if set in vParam) then you can use VariantCopy API function to take care of all the gory details.

    cheers,
    </wqw>

  6. #6

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,650

    Re: Using IShellWindows.Register

    I suspected it might be getting cleared; that's why the check is run twice (copying the first two bytes with CopyMemory), it kept coming back as good, and when I skipped calling VarType entirely, still had the problem... could it be getting cleared when passed, in which case copying it wouldn't help?

    VarType is returning VT_VARIANT alone but as I said, when I copy the memory again afterwards it's fine.

    Copying it with
    Code:
    Private Declare Function VariantCopy Lib "oleaut32" (pvargDest As Variant, ByRef pvargSrc As Variant) As Long
    
        hr = VariantCopy(vrpidl2, vrpidl)
    didn't help, after it didn't work I did check and the copy appeared successful even though the VariantCopy looks like it's supposed be taking a ByVal on the 2nd param, but that crashes VB; the type was right. Still invalid procedure call or argument.

    I think I'm going to try changing IShellWindows to pass a VarPtr(var) instead. Edit: Now I really don't understand tf is going on. Still giving 'invalid procedure call or argument' when it's just the 5 longs.
    Last edited by fafalone; Jan 17th, 2022 at 07:18 AM.

  7. #7

  8. #8
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,671

    Re: Using IShellWindows.Register

    Okay, i found out. There is seems error in documentation because RegisterPending doesn't accept PIDL. It accepts VARIANT with many different formats but integral values are CSIDL not PIDL. You could use CSIDL value directly for example:
    Code:
    pSW.RegisterPending App.ThreadID, CSIDL_APPDATA, vre, SWC_EXPLORER, lCookie
    If you want to use arbitrary path you could use it directly too:
    Code:
    pSW.RegisterPending App.ThreadID, App.Path, vre, SWC_EXPLORER, lCookie
    It also accepts IPersistIDList, IPersistFolder2, IStream, Byte Array

  9. #9

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,650

    Re: Using IShellWindows.Register

    I have the XP/2k3 source and it shows passing it to a VariantToIDList function, but it's in varutil.h but not varutil.cpp so I'm not sure exactly what it takes only that some forms of pidls are ok.

    But, passing the path works, so fantastic, much easier. I can find it by path with FindWindowSW, returning the IDispatch with my implementations.

    Unfortunately, despite all that my main goal still failed... SHOpenFolderAndSelectItems isn't finding my window at all.

    Maybe I have to register as an Explorer replacement?

    From XPSP1 (I'm not on XP but obviously 7 and 10 aren't public):

    Code:
    STDAPI SHGetIDispatchForFolder(LPCITEMIDLIST pidl, IWebBrowserApp **ppauto)
    {
        HRESULT hr = E_UNEXPECTED;
    
        if (ppauto)
            *ppauto = NULL;
    
        if (!pidl)
            return E_POINTER;
    
        // Try a cached psw if we don't need ppauto
        IShellWindows* psw = WinList_GetShellWindows(ppauto != NULL);
        if (psw)
        {
            VARIANT var;
            hr = InitVariantFromIDList(&var, pidl);
            if (SUCCEEDED(hr)) 
            {
                LONG lhwnd;
                IDispatch* pdisp;
                hr = psw->FindWindowSW(&var, PVAREMPTY, SWC_BROWSER, &lhwnd,
                        ppauto ? (SWFO_NEEDDISPATCH | SWFO_INCLUDEPENDING) : SWFO_INCLUDEPENDING,
                        &pdisp);
                if ((hr == E_PENDING) || (hr == S_FALSE))
                {
                    HRESULT hrOld = hr;
                    hr = E_FAIL;
                    CWaitForWindow *pdfwait = new CWaitForWindow();   // Setup a wait object...
                    if (pdfwait)
                    {
                        if (pdfwait->Init(psw, pidl, 0))
                        {
                            if (hrOld == S_FALSE)
                            {
                                // Startup opening a new window
                                SHELLEXECUTEINFO sei = {sizeof(sei)};
    
                                sei.lpIDList = (void *)pidl;
    
                                //
                                //  WARNING - old versions of ShellExec() didnt pay attention - ZekeL - 30-DEC-98
                                //  to whether the hwnd is in the same process or not, 
                                //  and so could fault in TryDDEShortcut().
                                //  only pass the hwnd if the shell window shares 
                                //  the same process.
                                //
                                sei.hwnd = GetShellWindow();
                                DWORD idProcess;
                                GetWindowThreadProcessId(sei.hwnd, &idProcess);
                                if (idProcess != GetCurrentProcessId())
                                    sei.hwnd = NULL;
    
                                // Everything should have been initialize to NULL(0)
                                sei.fMask = SEE_MASK_IDLIST | SEE_MASK_FLAG_DDEWAIT;
                                sei.nShow = SW_SHOWNORMAL;
    
                                hr = ShellExecuteEx(&sei) ? S_OK : S_FALSE;
                            }
    
                            while ((hr = psw->FindWindowSW(&var, PVAREMPTY, SWC_BROWSER, &lhwnd,
                                    ppauto ? (SWFO_NEEDDISPATCH | SWFO_INCLUDEPENDING) : SWFO_INCLUDEPENDING,
                                    &pdisp)) != S_OK)
                            {
                                if (FAILED(pdfwait->WaitForWindowToOpen(20 * 1000)))
                                {
                                    hr = E_ABORT;
                                    break;
                                }
                            }
                        }
                        pdfwait->CleanUp();   // No need to watch things any more...
                        pdfwait->Release(); // release our use of this object...
                    }
                }
                if (hr == S_OK && ppauto) 
                {
                    // if this fails this is because we are inside SendMessage loop
                    hr = pdisp->QueryInterface(IID_PPV_ARG(IWebBrowserApp, ppauto));
                }
                if (pdisp)
                    pdisp->Release();
                VariantClear(&var);
            }
            psw->Release();
        }
        return hr;
    }
    That appears to be how it finds the window... but it's not calling my IWebBrowserApp or IShellFolderViewDual methods.

    Code:
    SHSTDAPI SHOpenFolderAndSelectItems(LPCITEMIDLIST pidlFolder, UINT cidl, LPCITEMIDLIST *apidl, DWORD dwFlags)
    {
        HRESULT hr;
        if (0 == cidl)
        {
            // overload the 0 item case to mean pidlFolder is the full pidl to the item
            LPITEMIDLIST pidlTemp;
            hr = SHILClone(pidlFolder, &pidlTemp);
            if (SUCCEEDED(hr))
            {
                ILRemoveLastID(pidlTemp); // strip to the folder
                LPCITEMIDLIST pidl = ILFindLastID(pidlFolder);
    
                hr = SHOpenFolderAndSelectItems(pidlTemp, 1, &pidl, 0); // recurse
    
                ILFree(pidlTemp);
            }
        }
        else
        {
            IShellFolderViewDual *psfv;
            hr = OpenFolderAndGetView(pidlFolder, &psfv);
            if (SUCCEEDED(hr))
            {
                DWORD dwSelFlags = SVSI_SELECT | SVSI_FOCUSED | SVSI_DESELECTOTHERS | SVSI_ENSUREVISIBLE;
                for (UINT i = 0; i < cidl; i++)
                {
                    hr = SelectPidlInSFV(psfv, apidl[i], dwSelFlags);
                    dwSelFlags = SVSI_SELECT;   // second items append to sel
                }
               psfv->Release();
            }
        }
        return hr;
    }
    
    HRESULT SelectPidlInSFV(IShellFolderViewDual *psfv, LPCITEMIDLIST pidl, DWORD dwOpts)
    {
        VARIANT var;
        HRESULT hr = InitVariantFromIDList(&var, pidl);
        if (SUCCEEDED(hr))
        {
            hr = psfv->SelectItem(&var, dwOpts);
            VariantClear(&var);
        }
        return hr;
    }
    
    HRESULT OpenFolderAndGetView(LPCITEMIDLIST pidlFolder, IShellFolderViewDual **ppsfv)
    {
        *ppsfv = NULL;
    
        IWebBrowserApp *pauto;
        HRESULT hr = SHGetIDispatchForFolder(pidlFolder, &pauto);
        if (SUCCEEDED(hr))
        {
            HWND hwnd;
            if (SUCCEEDED(pauto->get_HWND((LONG_PTR*)&hwnd)))
            {
                // Make sure we make this the active window
                SetForegroundWindow(hwnd);
                ShowWindow(hwnd, SW_SHOWNORMAL);
            }
    
            IDispatch *pdoc;
            hr = pauto->get_Document(&pdoc);
            if (S_OK == hr) // careful, automation returns S_FALSE
            {
                hr = pdoc->QueryInterface(IID_PPV_ARG(IShellFolderViewDual, ppsfv));
                pdoc->Release();
            }
            else
                hr = E_FAIL;
            pauto->Release();
        }
        return hr;
    }
    One thought I had might be a 32/64 bit issue, but Windows Explorer is 64bit and I can access those windows.

  10. #10

  11. #11

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,650

    Re: Using IShellWindows.Register

    Edit: Nevermind. It's an issue with the Downloads folder being a special location and the thousand different ways to represent it. It's working when I register a normal folder.

    The calling app is crashing hard for now, but that's probably because I haven't written the code so all the methods are blank.


    Edit: GOT IT WORKING ENTIRELY!

    It turns out sometime after XP the API switched to using IServiceProvider (which I already had set up) for an IShellView interface, so I'm going to have to figure out when that change was made, whether it was Vista/7 or 8 or 10, because I kinda need to support 7 and IShellFolderView Dual accepts a pidl through a variant as opposed to the pure pidl IShellView accepts, the latter being far easier to work with.

    But that's just trivial details at this point.

    Thanks for all the help The_trick


    Posted a basic demo, that just does the barebones for handling apps using that API call-
    [VB6] Using IShellWindows to register for SHOpenFolderAndSelectItems
    Last edited by fafalone; Jan 19th, 2022 at 03:15 AM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width