See the question and my original answer on StackOverflow

Here is some sample Console code (uses COM) that uses the IShellWindows interface. First it dumps the current explorer windows ("views"), then it hooks events raised by the companion interface DShellWindowsEvents

#include <atlbase.h>
#include <atlcom.h>
#include <shobjidl_core.h>
#include <shlobj_core.h>
#include <exdispid.h>


// a COM class that handles DShellWindowsEvents
class WindowsEvents : public IDispatch
{
  // poor man's COM object... we don't care, we're basically a static thing here
  STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject)
  {
    if (IsEqualIID(riid, IID_IUnknown))
    {
      *ppvObject = static_cast<IUnknown*>(this);
      return S_OK;
    }

    if (IsEqualIID(riid, IID_IDispatch))
    {
      *ppvObject = static_cast<IDispatch*>(this);
      return S_OK;
    }

    *ppvObject = NULL;
    return E_NOINTERFACE;
  }
  STDMETHODIMP_(ULONG) AddRef() { return 1; }
  STDMETHODIMP_(ULONG) Release() { return 1; }

  // this is what's called by the Shell (BTW, on the same UI thread)
  // there are only two events "WindowRegistered" (opened) and "WindowRevoked" (closed)
  STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr)
  {
    // first parameter is window's registration cookie
    int cookie = V_I4(&pDispParams->rgvarg[0]);
    if (dispIdMember == DISPID_WINDOWREGISTERED) // needs exdispid.h
    {
      wprintf(L"Window registered, cookie:%u\n", cookie);
    }
    else if (dispIdMember == DISPID_WINDOWREVOKED)
    {
      wprintf(L"Window revoked, cookie:%u\n", cookie);
    }

    // currently the cookie is not super useful, it's supposed to be usable by FindWindowSW
    CComVariant empty;
    long hwnd;
    CComPtr<IDispatch> window;
    HRESULT hr = Windows->FindWindowSW(&pDispParams->rgvarg[0], &empty, 0, &hwnd, SWFO_COOKIEPASSED, &window);
    // always returns S_FALSE... it does not seem to work
    // so, you'll have to ask for all windows again...
    return S_OK;
  }

  // the rest is left not implemented
  STDMETHODIMP GetTypeInfoCount(UINT* pctinfo) { return E_NOTIMPL; }
  STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) { return E_NOTIMPL; }
  STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) { return E_NOTIMPL; }

public:
  CComPtr<IShellWindows> Windows;
};

int main()
{
  CoInitialize(NULL);
  {
    CComPtr<IShellWindows> windows;
    if (SUCCEEDED(windows.CoCreateInstance(CLSID_ShellWindows)))
    {
      // dump current windows
      long count = 0;
      windows->get_Count(&count);
      for (long i = 0; i < count; i++)
      {
        CComPtr<IDispatch> window;
        if (SUCCEEDED(windows->Item(CComVariant(i), &window)))
        {
          // get the window handle
          CComPtr<IWebBrowserApp> app;
          if (SUCCEEDED(window->QueryInterface(&app)))
          {
            HWND hwnd = NULL;
            app->get_HWND((SHANDLE_PTR*)&hwnd);
            wprintf(L"HWND[%i]:%p\n", i, hwnd);
          }
        }
      }

      // now wait for windows to open
      // get the DShellWindowsEvents dispinterface for events
      CComPtr<IConnectionPointContainer> cpc;
      if (SUCCEEDED(windows.QueryInterface(&cpc)))
      {
        // https://learn.microsoft.com/en-us/windows/win32/shell/dshellwindowsevents
        CComPtr<IConnectionPoint> cp;
        if (SUCCEEDED(cpc->FindConnectionPoint(DIID_DShellWindowsEvents, &cp)))
        {
          WindowsEvents events;
          events.Windows = windows;
          DWORD cookie = 0;

          // hook events
          if (SUCCEEDED(cp->Advise(&events, &cookie)))
          {
            // pump COM messages to make sure events arrive
            do
            {
              MSG msg;
              while (GetMessage(&msg, NULL, 0, 0))
              {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
              }
            } while (TRUE);

            // normally we should get here if someone sends a PostQuitMessage(0) to the current thread
            // but this is a console sample...

            // unhook events
            cp->Unadvise(cookie);
          }
        }
      }
    }
  }
  CoUninitialize();
  return 0;
}