/******************************************************************************* // Copyright Microsoft Corporation. All Rights Reserved. * SPDDKHLP.h * *------------* * Description: * This is the header file for core helper functions implementation. * * *******************************************************************************/ #ifndef SPDDKHLP_h #define SPDDKHLP_h #include #ifndef SPHelper_h #include #endif #include //=== Constants ============================================================== #define sp_countof(x) ((sizeof(x) / sizeof(*(x)))) #define SP_IS_BAD_WRITE_PTR(p) ( !(p) || SPIsBadWritePtr( p, sizeof(*(p)) )) #define SP_IS_BAD_READ_PTR(p) ( !(p) || SPIsBadReadPtr( p, sizeof(*(p)) )) #define SP_IS_BAD_CODE_PTR(p) ( (FARPROC)(p) == 0 ) #define SP_IS_BAD_INTERFACE_PTR(p) ( SPIsBadInterfacePtr( (p) ) ) #define SP_IS_BAD_VARIANT_PTR(p) ( SPIsBadVARIANTPtr( (p) ) ) #define SP_IS_BAD_STRING_PTR(p) ( SPIsBadStringPtr( (p) ) ) #define SP_IS_BAD_OPTIONAL_WRITE_PTR(p) ((p) && SPIsBadWritePtr( p, sizeof(*(p)) )) #define SP_IS_BAD_OPTIONAL_READ_PTR(p) ((p) && SPIsBadReadPtr( p, sizeof(*(p)) )) #define SP_IS_BAD_OPTIONAL_INTERFACE_PTR(p) ((p) && SPIsBadInterfacePtr(p)) #define SP_IS_BAD_OPTIONAL_STRING_PTR(p) ((p) && SPIsBadStringPtr(p)) #define SP_IS_BAD_READ_ARRAY(p,cElements) ( SPIsBadReadArray( p, cElements, sizeof(*p) ) ) #define SP_IS_BAD_WRITE_ARRAY(p,cElements) ( SPIsBadWriteArray( p, cElements, sizeof(*p) ) ) #define SP_DOES_PTR_ADDITION_WRAP(p) ( SPDoesPtrAdditionWrap( p, sizeof(*p) ) ) #define SP_IS_PTR_OUT_OF_BOUNDS(p,pBound) ( SPIsPtrOutOfBounds( p, sizeof(*p), pBound ) ) #define sp_sgn(x) ( (x) == 0 ? 0 : ( (x)>0 ? 1 : -1 ) ) //=== Class, Enum, Struct, Template, and Union Declarations ================== //=== Inlines ================================================================ /*** Pointer validation functions */ inline BOOL SPIsBadStringPtr( const WCHAR * psz, ULONG cMaxChars = 0xFFFFF ) { return ((psz == NULL) || (wcslen(psz) + 1 > cMaxChars)); } inline BOOL SPIsBadReadPtr( __in_bcount(Size) const void* pMem, size_t Size ) { return (pMem == NULL) && (Size != 0); } inline BOOL SPIsBadWritePtr( __in_bcount(Size) void* pMem, size_t Size ) { return (pMem == NULL) && (Size != 0); } inline BOOL SPIsBadInterfacePtr( const IUnknown* pUnknown ) { return (pUnknown == NULL); } inline BOOL SPIsBadVARIANTPtr( const VARIANT* pVar ) { return (pVar == NULL); } inline BOOL SPDoesPtrAdditionWrap( __in_bcount(Size) const void* pMem, size_t Size ) { return ((const BYTE*)pMem + Size) < (const BYTE*)pMem; } inline BOOL SPIsBadReadArray( const void* pMem, ULONG cElementCount, size_t cBytesPerElement) { size_t cByteCount = cBytesPerElement * cElementCount; if ( // Does the multiplication operation overflow? ( ( cByteCount / cBytesPerElement ) != cElementCount ) || // Does the pointer addition wrap? ( SPDoesPtrAdditionWrap ( pMem, cByteCount ) ) || // Can we read from the pointer? ( SPIsBadReadPtr ( pMem, cByteCount ) ) ) { // One of the preconditions didn't hold. // YES, this IS a BAD Read array. return TRUE; } else { // NO, this is NOT a BAD Read array. return FALSE; } } inline BOOL SPIsBadWriteArray( void* pMem, ULONG cElementCount, size_t cBytesPerElement) { size_t cByteCount = cBytesPerElement * cElementCount; if ( // Did the multiplication operation overflow? ( ( cByteCount / cBytesPerElement ) != cElementCount ) || // Does the pointer addition wrap? ( SPDoesPtrAdditionWrap ( pMem, cByteCount ) ) || // Can we write to this pointer? ( SPIsBadWritePtr ( pMem, cByteCount ) ) ) { // One of the preconditions didn't hold. // YES, this IS a BAD Write array. return TRUE; } else { // NO, this is NOT a BAD Write array. return FALSE; } } /************************************************************************ * SPIsPtrOutOfBounds * * -------------------------------* * Description: * Ensures that ( n_pStart + n_cBytes ) doesn't wrap/overflow, and * doesn't scroll up to or past n_pBound. * * Returns: * FALSE Memory block is in bounds * TRUE Pointer arithmetic overflow, or * ( n_pStart + n_cBytes ) >= n_pBound * ************************************************************** mishav ***/ inline BOOL SPIsPtrOutOfBounds(__in_bcount(n_cBytes) const void* n_pStart, size_t n_cBytes, const void* n_pBound) { const void* pEnd = (const BYTE*)n_pStart + n_cBytes; // In order to be valid, the pointers MUST be arranged // in the following order: // n_pStart <= pEnd < n_pBound. if ( ( n_pStart <= pEnd ) && ( pEnd <= n_pBound) ) { return FALSE; } else { return TRUE; } } inline BOOL SPIsArrayOutOfBounds(const void* n_pStart, ULONG n_cElements, size_t n_cBytesPerElement, const void* n_pBound) { // Perform and check the multiplication size_t cBytesTotal = n_cElements * n_cBytesPerElement; if ( ( cBytesTotal / n_cElements ) != n_cBytesPerElement ) { return FALSE; } else { return SPIsPtrOutOfBounds(n_pStart, cBytesTotal, n_pBound); } } inline BOOL SPIsStringOutOfBounds(const WCHAR* n_pwcszString, const void* n_pBound = NULL, const void* n_pLowerBound = NULL) { BOOL br = FALSE; const WCHAR* pwcszBound = (const WCHAR*)n_pBound; const WCHAR* pwcszLowerBound = (const WCHAR*)n_pLowerBound; // Pointers must be in the following order: // Lower bound, String, Upper bound if ( (pwcszLowerBound && (pwcszLowerBound > n_pwcszString) ) || (pwcszBound && (n_pwcszString >= pwcszBound) ) || SP_IS_BAD_STRING_PTR(n_pwcszString) ) { br = TRUE; } else if (pwcszBound) { const size_t cStrMaxLen = pwcszBound - n_pwcszString; const size_t cStrActualLen = wcslen(n_pwcszString); if (cStrActualLen > cStrMaxLen) { br = TRUE; } } else { br = FALSE; } return br; } /************************************************************ * SpSafePtrAdd * *----------------* * Description: * Advances the n_ppBase pointer by n_cBytes bytes. * Returns E_INVALIDARG on pointer wraparound or if * the pointer exceeds the bound (presuming the bound * is specified). ******************************************* mishav *********/ template inline HRESULT SpSafePtrAdd (_C** n_ppBase, size_t n_cBytes, const void* n_pBound = NULL) { HRESULT hr = S_OK; // Perform the addition const BYTE* pByteCast = (const BYTE*)(*n_ppBase); const BYTE* pByteAdded = pByteCast + n_cBytes; // Check for wrapping // Check for a bound having been specified, and make sure it doesn't exceed it. if ( (pByteAdded < pByteCast) || (n_pBound!=NULL && pByteAdded>n_pBound) ) { hr = E_INVALIDARG; } else { *n_ppBase = (_C*)(pByteAdded); } return hr; } /************************************************************ * SpSafePtrMultiplyAdd * *---------------------* * Description: * Advances the n_ppBase pointer by (n_cElements * n_cBytesPerElement) bytes. * Returns E_INVALIDARG on pointer wraparound or if * the pointer exceeds the bound (presuming the bound * is specified). ******************************************* mishav *********/ template inline HRESULT SpSafePtrMultiplyAdd (_C** n_ppBase, ULONG n_cElements, size_t n_cBytesPerElement, const void* n_pBound = NULL) { HRESULT hr = S_OK; // Perform and check the multiplication ULONG cBytesTotal = n_cElements * n_cBytesPerElement; if ( ( cBytesTotal / n_cElements ) != n_cBytesPerElement ) { hr = E_INVALIDARG; } else { hr = SpSafePtrAdd(n_ppBase, cBytesTotal, n_pBound); } return hr; } #ifdef __ATLCOM_H__ //--- Only enable these if ATL is being used // // Helper functions can be used to implement GetObjectToken/SetObjectToken for objects that // support ISpObjectWithToken // inline HRESULT SpGenericSetObjectToken(ISpObjectToken * pCallersToken, CComPtr & cpObjToken) { HRESULT hr = S_OK; if (SP_IS_BAD_INTERFACE_PTR(pCallersToken)) { hr = E_INVALIDARG; } else { if (cpObjToken) { hr = SPERR_ALREADY_INITIALIZED; } else { cpObjToken = pCallersToken; } } return hr; } inline HRESULT SpGenericGetObjectToken(ISpObjectToken ** ppCallersToken, CComPtr & cpObjToken) { HRESULT hr = S_OK; if (SP_IS_BAD_WRITE_PTR(ppCallersToken)) { hr = E_POINTER; } else { *ppCallersToken = cpObjToken; if (*ppCallersToken) { (*ppCallersToken)->AddRef(); } else { hr = S_FALSE; } } return hr; } #endif // __ATLCOM_H__ // // Helper class for SPSTATEINFO sturcture automatically initializes and cleans up // the structure + provides a few helper functions. // class CSpStateInfo : public SPSTATEINFO { public: CSpStateInfo() { cAllocatedEntries = NULL; pTransitions = NULL; } ~CSpStateInfo() { ::CoTaskMemFree(pTransitions); } SPTRANSITIONENTRY * FirstEpsilon() { return pTransitions; } SPTRANSITIONENTRY * FirstRule() { return pTransitions + cEpsilons; } SPTRANSITIONENTRY * FirstWord() { return pTransitions + cEpsilons + cRules; } SPTRANSITIONENTRY * FirstSpecialTransition() { return pTransitions + cEpsilons + cRules + cWords; } }; // // This basic queue implementation can be used to maintin linked lists of classes. The class T // must contain the member m_pNext which is used by this template to point to the next element. // If the bPurgeWhenDeleted is TRUE then all of the elements in the queue will be deleted // when the queue is deleted, otherwise they will not. // If bMaintainCount is TRUE then a running count will be maintained, and GetCount() will be // efficent. If it is FALSE then a running count will not be maintained, and GetCount() will // be an order N operation. If you do not require a count, then // template class CSpBasicList; template class CSpBasicQueue { public: T * m_pHead; T * m_pTail; ULONG m_cElements; // Warning! Use GetCount() -- Not maintained if bMaintainCount is FALSE. CSpBasicQueue() { m_pHead = NULL; if (bMaintainCount) { m_cElements = 0; } } ~CSpBasicQueue() { if (bPurgeWhenDeleted) { Purge(); } } HRESULT CreateNode(T ** ppNode) { *ppNode = new T; if (*ppNode) { return S_OK; } else { return E_OUTOFMEMORY; } } T * GetNext(const T * pNode) { return pNode->m_pNext; } T * Item(ULONG i) { T * pNode = m_pHead; while (pNode && i) { i--; pNode = pNode->m_pNext; } return pNode; } void InsertAfter(T * pPrev, T * pNewNode) { if (pPrev) { pNewNode->m_pNext = pPrev->m_pNext; pPrev->m_pNext = pNewNode; if (pNewNode->m_pNext == NULL) { m_pTail = pNewNode; } if (bMaintainCount) ++m_cElements; } else { InsertHead(pNewNode); } } void InsertTail(T * pNode) { pNode->m_pNext = NULL; if (m_pHead) { m_pTail->m_pNext = pNode; } else { m_pHead = pNode; } m_pTail = pNode; if (bMaintainCount) ++m_cElements; } void InsertHead(T * pNode) { pNode->m_pNext = m_pHead; if (m_pHead == NULL) { m_pTail = pNode; } m_pHead = pNode; if (bMaintainCount) ++m_cElements; } T * RemoveHead() { T * pNode = m_pHead; if (pNode) { m_pHead = pNode->m_pNext; if (bMaintainCount) --m_cElements; } return pNode; } T * RemoveTail() { T * pNode = m_pHead; if (pNode) { if (pNode == m_pTail) { m_pHead = NULL; } else { T * pPrev; do { pPrev = pNode; pNode = pNode->m_pNext; } while ( pNode != m_pTail ); pPrev->m_pNext = NULL; m_pTail = pPrev; } if (bMaintainCount) --m_cElements; } return pNode; } void Purge() { while (m_pHead) { T * pDie = m_pHead; m_pHead = pDie->m_pNext; delete pDie; } if (bMaintainCount) m_cElements = 0; } void ExplicitPurge() { T * pDie; BYTE * pb; while (m_pHead) { pDie = m_pHead; m_pHead = pDie->m_pNext; pDie->~T(); pb = reinterpret_cast(pDie); delete [] pb; } if (bMaintainCount) m_cElements = 0; } T * GetTail() const { if (m_pHead) { return m_pTail; } return NULL; } T * GetHead() const { return m_pHead; } BOOL IsEmpty() const { return m_pHead == NULL; } BOOL Remove(T * pNode) { if (m_pHead == pNode) { m_pHead = pNode->m_pNext; if (bMaintainCount) --m_cElements; return TRUE; } else { T * pCur = m_pHead; while (pCur) { T * pNext = pCur->m_pNext; if (pNext == pNode) { if ((pCur->m_pNext = pNode->m_pNext) == NULL) { m_pTail = pCur; } if (bMaintainCount) --m_cElements; return TRUE; } pCur = pNext; } } return FALSE; } void MoveAllToHeadOf(CSpBasicQueue & DestQueue) { if (m_pHead) { m_pTail->m_pNext = DestQueue.m_pHead; if (DestQueue.m_pHead == NULL) { DestQueue.m_pTail = m_pTail; } DestQueue.m_pHead = m_pHead; m_pHead = NULL; if (bMaintainCount) { DestQueue.m_cElements += m_cElements; m_cElements = 0; } } } void MoveAllToList(CSpBasicList & List) { if (m_pHead) { m_pTail->m_pNext = List.m_pFirst; List.m_pFirst = m_pHead; m_pHead = NULL; } if (bMaintainCount) { m_cElements = 0; } } BOOL MoveToList(T * pNode, CSpBasicList & List) { BOOL bFound = Remove(pNode); if (bFound) { List.AddNode(pNode); } return bFound; } ULONG GetCount() const { if (bMaintainCount) { return m_cElements; } else { ULONG c = 0; for (T * pNode = m_pHead; pNode; pNode = pNode->m_pNext, c++) {} return c; } } // // The following functions require the class T to implement a static function: // // LONG Compare(const T * pElem1, const T * pElem2) // // which returns < 0 if pElem1 is less than pElem2, 0 if they are equal, and > 0 if // pElem1 is greater than pElem2. // void InsertSorted(T * pNode) { if (m_pHead) { if (T::Compare(pNode, m_pTail) >= 0) { pNode->m_pNext = NULL; m_pTail->m_pNext = pNode; m_pTail = pNode; } else { // // We don't have to worry about walking off of the end of the list here since // we have already checked the tail. // T ** ppNext = &m_pHead; while (T::Compare(pNode, *ppNext) >= 0) { ppNext = &((*ppNext)->m_pNext); } pNode->m_pNext = *ppNext; *ppNext = pNode; } } else { pNode->m_pNext = NULL; m_pHead = m_pTail = pNode; } if (bMaintainCount) ++m_cElements; } HRESULT InsertSortedUnique(T * pNode) { HRESULT hr = S_OK; if (m_pHead) { if (T::Compare(pNode, m_pTail) > 0) { pNode->m_pNext = NULL; m_pTail->m_pNext = pNode; m_pTail = pNode; } else { // // We don't have to worry about walking off of the end of the list here since // we have already checked the tail. // T ** ppNext = &m_pHead; while (T::Compare(pNode, *ppNext) > 0) { ppNext = &((*ppNext)->m_pNext); } if (T::Compare(pNode, *ppNext) != 0) { pNode->m_pNext = *ppNext; *ppNext = pNode; } else { delete pNode; hr = S_FALSE; } } } else { pNode->m_pNext = NULL; m_pHead = m_pTail = pNode; } if (bMaintainCount) ++m_cElements; return hr; } // // These functions must support the "==" operator for the TFIND type. // template T * Find(TFIND & FindVal) const { T * pNode = m_pHead; for (; pNode && (!(*pNode == FindVal)); pNode = pNode->m_pNext) {} return pNode; } template T * FindNext(const T * pCurNode, TFIND & FindVal) const { for (T * pNode = pCurNode->m_pNext; pNode && (!(*pNode == FindVal)); pNode = pNode->m_pNext) {} return pNode; } // // Searches for and removes a single list element // template T * FindAndRemove(TFIND & FindVal) { T * pNode = m_pHead; if (pNode) { if (*pNode == FindVal) { m_pHead = pNode->m_pNext; if (bMaintainCount) --m_cElements; } else { T * pPrev = pNode; for (pNode = pNode->m_pNext; pNode; pPrev = pNode, pNode = pNode->m_pNext) { if (*pNode == FindVal) { pPrev->m_pNext = pNode->m_pNext; if (pNode->m_pNext == NULL) { m_pTail = pPrev; } if (bMaintainCount) --m_cElements; break; } } } } return pNode; } // // Searches for and deletes all list elements that match // template void FindAndDeleteAll(TFIND & FindVal) { T * pNode = m_pHead; while (pNode && *pNode == FindVal) { m_pHead = pNode->m_pNext; delete pNode; if (bMaintainCount) --m_cElements; pNode = m_pHead; } T * pPrev = pNode; while (pNode) { T * pNext = pNode->m_pNext; if (*pNode == FindVal) { pPrev->m_pNext = pNext; delete pNode; if (bMaintainCount) --m_cElements; } else { pPrev = pNode; } pNode = pNext; } m_pTail = pPrev; // Just always set it in case we removed the tail. } }; template class CSpBasicList { public: T * m_pFirst; CSpBasicList() : m_pFirst(NULL) {} ~CSpBasicList() { if (bPurgeWhenDeleted) { Purge(); } } void Purge() { while (m_pFirst) { T * pNext = m_pFirst->m_pNext; delete m_pFirst; m_pFirst = pNext; } } void ExplicitPurge() { T * pDie; BYTE * pb; while (m_pFirst) { pDie = m_pFirst; m_pFirst = pDie->m_pNext; pDie->~T(); pb = reinterpret_cast(pDie); delete [] pb; } } HRESULT RemoveFirstOrAllocateNew(T ** ppNode) { if (m_pFirst) { *ppNode = m_pFirst; m_pFirst = m_pFirst->m_pNext; } else { *ppNode = new T; if (*ppNode == NULL) { return E_OUTOFMEMORY; } } return S_OK; } void AddNode(T * pNode) { pNode->m_pNext = m_pFirst; m_pFirst = pNode; } T * GetFirst() { return m_pFirst; } T * RemoveFirst() { T * pNode = m_pFirst; if (pNode) { m_pFirst = pNode->m_pNext; } return pNode; } }; #define STACK_ALLOC(TYPE, COUNT) (TYPE *)_alloca(sizeof(TYPE) * (COUNT)) #define STACK_ALLOC_AND_ZERO(TYPE, COUNT) (TYPE *)memset(_alloca(sizeof(TYPE) * (COUNT)), 0, (sizeof(TYPE) * (COUNT))) #define STACK_ALLOC_AND_COPY(TYPE, COUNT, SOURCE) (TYPE *)memcpy(_alloca(sizeof(TYPE) * (COUNT)), (SOURCE), (sizeof(TYPE) * (COUNT))) inline HRESULT SpGetSubTokenFromToken( ISpObjectToken * pToken, const WCHAR * pszSubKeyName, ISpObjectToken ** ppToken, BOOL fCreateIfNotExist = FALSE) { HRESULT hr = S_OK; if (SP_IS_BAD_INTERFACE_PTR(pToken) || SP_IS_BAD_STRING_PTR(pszSubKeyName) || SP_IS_BAD_WRITE_PTR(ppToken)) { hr = E_POINTER; } // First, either create or open the datakey for the new token CComPtr cpDataKeyForNewToken; if (SUCCEEDED(hr)) { if (fCreateIfNotExist) { hr = pToken->CreateKey(pszSubKeyName, &cpDataKeyForNewToken); } else { hr = pToken->OpenKey(pszSubKeyName, &cpDataKeyForNewToken); } } // The sub token's category will be the token id of it's parent token CSpDynamicString dstrCategoryId; if (SUCCEEDED(hr)) { hr = pToken->GetId(&dstrCategoryId); } // The sub token's token id will be it's category id + "\\" the key name CSpDynamicString dstrTokenId; if (SUCCEEDED(hr)) { dstrTokenId = dstrCategoryId; dstrTokenId.Append2(L"\\", pszSubKeyName); } // Now create the token and initalize it CComPtr cpTokenInit; if (SUCCEEDED(hr)) { hr = cpTokenInit.CoCreateInstance(CLSID_SpObjectToken); } if (SUCCEEDED(hr)) { hr = cpTokenInit->InitFromDataKey(dstrCategoryId, dstrTokenId, cpDataKeyForNewToken); } if (SUCCEEDED(hr)) { *ppToken = cpTokenInit.Detach(); } return hr; } template HRESULT SpCreateObjectFromSubToken(ISpObjectToken * pToken, const WCHAR * pszSubKeyName, T ** ppObject, IUnknown * pUnkOuter = NULL, DWORD dwClsCtxt = CLSCTX_ALL) { HRESULT hr; CComPtr cpSubToken; hr = SpGetSubTokenFromToken(pToken, pszSubKeyName, &cpSubToken); if (SUCCEEDED(hr)) { hr = SpCreateObjectFromToken(cpSubToken, ppObject, pUnkOuter, dwClsCtxt); } return hr; } __inline HRESULT GetObjectToken(const WCHAR* pszTokenCat, const WCHAR* pszTokenName, CComPtr &cpToken) { cpToken = NULL; CComPtr cpEnum; ISpObjectToken* pToken = NULL; if (SUCCEEDED(SpEnumTokens(pszTokenCat, NULL, NULL, &cpEnum))) { while (cpEnum->Next(1, &pToken, NULL) == S_OK) { CSpDynamicString dstrDesc; if (SUCCEEDED(SpGetDescription(pToken, &dstrDesc))) { if (! wcscmp(dstrDesc.m_psz, pszTokenName)) { cpToken = pToken; goto Exit; } } if (pToken) { pToken->Release(); pToken = NULL; } } } Exit: if (pToken) { pToken->Release(); } if (cpToken == NULL) { return E_FAIL; } return S_OK; } // Return a token enumerator containing all tokens that match the primary language // of a particular language id. pszRequiredAttributes can be used to specify additional attributes all tokens must have. inline HRESULT SpEnumTokensMatchingPrimaryLangID(const LPCWSTR pszCategoryId, LANGID priLangID, LPCWSTR pszRequiredAtts, IEnumSpObjectTokens **ppEnum) { HRESULT hr = S_OK; // First enumerate the tokens using pszRequiredAtts. CComPtr cpCategory; hr = SpGetCategoryFromId(pszCategoryId, &cpCategory); CComPtr cpEnum; if (SUCCEEDED(hr)) { hr = cpCategory->EnumTokens(pszRequiredAtts, NULL, &cpEnum); } ULONG ulTokens; if (SUCCEEDED(hr)) { hr = cpEnum->GetCount(&ulTokens); } // Create enumerator to store new tokens. CComPtr cpBuilder; if (SUCCEEDED(hr)) { hr = cpBuilder.CoCreateInstance(CLSID_SpObjectTokenEnum); } if (SUCCEEDED(hr)) { hr = cpBuilder->SetAttribs(NULL, NULL); } // Now, for each token, check language string to see if it matches. for (ULONG ul = 0; SUCCEEDED(hr) && ul < ulTokens; ul++) { LANGID tokenLangID; CComPtr cpToken; hr = cpEnum->Item(ul, &cpToken); if (SUCCEEDED(hr)) { // Just look at the first language id hr = SpGetLanguageFromToken(cpToken, &tokenLangID); } if (SUCCEEDED(hr) && PRIMARYLANGID(tokenLangID) == PRIMARYLANGID(priLangID)) { // Add to builder hr = cpBuilder->AddTokens(1, &(cpToken.p)); } } if (SUCCEEDED(hr)) { hr = cpBuilder->Reset(); } if (SUCCEEDED(hr)) { *ppEnum = cpBuilder.Detach(); } return hr; } #endif /* This must be the last line in the file */