After taking advice from @IgorTandetnik, I developed a method that appears to fulfill the required task (although it's in pseudo-code):
BOOL ConvertVariantToProperties(CComVariant& var)
{
HRESULT hr;
if (var.vt != VT_DISPATCH)
return FALSE;
CComPtr<IDispatch> pDispatch = var.pdispVal;
CComQIPtr<IDispatchEx> pDispatchEx;
if(FAILED(hr = pDispatch->QueryInterface(IID_IDispatchEx, (void **)&pDispatchEx)))
return FALSE;
BSTR bstrName;
DISPID dispid;
// Assume success
BOOL success = TRUE;
// Enumerate object names
hr = pDispatchEx->GetNextDispID(fdexEnumAll, DISPID_STARTENUM, &dispid);
while (hr == NOERROR)
{
if(SUCCEEDED(hr = pDispatchEx->GetMemberName(dispid, &bstrName)))
{
// Get DISPID of item
DISPID dispidIndex = 0;
LPOLESTR pIndex = reinterpret_cast<LPOLESTR>(const_cast<WCHAR *>(bstrName));
if(SUCCEEDED(hr = pDispatch->GetIDsOfNames(IID_NULL, &pIndex, 1, LOCALE_USER_DEFAULT, &dispidIndex)))
{
CComVariant varItem;
DISPPARAMS dispParams = {0};
if(SUCCEEDED(hr = pDispatch->Invoke(dispidIndex, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &dispParams, &varItem, NULL, NULL)))
{
// Object's property name is stored in 'bstrName'
// Object's property value is stored in 'varItem'
}
else
{
ASSERT(NULL);
success = FALSE;
}
}
else
{
ASSERT(NULL);
success = FALSE;
}
}
SysFreeString(bstrName);
hr = pDispatchEx->GetNextDispID(fdexEnumAll, dispid, &dispid);
}
return success && hr == S_FALSE;
}