创建支持基于范围的迭代的 COM 指针

Creating a COM pointer that supports range-based iteration

迭代某些 COM 集合对象可能很麻烦,因此我试图创建一些支持基于范围的迭代的 COM 指针。它们源自 CComPtr。例如,这是我想出的一个 IShellItemArray 指针,它允许对其 IShellItems 进行基于范围的迭代(因此您可以通过 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;
    }
};

类似地,这是我想出的一个 IShellWindows 指针,它允许对其单个 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;
    }

};

我的问题是是否有一种聪明的方法可以将这种迭代行为抽象成更通用的(可能是模板化的)class。我不太确定如何去做,或者是否可行。感谢您的任何意见。

所有 IEnum... 接口都有一个共同的设计,即使它们输出不同的元素类型。该设计可以适用于 C++ 模板,因此我建议将 CIterator 分离到一个独立的模板 class 中,该模板可以迭代任何 IEnum... 接口,然后有 CShellWindowsPtrCShellItemArrayPtr 利用那个 class。例如

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>();
    }
};

在从 CComPtr<IShellItemArray> 派生的 class 中使用模板会在这种情况下导致解析冲突。这可以通过将 p 更改为 CComPtr<T_array>::p.

来解决
CIterator begin() const
{
    return CIterator(CComPtr<T_array>::p);
}

现在可以添加模板,如下所示。

我 运行 仅对此代码进行了一次测试!,我只是在记录分辨率

template<typename T_array, typename T_item, typename T_enum>
class CShellItemArrayPtr : public CComPtr<T_array>
{
    class CIterator
    {
        CComPtr<T_item> m_psi;
        CComPtr<T_enum> m_pesi;
        HRESULT m_hr;
    public:
        CIterator(T_array* psia) : m_hr(S_FALSE)
        {
            HRESULT 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; }
        T_item* operator* () { return m_psi; }
    };

public:
    CIterator begin() const { return CIterator(CComPtr<T_array>::p); }
    HRESULT end() const { return S_FALSE; }
};

用法:

CShellItemArrayPtr<IShellItemArray, IShellItem, IEnumShellItems> arr;
if SUCCEEDED(filedlg->GetResults(&arr))
    for (auto it : arr)
    {
        ...
        it->GetDisplayName(SIGDN_FILESYSPATH, &ptr);
    }