Enumerating all the programs that can open a particular file extension


Today's Little Program enumerates all the applications which are registered for a particular file extension and lets the user choose one. We then open a file with that chosen program.

As always, Little Programs do little to no error checking.

#include <windows.h>
#include <ole2.h>
#include <shlobj.h>
#include <atlbase.h>
#include <atlalloc.h>
#include <vector>
#include <iostream>

std::vector<CComPtr<IAssocHandler>> LoadHandlers(
  PCWSTR extension,
  ASSOC_FILTER filter)
{
  std::vector<CComPtr<IAssocHandler>> handlers;
  CComPtr<IEnumAssocHandlers> enumerator;
  SHAssocEnumHandlers(extension, filter, &enumerator);
  for (CComPtr<IAssocHandler> handler;
       enumerator->Next(1, &handler, nullptr) == S_OK;
       handler.Release()) {
       handlers.push_back(handler);
  }
  return handlers;
}

The Load­Handlers function shows off the meat of the program: We use SHAssoc­Enum­Handlers to enumerate all the handlers for a particular extension. The results get saved into a vector.

auto
ChooseHandler(
  const std::vector<CComPtr<IAssocHandler>>& handlers,
  bool allowChooseMore) -> decltype(handlers.size())
{
  decltype(handlers.size()) i;
  for (i = 0; i < handlers.size(); i++) {
    CComHeapPtr<wchar_t> name;
    handlers[i]->GetUIName(&name);
    std::wcout << i << L": " << static_cast<PCWSTR>(name)
                             << std::endl;
  }
  if (allowChooseMore) {
    std::wcout << i << L": Show more handlers" << std::endl;
    i++;
  }

  decltype(handlers.size()) selection;
  std::wcin >> selection;
  if (std::wcin.fail()) selection = i + 1;
  return selection;
}

The Choose­Handler function prints the vector of handlers (and optionally adds a "Show more handlers" option). It collects the user's reply and returns it, using handlers.size() to represent the "Show more handlers" option, if available. If the user's input is invalid, we return a value that is out of range. (I'm assuming you don't have four billion handlers.)

int __cdecl main(int, char**)
{
  CCoInitialize init;
  ProcessReference ref;

  auto handlers = LoadHandlers(L".txt", ASSOC_FILTER_RECOMMENDED);
  auto selection = ChooseHandler(handlers, true);
  if (selection == handlers.size()) {
    handlers = LoadHandlers(L".txt", ASSOC_FILTER_NONE);
    selection = ChooseHandler(handlers, false);
  }

  if (selection < handlers.size()) {
    CComPtr<IDataObject> dobj;
    GetUIObjectOfFile(nullptr, L"C:\\windows\\win.ini",
                      IID_PPV_ARGS(&dobj));
    handlers[selection]->Invoke(dobj);
  }
  return 0;
}

And here's the main function that ties everything together.

After some initial throat-clearing, it loads up the recommended handlers for the .txt file extension and lets the user choose from among them.

If the user says "Show more handlers", then we load up all handlers and try again.

We then take the user's selection and open WIN.INI with that program.

Exercise: What is the purpose of the Process­Reference object?

Comments (13)
  1. skSdnW says:

    Exercise answer: We don't know what's on the other side of IAssocHandler::Invoke() and our process needs to stick around in case the handler is asynchronous.

  2. AC says:

    CComPtr<T>

    Where was the dice roll?

  3. Dan Bugglin says:

    Uhhh the ProcessReference object isn't referenced anywhere? Interesting. I would guess it's to force the auto-declared variables to align themselves?

  4. Dan Bugglin says:

    @skSdnW Actually we don't care what's on the other side. In fact, we EXPECT it to be asynchronous (launching a process always is). Once we Invoke, we're done so we quit. We don't care what the other process does, that's outside our scope.

  5. George says:

    I think that Raymond has provided the answer to his bonus question, at the link for ProcessReference in the code!  It goes to a previous article in 2008 that describes what it is and how it works.

  6. skSdnW says:

    @The MAZZTer: It has nothing to do with alignment. Click on its link and you see the important bits in the constructor and destructor.

  7. Medinoc says:

    So this enumerates everything, from the stuff in HKEY_CLASSES_ROOT to the stuff in ExplorerFileExts?

  8. FA85 says:

    The constructor of ProcessReference calls SHSetInstanceExplorer to "prevent their host process from closing prematurely" (from MSDN). I'm not sure, but I think it's for preventing the last Explorer thread to entirely quit and stay alive before the Little Program quits, because the enumeration won't work further then....?

  9. meh says:

    "I'm assuming you don't have four billion handlers"... I guess that should be "I'm assuming you don't have std::numeric_limits<decltype(handlers.size())>::max() handlers."? C++11+ is so purty on the eyes.

  10. Neil says:

    Like yme who commented on the linked page, I was initially confused that ProcessReference is a stack-allocated object with Addref and Release methods, and I think that if I ever had to write that then I'd rather pay the price for allocating an object on the heap.

  11. Lars says:

    @Neil: Then you wouldn't get the benefit of RAII...

  12. Micha says:

    Could someone please explain, why it is okay to release the handler but still use/add it to the vector?

       handler.Release()) {

       handlers.push_back(handler);

  13. Mike Dimmick says:

    @Micha: handler.Release(), being in the 'next iteration' part of the for() loop statement, is executed *after* the loop body (the push_back call). So at the point that push_back is called, the reference count is still 1.

    handlers is a vector of CComPtr. When assigning a CComPtr from another CComPtr through the copy constructor, AddRef is called on the held pointer to increment the reference count. So now the count is 2. The Release() is necessary because CComPtr::operator& returns the address of the internal pointer, which enumerator->Next will just overwrite without releasing. We have to explicitly clean up before reusing the CComPtr in this way, otherwise we'd leak.

    I'm not convinced this is clearer than using a raw pointer for the loop variable.

Comments are closed.

Skip to main content