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:
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.
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.
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:
regsvr32 /u
)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:
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.
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).