March 14th, 2014

How do I create an IShellItemArray from a bunch of file paths?

The IFile­Operation interface accepts bulk operations in the form of an IShell­Item­Array. So how do you take a list of file names and convert them into an IShell­Item­Array?

There is no SHCreate­Shell­Item­Array­From­Paths function, but there is a SHCreate­Shell­Item­Array­From­ID­Lists, and we know how to convert a path to an ID list, namely via SHParse­Display­Name. So lets snap two blocks together.

#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
#include <windows.h>
#include <shlobj.h>
#include <wrl/client.h>
// class CCoInitialize incorporated by reference
template<typename T>
HRESULT CreateShellItemArrayFromPaths(
    UINT ct, T rgt[], IShellItemArray **ppsia)
{
 *ppsia = nullptr;
 PIDLIST_ABSOLUTE *rgpidl = new(std::nothrow) PIDLIST_ABSOLUTE[ct];
 HRESULT hr = rgpidl ? S_OK : E_OUTOFMEMORY;
 int cpidl;
 for (cpidl = 0; SUCCEEDED(hr) && cpidl < ct; cpidl++)
 {
  hr = SHParseDisplayName(rgt[cpidl], nullptr, &rgpidl[cpidl], 0, nullptr);
 }
 if (SUCCEEDED(hr)) {
  hr = SHCreateShellItemArrayFromIDLists(cpidl, rgpidl, ppsia);
 }
 for (int i = 0; i < cpidl; i++)
 {
  CoTaskMemFree(rgpidl[i]);
 }
 delete[] rgpidl;
 return hr;
}

The Create­Shell­Item­Array­From­Paths template function takes an array of paths and starts by creating a corresponding array of ID lists. (If you’re feeling fancy, you can use a file system bind context to make simple ID lists.) It then pumps this array into the SHCreate­Shell­Item­Array­From­ID­Lists function to get the item array.

Using a template allows you to pass an array of anything as the array of paths, as long as it has a conversion to PCWSTR. So you can pass an array of PCWSTR or an array of PWSTR or an array of BSTR or an array of CCom­Heap­Ptr<wchar_t> or an array of CStringW or whatever else floats your boat.

Let’s take this function out for a spin.

int __cdecl wmain(int argc, wchar_t **argv)
{
 CCoInitialize init;
 Microsoft::WRL::ComPtr<IShellItemArray> spsia;
 Microsoft::WRL::ComPtr<IFileOperation> spfo;
 if (SUCCEEDED(CreateShellItemArrayFromPaths(
                      argc - 1, argv + 1, &spsia)) &&
     SUCCEEDED(CoCreateInstance(__uuidof(FileOperation), nullptr,
                      CLSCTX_ALL, IID_PPV_ARGS(&spfo)))) {
  spfo->DeleteItems(spsia.Get());
  spfo->PerformOperations();
 }
 return 0;
}

The main program first treats the command line arguments as a list of absolute file paths and uses our new helper function to create a shell item array from them. It then passes the shell item array to the IFile­Operation::Delete­Items method to delete all the items.

No magic here. Just taking the pieces available and combining them in a relatively obvious way.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.