5

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:

  • Registred both x32 and x64 SNE - still old GUI
  • Removed Computer\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Cached value with my NSE GUID and restarted Explorer afterwards. Still old GUI.

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.

ElDorado
  • 448
  • 6
  • 19
  • You should see some calls to your ITransferSource implementation methods. Among the first ones, there should be ITransferSource::Advise. Do you trace all these calls? – Simon Mourier Oct 03 '22 at 17:38
  • Yes. I see `ITransferSource::Advise` is called IF and only if I disable `IDataObject` (`return E_NOINTERFACE` from `IShellFolder::GetUIObjectOf` for `IID_IDataObject`). Otherwise I see calls only to `ITransferSource::AddRef` and `ITransferSource::Release`. – ElDorado Oct 03 '22 at 17:52
  • What I can say is both should work together fine. If you provide a reproducible sample, we can have a deeper look, otherwise it's impossible to diagnose. – Simon Mourier Oct 03 '22 at 17:59
  • Are you saying the old UI is used if IDataObject is implemented as well as ITransferSource? Do you need to implement both? – Jonathan Potter Oct 03 '22 at 21:06
  • Yes, the old UI is used if both `IDataObject` and `ITransferSource` are implemented. I think I should implement both because I expect old software to handle `IDataObject` only. Also as far as I understood Drag'n'Drop and copy-paste also relies a lot on `IDataObject`. In the end I have seen that default Folders from system are implementing both, so why should not I? – ElDorado Oct 03 '22 at 21:42
  • You call `IShellFolder::GetUIObjectOf` to request an object for one of the items contained by a folder. Call `IShellFolder::CreateViewObject` to request an object for the folder itself. Could you please share [a minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example)? – Jeaninez - MSFT Oct 04 '22 at 09:19
  • @SimonMourier please find a link to reproducible example in the updated question. – ElDorado Oct 04 '22 at 21:28
  • @Jeaninez-MSFT please find a link to reproducible example in the updated question. – ElDorado Oct 04 '22 at 21:45
  • 1
    No there's no cache like this (there are lots of caches in the Shell, but not here - in doubt, just kill all explorer.exe). #1 problem : just do `SHCreateDataObject(m_pidl, cidl, apidl, NULL, riid, ppv);` don't add an inner (broken) `IDataObject` to what the Shell provides. You can just throw your `CDataObject` class away, you should never need this, the Shell does all HIDA support for you. Other things: #2 don't implement `ITransferMediumItem` unless you need to. #3 just return S_FALSE (undocumented) in `ITransferSource::OpenItem` (and throw your custom `IShellItemResources` implementation). – Simon Mourier Oct 05 '22 at 05:57
  • @SimonMourier, thanks for the advices. Indeed it was because of inner `IDataObject`. Once disabled - UI became as expected. May you please post it as an answer so I can accept it? P.S. May I ask you to explain what was broken in the `CDataObject` except that it does not support folders (I know I need to report full hierarchy in `CFSTR_FILEDESCRIPTORW` format). – ElDorado Oct 05 '22 at 09:48
  • I've not checked the exact reason. Maybe your CDataObject implementation is not broken but it's useless; no offense :-) – Simon Mourier Oct 05 '22 at 12:36

2 Answers2

3

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

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
0

For those who need to use inner IDataObject within SHCreateDataObject for additional format:

Inner IDataObject should support CFSTR_SHELLIDLIST clipboard format in GetData() as well as report this format in EnumFormatEtc(). Supporting SetData() with CFSTR_SHELLIDLIST format is really useful here since DefDataObject sets inner data object with already fully constructed CIDA and you may just store a copy of it.

Once inner data object will report CFSTR_SHELLIDLIST in EnumFormatEtc() and return valid CIDA in GetData() - modern Copy UI will be used since it relies only on PIDL of the items.

ElDorado
  • 448
  • 6
  • 19