How does a shell namespace extension provide icons for virtual items that track the standard icons set by the user's file associations?


A customer asked, "What is the correct way to retrieve the icon associated with a file extension? We are writing a shell namespace extension that holds virtual file content, and we want to show the icon that would have been shown if the file were a physical file on disk rather than a virtual one. We tried using SH­Get­File­Info, expecting it to return the icon location and index, but the szDisplay­Name comes out as a blank string. (See sample program attached.) What's the right way to get the location so we can return it in our own Get­UI­Object­Of(IExtract­Icon) handler?"

#include <windows.h>
#include <iostream>

int main()
{
 SHFILEINFOW info;
 ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
 ::SHGetFileInfoW(L".txt", FILE_ATTRIBUTE_NORMAL,
    &info, sizeof(info),
    SHGFI_ICONLOCATION | SHGFI_USEFILEATTRIBUTES);
 std::wcout << info.szDisplayName << std::endl;
 std::wcout << info.iIcon << std::endl;
 return 0;
}

The location is coming out blank because the file location returned is GIL_NOT­FILENAME so there is no file name to return.

But let's look past the question to the problem. The problem is that you want to implement IShell­Folder::Get­UI­Object­Of(IExtract­Icon) for your shell namespace extension. Your plan is to create a custom implementation of IExtract­Icon and tell it to report the information you obtained from SH­Get­File­Info. The catch is that this information is lossy because IExtract­Icon::Get­Icon­Location returns additional information that is not captured by SH­Get­File­Info.

Avoid the loss of fidelity by removing the middle man. Just ask for the standard icon extractor and return that.

We start with a helper function that takes its inspiration from Get­UI­Object­Of­File but applies a little seasoning from Create­Simple­Pidl:

HRESULT GetUIObjectOfVirtualFile(HWND hwnd, LPCWSTR pszPath,
    REFIID riid, void **ppv)
{
  *ppv = nullptr;

  WIN32_FIND_DATAW fd = {};
  fd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
  CComHeapPtr<ITEMIDLIST_ABSOLUTE> spidlSimple;
  HRESULT hr = CreateSimplePidl(&fd, pszPath, &spidlSimple);
  if (FAILED(hr)) return hr;

  CComPtr<IShellFolder> spsf;
  PCUITEMID_CHILD pidlChild;
  hr = SHBindToParent(spidlSimple, IID_PPV_ARGS(&spsf), &pidlChild);
  if (FAILED(hr)) return hr;

  return spsf->GetUIObjectOf(hwnd, 1, &pidlChild, riid, NULL, ppv);
}

This helper function is like Get­UI­Object­Of­File except that it uses a simple pidl to get the UI object for a file that doesn't actually exist.

We can use this function to get the icon extractor for an arbitrary file extension.

HRESULT GetIconExtractorForExtension(
    HWND hwnd,
    PCWSTR pszExtension,
    REFIID riid,
    void **ppv)
{
 *ppv = nullptr;

 wchar_t szPath[MAX_PATH];
 HRESULT hr = StringCchPrintfW(szPath, ARRAYSIZE(szPath),
                              L"C:\\a%ls", pszExtension);
 if (FAILED(hr)) return hr;

 return GetUIObjectOfVirtualFile(hwnd, szPath, riid, ppv);
}

and then use this function when handling the request for IExtract­Icon.

if (interfaceId == IID_IExtractIconW ||
    interfaceId == IID_IExtractIconA)
{
  return GetIconExtractorForExtension(hwnd, L".txt", riid, ppv);
}
Comments (2)
  1. Still, a well articulated problem/question, with reduced sample project.

    I imagine your answer is/was well received.

  2. Kelden says:

    I would like to know how to get the icon for a printer.

    If you install the drivers from the manufacturers, they also supply images and you can see them in "Devices and Printers". But how can I load the picture?

Comments are closed.

Skip to main content