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



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

我的问题是是否有一种聪明的方法将这种迭代行为抽象到一个更一般化的(可能是模板化的)类中。我不确定该怎么做,或者是否有可能。谢谢你的建议。

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

在这种情况下,在CComPtr<IShellItemArray>派生的类中使用模板会导致解析冲突。这可以通过将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);
}

最新更新