{"id":44773,"date":"2015-02-02T07:00:00","date_gmt":"2015-02-02T22:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2015\/02\/02\/customizing-item-enumeration-with-ishellitem-the-old-fashioned-way\/"},"modified":"2019-03-13T12:12:31","modified_gmt":"2019-03-13T19:12:31","slug":"20150202-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20150202-00\/?p=44773","title":{"rendered":"Customizing item enumeration with IShellItem, the old-fashioned way"},"content":{"rendered":"<p>If you are targeting Windows&nbsp;8 or higher, you can <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2015\/01\/26\/10588645.aspx\">use <code>STR_ENUM_ITEMS_FLAGS<\/code><\/a> to customize how shell items are enumerated. But what if you need to run on older systems, too? <\/p>\n<p>In that case, you will need to drop to the lower-level <code>IShell&shy;Folder::Enum&shy;Objects<\/code> function, <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2014\/03\/17\/10508309.aspx\">like we did before<\/a>, and then reconstructe shell items from the low-level <code>IShell&shy;Folder<\/code> and <code>ITEMID_CHILD<\/code>. (Note that the term &#8220;low-level&#8221; is used here only in a relative sense; it&#8217;s lower level than <code>IShell&shy;Item<\/code>.) <\/p>\n<p>We can wrap that inside a helper class. <\/p>\n<pre>\n#define UNICODE\n#define <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/02\/12\/71851.aspx\">_UNICODE<\/a>\n#define STRICT\n#define <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2013\/01\/24\/10387757.aspx\">STRICT_TYPED_ITEMIDS<\/a>\n#include &lt;windows.h&gt;\n#include &lt;shlobj.h&gt;\n#include &lt;atlbase.h&gt;\nCComModule _Module;\n#include &lt;atlcom.h&gt;\n#include &lt;atlalloc.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;tchar.h&gt;\n\nclass CEnumItemsWithSHCONTF :\n    public CComObjectRoot,\n    public IEnumShellItems\n{\npublic:\n  BEGIN_COM_MAP(CEnumItemsWithSHCONTF)\n    COM_INTERFACE_ENTRY(IEnumShellItems)\n  END_COM_MAP()\n\n  static HRESULT Create(HWND hwnd, SHCONTF shcontf,\n                 IShellItem *psiFolder, REFIID riid, void **ppv);\n\n  STDMETHOD(Next)(ULONG celt, IShellItem **ppsi, ULONG *pceltFetched);\n  STDMETHOD(Skip)(ULONG celt);\n  STDMETHOD(Reset)();\n  STDMETHOD(Clone)(IEnumShellItems **ppesiClone);\n\nprivate:\n  static HRESULT CreateRef1(CComObject&lt;CEnumItemsWithSHCONTF&gt; **ppObj);\n  HRESULT Initialize(HWND hwnd, SHCONTF shcontf, IShellItem *psiFolder);\n  HRESULT CloneFrom(CEnumItemsWithSHCONTF *pSource);\nprivate:\n  CComPtr&lt;IShellFolder&gt; m_spsfParent;\n  CComPtr&lt;IEnumIDList&gt; m_speidl;\n};\n\nHRESULT CEnumItemsWithSHCONTF::CreateRef1(\n    CComObject&lt;CEnumItemsWithSHCONTF&gt; **ppObj)\n{\n  CComObject&lt;CEnumItemsWithSHCONTF&gt; *pObj;\n  HRESULT hr = CComObject&lt;CEnumItemsWithSHCONTF&gt;::\n                       CreateInstance(&amp;pObj);\n  *ppObj = CComPtr&lt;CComObject&lt;CEnumItemsWithSHCONTF&gt;&nbsp;&gt;(pObj).Detach();\n  return hr;\n}\n\nHRESULT CEnumItemsWithSHCONTF::Initialize(\n  HWND hwnd, SHCONTF shcontf, IShellItem *psiFolder)\n{\n  HRESULT hr = psiFolder-&gt;BindToHandler(\n    nullptr, BHID_SFObject, IID_PPV_ARGS(&amp;m_spsfParent));\n  if (SUCCEEDED(hr)) {\n    hr = m_spsfParent-&gt;EnumObjects(hwnd, shcontf, &amp;m_speidl);\n  }\n  return hr;\n}\n\nHRESULT CEnumItemsWithSHCONTF::CloneFrom(\n    CEnumItemsWithSHCONTF *pSource)\n{\n  HRESULT hr = pSource-&gt;m_speidl-&gt;Clone(&amp;m_speidl);\n  if (SUCCEEDED(hr)) {\n    m_spsfParent = pSource-&gt;m_spsfParent;\n  }\n  return hr;\n}\n\nHRESULT CEnumItemsWithSHCONTF::Create(\n    HWND hwnd, SHCONTF shcontf,\n    IShellItem *psiFolder, REFIID riid, void **ppv)\n{\n  *ppv = nullptr;\n\n  CComPtr&lt;CComObject&lt;CEnumItemsWithSHCONTF&gt;&gt; spObj;\n  HRESULT hr = CreateRef1(&amp;spObj);\n\n  if (SUCCEEDED(hr)) {\n    hr = spObj-&gt;Initialize(hwnd, shcontf, psiFolder);\n    if (SUCCEEDED(hr)) {\n      hr = spObj-&gt;QueryInterface(riid, ppv);\n    }\n  }\n  return hr;\n}\n\nHRESULT CEnumItemsWithSHCONTF::Next(\n    ULONG celt, IShellItem **ppsi, ULONG *pceltFetched)\n{\n  if (celt != 1 &amp;&amp; pceltFetched == nullptr) {\n    return E_INVALIDARG;\n  }\n\n  for (ULONG i = 0; i &lt; celt; i++) ppsi[i] = nullptr;\n\n  ULONG celtFetched = 0;\n  HRESULT hr = S_OK;\n  while (hr == S_OK &amp;&amp; celtFetched &lt; celt) {\n    CComHeapPtr&lt;ITEMID_CHILD&gt; spidlChild;\n    hr = m_speidl-&gt;Next(1, &amp;spidlChild, nullptr);\n    if (hr == S_OK) {\n      hr = SHCreateItemWithParent(nullptr, m_spsfParent,\n        spidlChild, IID_PPV_ARGS(&amp;ppsi[celtFetched]));\n      if (SUCCEEDED(hr)) celtFetched++;\n    }\n  }\n\n  if (pceltFetched != nullptr) *pceltFetched = celtFetched;\n  if (SUCCEEDED(hr)) {\n    hr = (celtFetched == celt) ? S_OK : S_FALSE;\n  }\n  return hr;\n}\n\nHRESULT CEnumItemsWithSHCONTF::Skip(ULONG celt)\n{\n  return m_speidl-&gt;Skip(celt);\n}\n\nHRESULT CEnumItemsWithSHCONTF::Reset()\n{\n  return m_speidl-&gt;Reset();\n}\n\nHRESULT CEnumItemsWithSHCONTF::Clone(\n    IEnumShellItems **ppesiClone)\n{\n  *ppesiClone = nullptr;\n\n  CComPtr&lt;CComObject&lt;CEnumItemsWithSHCONTF&gt;&gt; spClone;\n  HRESULT hr = CreateRef1(&amp;spClone);\n\n  if (SUCCEEDED(hr)) {\n    hr = spClone-&gt;CloneFrom(this);\n    if (SUCCEEDED(hr)) {\n        *ppesiClone = spClone.Detach();\n    }\n  }\n  return hr;\n}\n<\/pre>\n<p>The <code>CEnum&shy;Items&shy;With&shy;SHCONTF<\/code> class does the work of enumerating child items the old-fashioned way, then constructing shell items from the result. Most of this code is boilerplate (including the part to <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2005\/09\/29\/475298.aspx\">avoid having a live COM object with reference count zero<\/a>). <\/p>\n<p>The object has two members, the source folder from which the items are being enumerated and the low-level enumerator itself. We initialize the object by asking for the low-level <code>IEnum&shy;ID&shy;List<\/code> handler and calling <code>IEnum&shy;ID&shy;List::Enum&shy;Objects<\/code> with the specific flags we want. When it is time to generate items, we ask the inner enumerator for the next ID list, and construct a shell item around it by comining the ID list with the parent folder. <\/p>\n<p>The rest is bookkeeping: We keep track of the number of elements fetched so far in order to return it to the caller if requested, and also in order to decide what the return value should be. If all items were retrieved successfully, then return <code>S_OK<\/code>. If we ran out of items, then return <code>S_FALSE<\/code>. If something went wrong, we return the error code, possibly with partially-fetched results. <\/p>\n<p>The other enumerator operations like <code>Reset<\/code> and <code>Clone<\/code> are delegated to the inner enumerator. Cloning is a little tricky because we also have to clone ourselves! <\/p>\n<p>Now we can adapt <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2015\/01\/26\/10588645.aspx\">our program from last time<\/a> to use this class instead of <code>BHID_Enum&shy;Items<\/code>. <\/p>\n<pre>\nint __cdecl wmain(int argc, wchar_t **argv)\n{\n <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/05\/20\/135841.aspx\">CCoInitialize<\/a> init;\n\n if (argc &lt; 2) return 0;\n CComPtr&lt;IShellItem&gt; spsiFolder;\n SHCreateItemFromParsingName(argv[1], nullptr,\n                             IID_PPV_ARGS(&amp;spsiFolder));\n\n CComPtr&lt;IEnumShellItems&gt; spesi;\n <font COLOR=\"blue\">CEnumItemsWithSHCONTF::Create(nullptr, SHCONTF_FOLDERS,\n    spsiFolder, IID_PPV_ARGS(&amp;spesi));<\/font>\n for (CComPtr&lt;IShellItem&gt; spsi;\n      spesi-&gt;Next(1, &amp;spsi, nullptr) == S_OK;\n      spsi.Release()) {\n  <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2011\/08\/30\/10202076.aspx\">PrintDisplayName<\/a>(spsi, SIGDN_NORMALDISPLAY, L\"Display Name\");\n  PrintDisplayName(spsi, SIGDN_FILESYSPATH, L\"File system path\");\n  wprintf(L\"\\n\");\n }\n return 0;\n}\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Driving it manually.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-44773","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Driving it manually.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/44773","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=44773"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/44773\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=44773"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=44773"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=44773"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}