【问题标题】:Creating a COM pointer that supports range-based iteration创建支持基于范围的迭代的 COM 指针
【发布时间】:2021-11-17 08:13:55
【问题描述】:

迭代某些 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;
    }

};

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

【问题讨论】:

    标签: c++ winapi com atl


    【解决方案1】:

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

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

    【讨论】:

    • 只要将ElementType 设置为RAII 类型(CComPtrCComVariantCComBSTR 等),则不会。但是,如果你担心它,你总是可以添加一个析构函数来调用 CEnumRelease(),并为非 RAII 类型添加重载(原始接口指针、BSTRVARIANT 等)。我没有把它写成 100% 的生产就绪,只是为了传达如何使用模板类实现迭代器的想法。
    【解决方案2】:

    在这种情况下,在派生自CComPtr&lt;IShellItemArray&gt; 的类中使用模板会导致解决冲突。这可以通过将p 更改为CComPtr&lt;T_array&gt;::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);
        }
    

    【讨论】:

    • 谢谢。我开始自己制作一个类似的版本。 using 语句引入了 CComPtr 构造函数,这很重要。这样,该类可以包装原始指针以进行迭代。
    • 我必须删除 using CComPtr::CComPtr 否则代码无法使用模板编译。
    猜你喜欢
    • 2011-08-08
    • 1970-01-01
    • 2011-06-01
    • 1970-01-01
    • 2019-09-19
    • 2020-04-03
    • 1970-01-01
    • 1970-01-01
    • 2012-08-19
    相关资源
    最近更新 更多