#include "precomp.h" #include "imagprop.h" #include "imgprop.h" #include #pragma hdrstop static const STATPROPSTG g_cImageSummaryProps[] = { {NULL, PIDISI_CX, VT_UI4}, {NULL, PIDISI_CY, VT_UI4}, {NULL, PIDISI_RESOLUTIONX, VT_UI4}, {NULL, PIDISI_RESOLUTIONY, VT_UI4}, {NULL, PIDISI_BITDEPTH, VT_UI4}, {NULL, PIDISI_FRAMECOUNT, VT_UI4}, {NULL, PIDISI_DIMENSIONS, VT_LPWSTR}, }; HRESULT GetImageFrameCount(Image *pImage, PROPVARIANT *ppv); // simple IEnumSTATPROPSTG for FMTID_ImageSummaryInformation class CPropEnum : public IEnumSTATPROPSTG, public NonATLObject { public: CPropEnum(const STATPROPSTG *pStats, ULONG nStat); // IUnknown STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); // IEnumSTATPROPSTG STDMETHODIMP Next(ULONG celt, STATPROPSTG *rgelt, ULONG *pceltFetched); STDMETHODIMP Skip(ULONG celt); STDMETHODIMP Reset(void); STDMETHODIMP Clone(IEnumSTATPROPSTG **ppenum); private: ~CPropEnum(); LONG _cRef; ULONG _idx; const STATPROPSTG *_pStat; ULONG _nStat; FMTID _fmtid; }; CImagePropSet::CImagePropSet(Image *pimg, IShellImageData *pData, IPropertyStorage *pps, REFFMTID fmtid, FNPROPCHANGE fnCallback) : _pimg(pimg), _pData(pData), _ppsImg(pps), _cRef(1), _fDirty(FALSE), _fmtid(fmtid), _fnPropChanged(fnCallback), _fEditable(TRUE) { if (_pData) { _pData->AddRef(); _fEditable = (S_OK == _pData->IsEditable()); } if (_ppsImg) { _ppsImg->AddRef(); } _Module.Lock(); } CImagePropSet::~CImagePropSet() { ATOMICRELEASE(_ppsImg); ATOMICRELEASE(_pData); _Module.Unlock(); } // IUnknown STDMETHODIMP CImagePropSet::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CImagePropSet, IPropertyStorage), { 0 }, }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_(ULONG) CImagePropSet::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CImagePropSet::Release() { if (InterlockedDecrement(&_cRef)) return _cRef; delete this; return 0; } // IPropertyStorage methods STDMETHODIMP CImagePropSet::ReadMultiple(ULONG cpspec, const PROPSPEC rgpspec[], PROPVARIANT rgvar[]) { HRESULT hr = E_UNEXPECTED; if (FMTID_ImageSummaryInformation == _fmtid) { hr = _GetImageSummaryProps(cpspec, rgpspec, rgvar); } else if (_ppsImg) { hr = _ppsImg->ReadMultiple(cpspec, rgpspec, rgvar); } return hr; } STDMETHODIMP CImagePropSet::WriteMultiple(ULONG cpspec, const PROPSPEC rgpspec[], const PROPVARIANT rgvar[], PROPID propidNameFirst) { HRESULT hr = E_UNEXPECTED; if (!_fEditable) { hr = STG_E_ACCESSDENIED; } else if (_ppsImg) { hr = _ppsImg->WriteMultiple(cpspec, rgpspec, rgvar, propidNameFirst); if (SUCCEEDED(hr)) { _fDirty = TRUE; if (_pData && _fnPropChanged) { SHCOLUMNID scid; scid.fmtid = _fmtid; for (ULONG i=0;iReadPropertyNames(cpropid, rgpropid, rglpwstrName); } return hr; } STDMETHODIMP CImagePropSet::WritePropertyNames(ULONG cpropid, const PROPID rgpropid[], const LPOLESTR rglpwstrName[]) { HRESULT hr = E_UNEXPECTED; if (_ppsImg) { hr = _ppsImg->WritePropertyNames(cpropid, rgpropid, rglpwstrName); } return hr; } STDMETHODIMP CImagePropSet::DeletePropertyNames(ULONG cpropid, const PROPID rgpropid[]) { HRESULT hr = E_UNEXPECTED; if (_ppsImg) { hr = _ppsImg->DeletePropertyNames(cpropid, rgpropid); } return hr; } STDMETHODIMP CImagePropSet::SetClass(REFCLSID clsid) { HRESULT hr = E_UNEXPECTED; if (_ppsImg) { hr = _ppsImg->SetClass(clsid); } return hr; } STDMETHODIMP CImagePropSet::Commit(DWORD grfCommitFlags) { HRESULT hr = S_FALSE; if (_fDirty && _pData) { IPersistFile *pFile; if (SUCCEEDED(_pData->QueryInterface(IID_PPV_ARG(IPersistFile, &pFile)))) { hr = pFile->Save(NULL, FALSE); pFile->Release(); } else { hr = E_UNEXPECTED; } } return hr; } STDMETHODIMP CImagePropSet::Revert() { return E_NOTIMPL; } STDMETHODIMP CImagePropSet::Enum(IEnumSTATPROPSTG** ppenm) { HRESULT hr = E_UNEXPECTED; if (FMTID_ImageSummaryInformation == _fmtid) { CPropEnum *pEnum = new CPropEnum(g_cImageSummaryProps, ARRAYSIZE(g_cImageSummaryProps)); if (pEnum) { hr = pEnum->QueryInterface(IID_PPV_ARG(IEnumSTATPROPSTG, ppenm)); pEnum->Release(); } else { hr = E_OUTOFMEMORY; } } else if (_ppsImg) { hr = _ppsImg->Enum(ppenm); } return hr; } STDMETHODIMP CImagePropSet::Stat(STATPROPSETSTG* pstatpsstg) { HRESULT hr = S_OK; if (_ppsImg) { hr = _ppsImg->Stat(pstatpsstg); } else if (FMTID_ImageSummaryInformation == _fmtid) { ZeroMemory(pstatpsstg, sizeof(STATPROPSETSTG)); pstatpsstg->fmtid = _fmtid; pstatpsstg->grfFlags = STGM_READ | STGM_SHARE_DENY_NONE; } else { hr = E_UNEXPECTED; } if (!_fEditable) { pstatpsstg->grfFlags = STGM_READ | STGM_SHARE_DENY_NONE; } return hr; } STDMETHODIMP CImagePropSet::SetTimes(const FILETIME* pmtime, const FILETIME* pctime, const FILETIME* patime) { return E_NOTIMPL; } void CImagePropSet::SaveProps(Image *pImage, CDSA *pdsaChanges) { // enum the properties in our propertystorage and convert them to PropertyItem structs, and // save them to the given frame. if (_ppsImg) { for (int i=0;iGetItemCount();i++) { SHCOLUMNID scid; if (pdsaChanges->GetItem(i,&scid)) { if (scid.fmtid == _fmtid) { PropertyItem pi; PROPID idUnicode; PROPID idStandard; if (SUCCEEDED(_MapPropidToImgPropid(scid.pid, &idStandard, &idUnicode))) { PROPVARIANT pv = {0}; PROPSPEC ps = {PRSPEC_PROPID, scid.pid}; if (SUCCEEDED(_ppsImg->ReadMultiple(1, &ps, &pv))) { if (pv.vt == VT_NULL || pv.vt == VT_EMPTY) { if (idUnicode) { pImage->RemovePropertyItem(idUnicode); } pImage->RemovePropertyItem(idStandard); } else if (SUCCEEDED(_PropVarToImgProp(idUnicode?idUnicode:idStandard, &pv, &pi, idUnicode?TRUE:FALSE))) { // if SetPropertyItem fails what should we do? // for now just ignore it and move on if (Ok == pImage->SetPropertyItem(&pi)) { if (idUnicode) { // remove the old ascii tag. pImage->RemovePropertyItem(idStandard); } } delete [] (BYTE*)pi.value; } PropVariantClear(&pv); } } } } } } _fDirty = FALSE; } // Helper functions HRESULT CImagePropSet::_PropVarToImgProp(PROPID pid, const PROPVARIANT *ppv, PropertyItem *pprop, BOOL bUnicode) { HRESULT hr = S_OK; CHAR szValue[MAX_PATH*2]; void *pBits = NULL; ULONG cbData = 0; szValue[0] = 0; SAFEARRAY *psa = NULL; switch (ppv->vt) { case VT_UI1: pprop->type = PropertyTagTypeByte; cbData = sizeof(ppv->bVal); pBits = (void *)&ppv->bVal; break; case VT_UI2: pprop->type = PropertyTagTypeShort; cbData = sizeof(ppv->uiVal); pBits = (void *)&ppv->uiVal; break; case VT_UI4: pprop->type = PropertyTagTypeLong; cbData = sizeof(ppv->ulVal); pBits = (void *)&ppv->ulVal; break; case VT_LPSTR: if (!bUnicode) { pprop->type = PropertyTagTypeASCII; cbData = sizeof(CHAR)*(lstrlenA(ppv->pszVal)+1); pBits = ppv->pszVal ? ppv->pszVal : szValue; } else { pprop->type = PropertyTagTypeByte; cbData = SHAnsiToUnicode(ppv->pszVal, (LPWSTR)szValue, sizeof(szValue)/sizeof(WCHAR))*sizeof(WCHAR); pBits = szValue; } break; case VT_BSTR: if (!bUnicode) { pprop->type = PropertyTagTypeASCII; cbData = sizeof(CHAR)*SHUnicodeToAnsi(ppv->bstrVal, szValue, ARRAYSIZE(szValue)); pBits = szValue; } else { pprop->type = PropertyTagTypeByte; cbData = sizeof(WCHAR)*(1+lstrlenW(ppv->bstrVal)); pBits = ppv->bstrVal; } break; case VT_LPWSTR: if (!bUnicode) { pprop->type = PropertyTagTypeASCII; cbData = sizeof(CHAR)*SHUnicodeToAnsi(ppv->pwszVal, szValue, ARRAYSIZE(szValue)); pBits = szValue; } else { pprop->type = PropertyTagTypeByte; cbData = sizeof(WCHAR)*(1+lstrlenW(ppv->pwszVal)); pBits = ppv->pwszVal; } break; case VT_UI1|VT_ARRAY: pprop->type = PropertyTagTypeByte; psa = ppv->parray; hr = SafeArrayAccessData(psa, &pBits); if (SUCCEEDED(hr)) { SafeArrayGetUBound(psa, 1, (LONG*)&cbData); } break; case VT_UI4|VT_ARRAY: pprop->type = PropertyTagTypeLong; psa = ppv->parray; hr = SafeArrayAccessData(psa, &pBits); if (SUCCEEDED(hr)) { SafeArrayGetUBound(psa, 1, (LONG*)&cbData); } break; // we ignore rational values because we can't convert back to numerator/denominator pairs case VT_R8: default: hr = E_INVALIDARG; break; } if (SUCCEEDED(hr)) { pprop->id = pid; pprop->length = cbData; pprop->value = (void **)new BYTE[cbData]; if (pprop->value) { CopyMemory(pprop->value, pBits, cbData); } else { hr = E_OUTOFMEMORY; } } if (psa) { SafeArrayUnaccessData(psa); } return hr; } typedef HRESULT (CALLBACK* PROPPROC)(Image *pimg, PROPVARIANT *ppv); const static struct { FMTID fmtid; PROPID pid; PROPPROC fnPropProc; } c_aPropList [] = { {PSGUID_SUMMARYINFORMATION, PIDSI_PAGECOUNT, GetImageFrameCount}, }; #define UNI_AUTHOR 0x001 #define UNI_COMMENT 0x002 #define UNI_TITLE 0x004 #define UNI_KEYWORD 0x008 #define UNI_SUBJECT 0x010 const static struct { FMTID fmtid; PROPID propid; PROPID imgPropid; PROPID imgPropidUnicode; DWORD dwMask; } c_rgImagePropertyMap[] = { {PSGUID_SUMMARYINFORMATION, PIDSI_TITLE, PropertyTagImageDescription, PropertyTagUnicodeDescription, UNI_TITLE}, {PSGUID_SUMMARYINFORMATION, PIDSI_COMMENT, 0, PropertyTagUnicodeComment, UNI_COMMENT}, {PSGUID_SUMMARYINFORMATION, PIDSI_AUTHOR, PropertyTagArtist, PropertyTagUnicodeArtist, UNI_AUTHOR}, {PSGUID_SUMMARYINFORMATION, PIDSI_APPNAME, PropertyTagSoftwareUsed,0}, {PSGUID_SUMMARYINFORMATION, PIDSI_CREATE_DTM, PropertyTagDateTime,0}, // some tags have no standard EXIF/TIFF equivalent {PSGUID_SUMMARYINFORMATION, PIDSI_KEYWORDS, 0, PropertyTagUnicodeKeywords, UNI_KEYWORD}, {PSGUID_SUMMARYINFORMATION, PIDSI_SUBJECT, 0, PropertyTagUnicodeSubject, UNI_SUBJECT}, }; BOOL IsAsciiPropertyPresent(PROPID pidUnicode, PROPID *aid, UINT cProperties) { // first find the ASCII value UINT i; BOOL bRet = FALSE; PROPID pidAscii = 0; for (i=0;!pidAscii && iGetPropertyCount(); PROPSPEC pspec; pspec.ulKind = PRSPEC_PROPID; PROPVARIANT pvar = {0}; // create a simple mask for determining which of the unicode properties are written // if they aren't written we will special case them and write empty strings with VT_LPWSTR type DWORD dwUnicodeWritten = 0; if (cProperties) { PROPID *aid = new PROPID[cProperties]; if (aid) { if (Ok == _pimg->GetPropertyIdList(cProperties, aid)) { BOOL bUnicode; for (UINT i = 0; i < cProperties; i++) { if (SUCCEEDED(_MapImgPropidToPropid(aid[i], &pspec.propid, &bUnicode))) { if (!bUnicode || !IsAsciiPropertyPresent(aid[i], aid, cProperties)) { UINT cbSize = _pimg->GetPropertyItemSize(aid[i]); if (cbSize) { PropertyItem *ppi = (PropertyItem*)LocalAlloc(LPTR, cbSize); if (ppi) { if (Ok == _pimg->GetPropertyItem(aid[i], cbSize, ppi)) { if (SUCCEEDED(_PropImgToPropvar(ppi, &pvar, bUnicode))) { _ppsImg->WriteMultiple(1, &pspec, &pvar,2); if (_fmtid == FMTID_SummaryInformation) { _UpdateUnicodeMask(&dwUnicodeWritten, aid[i]); } PropVariantClear(&pvar); } } LocalFree(ppi); } } } } } } delete [] aid; } } // // Some properties are derived from other means than EXIF or TIFF tags, cycle // through the property list and add properties from callback functions // for (int i=0;iWriteMultiple(1, &pspec, &pvar,2); PropVariantClear(&pvar); } } // // Write the empty unicode strings if needed // if (_fEditable && _fmtid == FMTID_SummaryInformation) { PropVariantInit(&pvar); pvar.vt = VT_LPWSTR; pvar.pwszVal = L""; if (pvar.pwszVal) { for (int i=0;iWriteMultiple(1, &pspec, &pvar, 2); } } } // don't clear the propvar since we didn't heap alloc the string } return S_OK; } HRESULT StrDupNW(LPCWSTR psz, WCHAR **ppwsz, DWORD cch) { WCHAR *pwsz; DWORD cb = cch*sizeof(WCHAR); if (psz) { if (psz[cch-1] != L'\0') { cb+=sizeof(WCHAR); // need space for NULL } pwsz = (WCHAR *)CoTaskMemAlloc(cb); } else pwsz = NULL; *((PVOID UNALIGNED64 *) ppwsz) = pwsz; if (pwsz) { pwsz[(cb/sizeof(WCHAR))-1] = L'\0'; memcpy(pwsz, psz, cch*sizeof(WCHAR)); return S_OK; } return E_OUTOFMEMORY; } HRESULT CImagePropSet::_PropImgToPropvar(PropertyItem *pi, PROPVARIANT *pvar, BOOL bUnicode) { HRESULT hr = S_OK; if (!pi->length) { return E_FAIL; } switch (pi->type) { case PropertyTagTypeByte: pvar->vt = VT_UI1; // check for multi-valued property and convert to safearray or unicode string if found if (pi->length > sizeof(UCHAR)) { if (!bUnicode) { SAFEARRAYBOUND bound; bound.cElements = pi->length/sizeof(UCHAR); bound.lLbound = 0; pvar->vt |= VT_ARRAY; hr = E_OUTOFMEMORY; pvar->parray = SafeArrayCreate(VT_UI1, 1, &bound); if (pvar->parray) { void *pv; hr = SafeArrayAccessData(pvar->parray, &pv); if (SUCCEEDED(hr)) { CopyMemory(pv, pi->value, pi->length); SafeArrayUnaccessData(pvar->parray); } else { SafeArrayDestroy(pvar->parray); } } } else { pvar->vt = VT_LPWSTR; hr = StrDupNW((LPCWSTR)pi->value, &pvar->pwszVal, pi->length/sizeof(WCHAR)); } } else { pvar->bVal = *((UCHAR*)pi->value); } break; case PropertyTagTypeShort: pvar->vt = VT_UI2; pvar->uiVal = *((USHORT*)pi->value); break; case PropertyTagTypeLong: pvar->vt = VT_UI4; if (pi->length > sizeof(ULONG)) { SAFEARRAYBOUND bound; bound.cElements = pi->length/sizeof(ULONG); bound.lLbound = 0; pvar->vt |= VT_ARRAY; hr = E_OUTOFMEMORY; pvar->parray = SafeArrayCreate(VT_UI4, 1, &bound); if (pvar->parray) { void *pv; hr = SafeArrayAccessData (pvar->parray, &pv); if (SUCCEEDED(hr)) { CopyMemory (pv, pi->value, pi->length); SafeArrayUnaccessData(pvar->parray); } else { SafeArrayDestroy(pvar->parray); } } } else { pvar->ulVal = *((ULONG*)pi->value); } break; case PropertyTagTypeASCII: // special case for date taken if (_fmtid == FMTID_ImageProperties && pi->id == PropertyTagExifDTOrig) { SYSTEMTIME st = {0}; sscanf((LPSTR)pi->value, "%hd:%hd:%hd %hd:%hd:%hd", &st.wYear, &st.wMonth, &st.wDay, &st.wHour, &st.wMinute, &st.wSecond); if (st.wYear) { FILETIME ftUTC; FILETIME ftLocal; // we expect cameras to return local times. Need to convert to UTC. SystemTimeToFileTime(&st, &ftLocal); LocalFileTimeToFileTime(&ftLocal, &ftUTC); FileTimeToSystemTime(&ftUTC, &st); SystemTimeToVariantTime(&st, &pvar->date); pvar->vt = VT_DATE; } else { pvar->vt = VT_EMPTY; } } else { hr = SHStrDupA(pi->value ? (LPSTR)pi->value : "", &pvar->pwszVal); if (SUCCEEDED(hr)) { pvar->vt = VT_LPWSTR; } } break; case PropertyTagTypeSRational: case PropertyTagTypeRational: { LONG *pl = (LONG*)pi->value; LONG num = pl[0]; LONG den = pl[1]; pvar->vt = VT_R8; if (0 == den) pvar->dblVal = 0; // don't divide by zero else pvar->dblVal = ((double)num)/((double)den); break; } case PropertyTagTypeUndefined: case PropertyTagTypeSLONG: default: hr = E_UNEXPECTED; break; } return hr; } HRESULT CImagePropSet::_MapPropidToImgPropid(PROPID propid, PROPID *ppid, PROPID *pidUnicode) { HRESULT hr; *ppid = 0; *pidUnicode = 0; if (_fmtid == FMTID_ImageProperties) { *ppid = propid; // these go into the EXIF header hr = S_OK; } else { hr = E_FAIL; for (int i = 0; i < ARRAYSIZE(c_rgImagePropertyMap); i++) { if (c_rgImagePropertyMap[i].fmtid == _fmtid && c_rgImagePropertyMap[i].propid == propid) { *ppid = c_rgImagePropertyMap[i].imgPropid; *pidUnicode = c_rgImagePropertyMap[i].imgPropidUnicode; hr = S_OK; break; } } } return hr; } HRESULT CImagePropSet::_MapImgPropidToPropid(PROPID propid, PROPID *ppid, BOOL *pbUnicode) { HRESULT hr; *pbUnicode = FALSE; if (_fmtid == FMTID_ImageProperties) { *ppid = propid; // EXIF properties don't need to be mapped hr = S_OK; } else { *ppid = 0; hr = E_FAIL; for (int i = 0; i < ARRAYSIZE(c_rgImagePropertyMap); i++) { if (c_rgImagePropertyMap[i].fmtid == _fmtid && (c_rgImagePropertyMap[i].imgPropid == propid || c_rgImagePropertyMap[i].imgPropidUnicode == propid)) { *ppid = c_rgImagePropertyMap[i].propid; *pbUnicode = (c_rgImagePropertyMap[i].imgPropidUnicode == propid); hr = S_OK; break; } } } return hr; } HRESULT GetImageFrameCount(Image *pImage, PROPVARIANT *ppv) { HRESULT hr = S_FALSE; LONG lCount; lCount = 1; //Default to 1 image UINT uiDim = pImage->GetFrameDimensionsCount(); ppv->vt = VT_EMPTY; if (uiDim) { GUID *pDim = new GUID[uiDim]; if (pDim) { if (Ok == pImage->GetFrameDimensionsList(pDim, uiDim)) { lCount = 0; ULONG uiN; for (ULONG i=0;iGetFrameCount(&pDim[i]); lCount += uiN; } ppv->vt = VT_UI4; ppv->lVal = lCount; hr = S_OK; } delete [] pDim; } else { hr = E_OUTOFMEMORY; } } return hr; } HRESULT CImagePropSet::_GetImageSummaryProps(ULONG cpspec, const PROPSPEC rgpspec[], PROPVARIANT rgvar[]) { HRESULT hr = E_FAIL; if (_pimg) { hr = S_OK; for (ULONG i = 0; i < cpspec; i++) { PropVariantInit(&rgvar[i]); rgvar[i].vt = VT_UI4; switch (rgpspec[i].propid) { case PIDISI_CX: rgvar[i].ulVal = _pimg->GetWidth(); break; case PIDISI_CY: rgvar[i].ulVal = _pimg->GetHeight(); break; case PIDISI_RESOLUTIONX: rgvar[i].ulVal = (ULONG)_pimg->GetHorizontalResolution(); break; case PIDISI_RESOLUTIONY: rgvar[i].ulVal = (ULONG)_pimg->GetVerticalResolution(); break; case PIDISI_BITDEPTH: { PixelFormat pf = _pimg->GetPixelFormat(); rgvar[i].ulVal = (pf >> 8) & 0xff; } break; case PIDISI_FRAMECOUNT: hr = GetImageFrameCount(_pimg, &rgvar[i]); break; case PIDISI_DIMENSIONS: { TCHAR szFmt[64]; if (LoadString(_Module.GetModuleInstance(), IDS_DIMENSIONS_FMT, szFmt, ARRAYSIZE(szFmt))) { DWORD_PTR args[2]; args[0] = (DWORD_PTR)_pimg->GetWidth(); args[1] = (DWORD_PTR)_pimg->GetHeight(); TCHAR szBuffer[64]; FormatMessage(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY, szFmt, 0, 0, szBuffer, ARRAYSIZE(szBuffer), (va_list*)args); hr = SHStrDup(szBuffer, &rgvar[i].pwszVal); if (SUCCEEDED(hr)) rgvar[i].vt = VT_LPWSTR; else rgvar[i].vt = VT_EMPTY; } break; } default: rgvar[i].vt = VT_EMPTY; hr = S_FALSE; break; } } } return hr; } STDMETHODIMP CPropEnum::Next(ULONG celt, STATPROPSTG *rgelt, ULONG *pceltFetched) { HRESULT hr = S_OK; ULONG uRet = 0; if (pceltFetched) { *pceltFetched = 0; } for (;_idx < _nStat && uRet < celt;_idx++) { rgelt[uRet] = _pStat[_idx]; uRet++; } if (uRet < celt) { hr = S_FALSE; } if (pceltFetched) { *pceltFetched = uRet; } return hr; } STDMETHODIMP CPropEnum::Skip(ULONG celt) { HRESULT hr = S_OK; ULONG ul = min(_idx+celt, _nStat); if (ul - _idx < celt) { hr = S_FALSE; } _idx = ul; return hr; } STDMETHODIMP CPropEnum::Reset(void) { _idx = 0; return S_OK; } STDMETHODIMP CPropEnum::Clone(IEnumSTATPROPSTG **ppenum) { HRESULT hr = E_OUTOFMEMORY; CPropEnum *pNew = new CPropEnum(_pStat, _nStat); if (pNew) { hr = pNew->QueryInterface(IID_PPV_ARG(IEnumSTATPROPSTG, ppenum)); pNew->Release(); } return hr; } STDMETHODIMP CPropEnum::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CPropEnum, IEnumSTATPROPSTG), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } STDMETHODIMP_(ULONG) CPropEnum::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CPropEnum::Release() { if (InterlockedDecrement(&_cRef)) return _cRef; delete this; return 0; } CPropEnum::CPropEnum(const STATPROPSTG *pStats, ULONG nStats) : _idx(0), _cRef(1), _pStat(pStats), _nStat(nStats) { _Module.Lock(); } CPropEnum::~CPropEnum() { _Module.Unlock(); }