Search code examples
c++winapicomatl

Creating a COM pointer that supports range-based iteration


Iterating over certain COM collection objects can be cumbersome, so I'm trying to create some COM pointers that support range-based iteration. They're derived from CComPtr. For example, here's an IShellItemArray pointer that I came up with that allows range-based iteration over its IShellItems (so you can iterate over it by just doing for (const auto psi : psia) ):

class CShellItemArrayPtr : public CComPtr<IShellItemArray>
{
public:
    using CComPtr::CComPtr;

private:
    class CIterator
    {
    public:
        CIterator(IShellItemArray* psia) : m_hr(S_FALSE)
        {
            HRESULT hr;
            hr = psia->EnumItems(&m_pesi);
            if (SUCCEEDED(hr))
                ++*this;
        }

        const CIterator& operator++ ()
        {
            m_psi.Release();
            m_hr = m_pesi->Next(1, &m_psi, NULL);
            return *this;
        }

        BOOL operator!= (const HRESULT hr) const
        {
            return m_hr != hr;
        }

        IShellItem* operator* ()
        {
            return m_psi;
        }

    private:
        CComPtr<IShellItem> m_psi;
        CComPtr<IEnumShellItems> m_pesi;
        HRESULT m_hr;
    };

public:
    CIterator begin() const
    {
        return CIterator(p);
    }

    HRESULT end() const
    {
        return S_FALSE;
    }
};

Similarly, here's an IShellWindows pointer I came up with that allows range-based iteration over its individual IWebBrowser2s:

class CShellWindowsPtr : public CComPtr<IShellWindows>
{
public:
    using CComPtr::CComPtr;

private:
    class CIterator
    {
    public:
        CIterator(IShellWindows* psw) : m_hr(S_FALSE)
        {
            HRESULT hr;
            CComPtr<IUnknown> punk;
            hr = psw->_NewEnum(&punk);
            if (SUCCEEDED(hr))
            {
                hr = punk->QueryInterface(&m_pev);
                if (SUCCEEDED(hr))
                    ++*this;
            }
        }

        const CIterator& operator++ ()
        {
            m_pwb2.Release();
            CComVariant var;
            m_hr = m_pev->Next(1, &var, NULL);
            if (m_hr == S_OK)
                var.pdispVal->QueryInterface(&m_pwb2);
            return *this;
        }

        BOOL operator!= (const HRESULT hr) const
        {
            return m_hr != hr;
        }

        IWebBrowser2* operator* () const
        {
            return m_pwb2;
        }

        CComPtr<IWebBrowser2> m_pwb2;
        CComPtr<IEnumVARIANT> m_pev;
        HRESULT m_hr;
    };

public:
    CIterator begin() const
    {
        return CIterator(p);
    }

    HRESULT end() const
    {
        return S_FALSE;
    }

};

My question is whether there's a smart way of abstracting out this iteration behavior into a more generalized (likely templated) class. I'm not really sure how to go about it, or if it's practically possible. Thank you for any input.


Solution

  • All IEnum... interfaces have a common design, even though they output different element types. That design can lend itself to C++ templates, so I would suggest separating out CIterator into a standalone template class that can iterate any IEnum... interface, and then have CShellWindowsPtr and CShellItemArrayPtr make use of that class. For example

    void CEnumRelease(CComVariant &value)
    {
        value.Clear();
    }
    
    template <typename IntfType>
    void CEnumRelease(CComPtr<IntfType> &value)
    {
        value.Release();
    }
    
    // other overloads as needed...
    
    template <typename Intf>
    void CEnumTransform(CComVariant &src, CComPtr<Intf> &dest)
    {
        if (src.vt & VT_TYPEMASK) == VT_UNKNOWN) {
            IUnknown *punk = (src.vt & VT_BYREF) ? *(src.ppunkVal) : src.punkVal;
            if (punk) punk->QueryInterface(IID_PPV_ARGS(&dest));
        }
        else if ((src.vt & VT_TYPEMASK) == VT_DISPATCH) {
            IDispatch *pdisp = (src.vt & VT_BYREF) ? *(src.ppdispVal) : src.pdispVal;
            if (pdisp) pdisp->QueryInterface(IID_PPV_ARGS(&dest));
        }
    }
    
    template <typename SrcIntf, typename DestIntf>
    void CEnumTransform(CComPtr<SrcIntf> &src, CComPtr<DestIntf> &dest)
    {
        if (src) src->QueryInterface(IID_PPV_ARGS(&dest));
    }
    
    // other overloads as needed...
    
    #include <type_traits>
    
    template<typename IEnumType, typename ElementType, typename IntermediateType = ElementType>
    class CEnumIterator
    {
    public:
        CEnumIterator() : m_enum()
        {
        }
    
        CEnumIterator(CComPtr<IEnumType> &enum) : m_enum(enum)
        {
            ++*this;
        }
    
        CEnumIterator& operator++ ()
        {
            CEnumRelease(m_currentValue);
            if (m_enum) {
                if constexpr (!std::is_same_v<IntermediateType, ElementType>) {
                    IntermediateType tmp;
                    if (m_enum->Next(1, &tmp, NULL) != S_OK)
                        m_enum.Release();
                    else
                        CEnumTransform(tmp, m_currentValue);
                }
                else {
                    if (m_enum->Next(1, &m_currentValue, NULL) != S_OK) {
                        m_enum.Release();
                }
            }
            return *this;
        }
    
        bool operator == (const CEnumIterator &rhs) const
        {
            return m_enum == rhs.m_enum;
        }
    
        bool operator != (const CEnumIterator &rhs) const
        {
            return m_enum != rhs.m_enum;
        }
    
        ElementType& operator* ()
        {
            return m_currentValue;
        }
    
    private:
        CComPtr<IEnumType> m_enum;
        ElementType m_currentValue;
    };
    
    class CShellItemArrayPtr : public CComPtr<IShellItemArray>
    {
    public:
        auto begin() const
        {
            CComPtr<IEnumShellItems> enum;
            if (p) p->EnumItems(&enum);
            return CEnumIterator<IEnumShellItems, CComPtr<IShellItem>>(enum);
        }
    
        auto end() const
        {
            return CEnumIterator<IEnumShellItems, CComPtr<IShellItem>>();
        }
    };
    
    class CShellWindowsPtr : public CComPtr<IShellWindows>
    {
    public:
        auto begin() const
        {
            CComPtr<IEnumVARIANT> enum;
            if (p) {
                CComPtr<IUnknown> punk;
                if (SUCCEEDED(p->_NewEnum(&punk) && punk)
                    punk->QueryInterface(IID_PPV_ARGS(&enum));
            }
            return CEnumIterator<IEnumVARIANT, CComPtr<IWebBrowser2>, CComVariant>(enum);
        }
    
        auto end() const
        {
            return CEnumIterator<IEnumVARIANT, CComPtr<IWebBrowser2>, CComVariant>();
        }
    };