winapiwindows-shellshell-extensionsshell-namespace-extension

How to force Explorer use modern file operation dialog with Shell Namespace Extension


In my understanding currently there are two ways to copy virtual files from a Shell Namespace Extension with the Explorer so that Copy GUI will be shown to the user:

  1. Via IDataObject interface:

    Reading a file is done via IDataObject::GetData that should support CFSTR_FILEDESCRIPTORW, CFSTR_FILECONTENTS and CFSTR_SHELLIDLIST clipboard formats at very minimum. Requesting CFSTR_FILECONTENTS from IDataObject::GetData should create an IStream that is used to access the data. UI is enabled via setting FD_PROGRESSUI flag when CFSTR_FILEDESCRIPTORW is requested.

    IDataObject copy UI

  2. Via ITransferSource interface:

    Reading a file is done via ITransferSource::OpenItem requesting for IShellItemResources. Then IShellItemResources should report {4F74D1CF-680C-4EA3-8020-4BDA6792DA3C} resource as supported (GUID indicating that there is an IStream for the item). Finally an IStream is requested via parent ShellFolder::BindToObject for accessing the data. UI is handled by the Explorer itself, it is always shown.

    ITransferSource copy UI

My problem is: these two mechanisms are working just fine separately (as you can see from the screenshots). But once I enable both IDataObject from IShellFolder::GetUIObjectOf and ITransferSource from IShellFolder::CreateViewObject - the approach via IDataObject is always used leading to the old copy GUI (as on the first screenshot). I see from the trace logs that ITransferSource is requested several times, but no actions are performed, it just gets Released and destroyed right away.

So how may I force Explorer to show fancy copy GUI when copying from my Shell Namespace Extension?


A minimal reproducible example may be found here: https://github.com/BilyakA/SO_73938149


While working on Minimal Reproducible example I somehow managed to make it work as expected with both IDataObject and ITranfserSource interfaces enabled. It happened after:

  1. unregistred x64 build SNE example (regsvr32 /u)
  2. registred x32 build SNE example (it was not working in x64 explorer, root was not opening)
  3. unregistred x32
  4. registred x64 again.

Somehow new copy UI was shown to me when copying the files. But I was not able to reproduce this result constantly after I have unregistred x64 SNE, restarted explorer and registered SNE x64 again.

What I have tried:

I suspect there is some kind of cache (other than Registry) that keeps track if NSE supports ITransferSource. And since I have developed and tested without ITransferSource at the beginning - it is cached that my NSE does not support it even that I have added it later. And somehow registering 32bit reset that cache value.


Solution

  • The current code is doing something like this when asked for an IDataObject:

    HRESULT CFolderViewImplFolder::GetUIObjectOf(HWND hwnd, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT rgfReserved, void **ppv)
    {
        ....
        if (riid == IID_IDataObject)
        {
            CDataObject* dataObject = new (std::nothrow) CDataObject(this, cidl, apidl);
            return ::SHCreateDataObject(m_pidl, cidl, apidl, dataObject, riid, ppv);
        }
        ...
    }
    

    SHCreateDataObject already creates already a full-blown IDataObject with everything needed from the (optional) input PIDL(s), including all Shell formats for items in the Shell namespace (CFSTR_SHELLIDLIST, etc.).

    Passing an inner object 1) will probably conflict with what the Shell does and 2) requires a good implementation (I've not checked it).

    So you can just replace it like this:

    HRESULT CFolderViewImplFolder::GetUIObjectOf(HWND hwnd, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT rgfReserved, void **ppv)
    {
        ....
        if (riid == IID_IDataObject)
        {
            return ::SHCreateDataObject(m_pidl, cidl, apidl, NULL, riid, ppv);
        }
        ...
    }
    

    In fact, the IDataObject provided by the Shell is complete (supports SetData method, etc.) so you should never need to implement it yourself, it's more complex than it seems. You can reuse it as a general purpose IDataObject (you can pass nulls and 0 for the 4 first arguments).

    PS: the Shell also provides the "reverse" method: SHCreateShellItemArrayFromDataObject that gets a list of Shell items from an IDataObject (if the data object contains the expected clipboard formats).