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 IShellItem
s (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 IWebBrowser2
s:
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.
CodePudding user response:
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>();
}
};