Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

795 lines
18 KiB

  1. /*++
  2. Copyright (C) 2000-2001 Microsoft Corporation
  3. Module Name:
  4. Cache.h
  5. Abstract:
  6. Contains all caching classes and objects.
  7. History:
  8. a-dcrews 01-Mar-00 Created
  9. ivanbrug 23-Jun-2000 mostly rewritten
  10. --*/
  11. #ifndef _CACHE_H_
  12. #define _CACHE_H_
  13. #include <windows.h>
  14. #include <wbemcli.h>
  15. #include <wbemint.h>
  16. #include <wstlallc.h>
  17. #include <sync.h>
  18. #include "RawCooker.h"
  19. #include "CookerUtils.h"
  20. #include <wstring.h>
  21. #include <map>
  22. #include <vector>
  23. #include <functional>
  24. ///////////////////////////////////////////////////////////////////////////////
  25. //
  26. // Macro Definitions
  27. //
  28. ///////////////////////////////////////////////////////////////////////////////
  29. #define WMI_COOKER_CACHE_INCREMENT 8 // The cache size adjustment increment
  30. ///////////////////////////////////////////////////////////////////////////////
  31. //
  32. // CProperty
  33. // =========
  34. //
  35. // The base property - used for raw properties and the base
  36. // class for the CookedProperty.
  37. //
  38. ///////////////////////////////////////////////////////////////////////////////
  39. class CProperty
  40. {
  41. protected:
  42. #ifdef _VERBOSE
  43. LPWSTR m_wszName; // The property name
  44. #endif
  45. long m_lPropHandle; // The property handle
  46. CIMTYPE m_ct;
  47. public:
  48. CProperty( LPWSTR wszName, long lHandle, CIMTYPE ct );
  49. ~CProperty();
  50. #ifdef _VERBOSE
  51. LPWSTR GetName();
  52. #endif
  53. CIMTYPE GetType();
  54. long GetHandle();
  55. };
  56. ///////////////////////////////////////////////////////////////////////////////
  57. //
  58. // CCookingProperty
  59. // ================
  60. //
  61. // The cooked property - used to model the data required to
  62. // cook a property of a cooekd class
  63. //
  64. ///////////////////////////////////////////////////////////////////////////////
  65. class CCookingProperty : public CProperty
  66. {
  67. DWORD m_dwCounterType; // Counter type
  68. DWORD m_dwReqProp; // which property are needed to perform calculation
  69. CRawCooker m_Cooker; // The cooker object
  70. CProperty* m_pRawCounterProp; // The raw counter property
  71. CProperty* m_pTimeProp; // The raw time property
  72. CProperty* m_pFrequencyProp; // The raw frequency property
  73. CProperty* m_pBaseProp; // The raw base property OPTIONAL for most counters
  74. __int32 m_nSampleWindow; // The number of samples used for the computation
  75. __int32 m_nTimeWindow; // The period used for the samples
  76. unsigned __int64 m_nTimeFreq; // The timer frequency;
  77. long m_lScale; // The Scale factor (10 ^ (m_lScale))
  78. BOOL m_bUseWellKnownIfNeeded;
  79. public:
  80. CCookingProperty( LPWSTR wszName,
  81. DWORD dwCounterType,
  82. long lPropHandle,
  83. CIMTYPE ct,
  84. DWORD dwReqProp,
  85. BOOL bUseWellKnownIfNeeded);
  86. virtual ~CCookingProperty();
  87. WMISTATUS Initialize( IWbemQualifierSet* pCookingPropQualifierSet,
  88. IWbemObjectAccess* pRawAccess,
  89. IWbemQualifierSet* pCookingClassQSet);
  90. WMISTATUS Cook( DWORD dwNumSamples,
  91. __int64* aRawCounter,
  92. __int64* aBaseCounter,
  93. __int64* aTimeStamp,
  94. __int64* pnResult );
  95. CProperty* GetRawCounterProperty();
  96. CProperty* GetBaseProperty();
  97. CProperty* GetTimeProperty();
  98. HRESULT SetFrequency(IWbemObjectAccess * pObjAcc);
  99. unsigned __int64 GetFrequency(void);
  100. BOOL IsReq(DWORD ReqProp) { return (m_dwReqProp&ReqProp); };
  101. DWORD NumberOfActiveSamples() { return m_nSampleWindow; };
  102. DWORD MinSamplesRequired() { return m_nSampleWindow; };
  103. };
  104. ///////////////////////////////////////////////////////////////////////////////
  105. //
  106. // CPropertySampleCache
  107. // ====================
  108. //
  109. // For every property in each instance, we must maintain a history of
  110. // previous samples for the cooking. The type of cooking determines the
  111. // number of required samples
  112. //
  113. ///////////////////////////////////////////////////////////////////////////////
  114. class CPropertySampleCache
  115. {
  116. DWORD m_dwNumSamples; // The number of current samples
  117. DWORD m_dwTotSamples; // The size of the sample array
  118. DWORD m_dwRefreshID;
  119. __int64* m_aRawCounterVals; // The array of raw counter values
  120. __int64* m_aBaseCounterVals; // The array of base counter values
  121. __int64* m_aTimeStampVals; // The array of timestamp values
  122. public:
  123. CPropertySampleCache();
  124. ~CPropertySampleCache();
  125. WMISTATUS SetSampleInfo( DWORD dwNumActiveSamples, DWORD dwMinReqSamples );
  126. WMISTATUS SetSampleData( DWORD dwRefreshID, __int64 nRawCounter, __int64 nRawBase, __int64 nTimeStamp );
  127. WMISTATUS GetData( DWORD* pdwNumSamples, __int64** paRawCounter, __int64** paBaseCounter, __int64** paTimeStamp );
  128. };
  129. ///////////////////////////////////////////////////////////////////////////////
  130. //
  131. // CCookingInstance
  132. // ================
  133. //
  134. // The cooking instance - used to model an instance of a cooked object. Each
  135. // property maintains a cache of values that will be used to compute the
  136. // final cooked value.
  137. //
  138. ///////////////////////////////////////////////////////////////////////////////
  139. class CCookingInstance
  140. {
  141. LPWSTR m_wszKey; // The instance key
  142. IWbemObjectAccess* m_pCookingInstance; // Cooking instance data
  143. IWbemObjectAccess* m_pRawInstance; // Raw sample source
  144. CPropertySampleCache* m_aPropertySamples; // The cache of property samples for this instance
  145. DWORD m_dwNumProps;
  146. public:
  147. CCookingInstance( IWbemObjectAccess *pCookingInstance, DWORD dwNumProps );
  148. virtual ~CCookingInstance();
  149. WMISTATUS InitProperty( DWORD dwProp, DWORD dwNumActiveSamples, DWORD dwMinReqSamples );
  150. WMISTATUS SetRawSourceInstance( IWbemObjectAccess* pRawSampleSource );
  151. WMISTATUS GetRawSourceInstance( IWbemObjectAccess** ppRawSampleSource );
  152. WMISTATUS AddSample( DWORD dwRefresherInc, DWORD dwProp, __int64 nRawCounter, __int64 nRawBase, __int64 nTimeStamp );
  153. WMISTATUS GetCachedSamples( IWbemObjectAccess** ppOldSample, IWbemObjectAccess** ppNewSample );
  154. IWbemObjectAccess* GetInstance();
  155. WMISTATUS UpdateSamples();
  156. WMISTATUS CookProperty( DWORD dwProp, CCookingProperty* pProperty );
  157. LPWSTR GetKey() { return m_wszKey; }
  158. WMISTATUS Refresh( IWbemObjectAccess* pRawData, IWbemObjectAccess** ppCookedData );
  159. BOOL IsValid() {
  160. return (m_dwNumProps && m_aPropertySamples);
  161. };
  162. };
  163. ///////////////////////////////////////////////////////////////////////////////
  164. //
  165. // CRecord
  166. // =======
  167. //
  168. ///////////////////////////////////////////////////////////////////////////////
  169. template<class T>
  170. class CRecord
  171. {
  172. long m_lID; // Instance ID
  173. CRecord* m_pNext; // The next pointer in the list
  174. static long m_lRefIDGen; // The ID generator
  175. public:
  176. CRecord() : m_lID( m_lRefIDGen++ ), m_pNext( NULL ) {}
  177. virtual ~CRecord() {}
  178. void SetNext( CRecord* pRecord ) { m_pNext = pRecord; }
  179. void SetID( long lID ) { m_lID = lID; }
  180. CRecord* GetNext() { return m_pNext; }
  181. long GetID() { return m_lID; }
  182. virtual T* GetData() = 0;
  183. };
  184. ///////////////////////////////////////////////////////////////////////////////
  185. //
  186. // CObjRecord
  187. // ==========
  188. //
  189. // A hidden class used by the cache to manage elements
  190. //
  191. ///////////////////////////////////////////////////////////////////////////////
  192. template<class T>
  193. class CObjRecord : public CRecord<T>
  194. {
  195. WCHAR* m_wszKey;
  196. T* m_pObj;
  197. public:
  198. CObjRecord( T* pObj, WCHAR* wszKey ) : m_pObj( pObj ), m_wszKey( NULL )
  199. {
  200. if ( NULL != wszKey )
  201. {
  202. size_t length = wcslen( wszKey ) + 1;
  203. m_wszKey = new WCHAR[ length ];
  204. if (m_wszKey)
  205. StringCchCopyW( m_wszKey, length , wszKey );
  206. else
  207. throw CX_MemoryException();
  208. }
  209. }
  210. ~CObjRecord()
  211. {
  212. delete m_pObj;
  213. delete m_wszKey;
  214. }
  215. T* GetData(){ return m_pObj; }
  216. bool IsValueByKey( WCHAR* wszKey )
  217. {
  218. return ( 0 == _wcsicmp( m_wszKey, wszKey ) );
  219. }
  220. };
  221. ///////////////////////////////////////////////////////////////////////////////
  222. //
  223. // CCache
  224. // ======
  225. //
  226. // BT - base type
  227. // RT - record type
  228. //
  229. ///////////////////////////////////////////////////////////////////////////////
  230. template<class BT, class RT>
  231. class CCache
  232. {
  233. RT* m_pHead; // Head of list
  234. RT* m_pTail; // Tail of list
  235. RT* m_pEnumNode; // Enumerator pointer
  236. public:
  237. CCache();
  238. virtual ~CCache();
  239. WMISTATUS Add( BT* pData, WCHAR* wszKey, long* plID );
  240. WMISTATUS Remove( long lID );
  241. WMISTATUS RemoveAll();
  242. WMISTATUS GetData( long lID, BT** ppData );
  243. WMISTATUS BeginEnum();
  244. WMISTATUS Next( BT** ppData );
  245. WMISTATUS EndEnum();
  246. bool FindByKey( WCHAR* wszKey, BT* pData );
  247. };
  248. template<class T>
  249. long CRecord<T>::m_lRefIDGen = 0;
  250. template<class BT, class RT>
  251. CCache<BT,RT>::CCache() : m_pHead( NULL ), m_pTail( NULL ), m_pEnumNode( NULL )
  252. {
  253. }
  254. template<class BT, class RT>
  255. CCache<BT,RT>::~CCache()
  256. {
  257. RT* pNode = m_pHead;
  258. RT* pNext = NULL;
  259. while ( NULL != pNode )
  260. {
  261. pNext = (RT*)pNode->GetNext();
  262. delete pNode;
  263. pNode = pNext;
  264. }
  265. };
  266. template<class BT, class RT>
  267. WMISTATUS CCache<BT,RT>::Add( BT *pData, WCHAR* wszKey, long* plID )
  268. {
  269. WMISTATUS dwStatus = S_OK;
  270. if ( NULL == pData )
  271. {
  272. dwStatus = WBEM_E_INVALID_PARAMETER;
  273. }
  274. if ( SUCCEEDED( dwStatus ) )
  275. {
  276. RT* pNewRecord = new RT( pData, wszKey );
  277. if ( NULL != pNewRecord )
  278. {
  279. if ( NULL == m_pHead )
  280. {
  281. m_pHead = pNewRecord;
  282. m_pTail = pNewRecord;
  283. }
  284. else
  285. {
  286. m_pTail->SetNext( pNewRecord );
  287. m_pTail = pNewRecord;
  288. }
  289. *plID = pNewRecord->GetID();
  290. }
  291. else
  292. {
  293. dwStatus = WBEM_E_OUT_OF_MEMORY;
  294. }
  295. }
  296. return dwStatus;
  297. };
  298. template<class BT, class RT>
  299. WMISTATUS CCache<BT,RT>::Remove( long lID )
  300. {
  301. WMISTATUS dwStatus = S_FALSE;
  302. RT* pNode = m_pHead;
  303. RT* pNext = (RT*)pNode->GetNext();
  304. RT* pPrev = NULL;
  305. while ( NULL != pNode )
  306. {
  307. if ( pNode->GetID() == lID )
  308. {
  309. if ( NULL == pNext )
  310. m_pTail = pPrev;
  311. if ( NULL == pPrev )
  312. m_pHead = pNext;
  313. else
  314. pPrev->SetNext( pNext );
  315. delete pNode;
  316. dwStatus = S_OK;
  317. }
  318. pPrev = pNode;
  319. pNode = pNext;
  320. if ( NULL != pNode )
  321. pNext = (RT*)pNode->GetNext();
  322. }
  323. return dwStatus;
  324. };
  325. template<class BT, class RT>
  326. WMISTATUS CCache<BT,RT>::RemoveAll()
  327. {
  328. WMISTATUS dwStatus = S_FALSE;
  329. RT* pNode = m_pHead;
  330. RT* pNext = NULL;
  331. while ( NULL != pNode )
  332. {
  333. pNext = (RT*)pNode->GetNext();
  334. delete pNode;
  335. pNode = pNext;
  336. }
  337. m_pHead = m_pTail = NULL;
  338. return dwStatus;
  339. };
  340. template<class BT, class RT>
  341. WMISTATUS CCache<BT,RT>::GetData( long lID, BT** ppData )
  342. {
  343. WMISTATUS dwStatus = S_FALSE;
  344. RT* pNode = m_pHead;
  345. while ( NULL != pNode )
  346. {
  347. if ( pNode->GetID() == lID )
  348. {
  349. *ppData = pNode->GetData();
  350. dwStatus = S_OK;
  351. break;
  352. }
  353. else
  354. {
  355. pNode = (RT*)pNode->GetNext();
  356. }
  357. }
  358. return dwStatus;
  359. };
  360. template<class BT, class RT>
  361. WMISTATUS CCache<BT,RT>::BeginEnum()
  362. {
  363. WMISTATUS dwStatus = S_OK;
  364. m_pEnumNode = m_pHead;
  365. return dwStatus;
  366. };
  367. template<class BT, class RT>
  368. WMISTATUS CCache<BT,RT>::Next( BT** ppData )
  369. {
  370. WMISTATUS dwStatus = WBEM_S_FALSE;
  371. if ( NULL != m_pEnumNode )
  372. {
  373. *ppData = m_pEnumNode->GetData();
  374. m_pEnumNode = (RT*)m_pEnumNode->GetNext();
  375. dwStatus = S_OK;
  376. }
  377. return dwStatus;
  378. };
  379. template<class BT, class RT>
  380. WMISTATUS CCache<BT,RT>::EndEnum()
  381. {
  382. WMISTATUS dwStatus = S_OK;
  383. m_pEnumNode = NULL;
  384. return dwStatus;
  385. };
  386. template<class BT, class RT>
  387. bool CCache<BT,RT>::FindByKey( WCHAR* wszKey, BT* pData )
  388. {
  389. BT Data;
  390. bool bRet = FALSE;
  391. BeginEnum();
  392. while( WBEM_S_FALSE != Next( &Data ) )
  393. {
  394. if ( pData->IsValueByKey( wszKey ) )
  395. {
  396. *pData = Data;
  397. bRet = TRUE;
  398. break;
  399. }
  400. }
  401. EndEnum();
  402. return bRet;
  403. };
  404. //
  405. // used to add/remove an instance from the coooker
  406. // and from the fastprox enumerator
  407. //
  408. typedef struct tagEnumCookId {
  409. long CookId;
  410. long EnumId;
  411. } EnumCookId;
  412. ///////////////////////////////////////////////////////////////
  413. //
  414. // CEnumeratorManager
  415. // ==================
  416. //
  417. ///////////////////////////////////////////////////////////////
  418. class CWMISimpleObjectCooker;
  419. class CEnumeratorManager
  420. // Manages a single enumerator
  421. {
  422. private:
  423. DWORD m_dwSignature;
  424. LONG m_cRef;
  425. HRESULT m_InithRes;
  426. CCritSec m_cs;
  427. CWMISimpleObjectCooker* m_pCooker; // The class' cooker
  428. long m_lRawID; // RawID
  429. IWbemHiPerfEnum* m_pRawEnum; // The hiperf cooked enumerator
  430. IWbemHiPerfEnum* m_pCookedEnum; // The hiperf cooked enumerator
  431. IWbemClassObject* m_pCookedClass; // The class definition for the cooking class
  432. std::vector<WString,wbem_allocator<WString> > m_pKeyProps;
  433. WCHAR* m_wszCookingClassName;
  434. BOOL m_IsSingleton;
  435. // to keep track of the differences
  436. // between the raw enum and our enum
  437. DWORD m_dwUsage;
  438. std::map< ULONG_PTR , EnumCookId , std::less<ULONG_PTR> ,wbem_allocator<EnumCookId> > m_mapID;
  439. std::vector< ULONG_PTR , wbem_allocator<ULONG_PTR> > m_Delta[2];
  440. DWORD m_dwVector;
  441. // members
  442. WMISTATUS Initialize( IWbemClassObject* pRawClass );
  443. WMISTATUS CreateCookingObject( IWbemObjectAccess* pRawObject, IWbemObjectAccess** ppCookedObject );
  444. WMISTATUS InsertCookingRecord( IWbemObjectAccess* pRawObject, EnumCookId * pStruct, DWORD dwRefreshStamp );
  445. WMISTATUS RemoveCookingRecord( EnumCookId * pEnumCookId );
  446. WMISTATUS GetRawEnumObjects( std::vector<IWbemObjectAccess*, wbem_allocator<IWbemObjectAccess*> > & refArray,
  447. std::vector<ULONG_PTR, wbem_allocator<ULONG_PTR> > & refObjHashKeys);
  448. WMISTATUS UpdateEnums(std::vector<ULONG_PTR, wbem_allocator<ULONG_PTR> > & apObjAccess);
  449. public:
  450. CEnumeratorManager( LPCWSTR wszCookingClass, IWbemClassObject* pCookedClass, IWbemClassObject* pRawClass, IWbemHiPerfEnum* pCookedEnum, IWbemHiPerfEnum* pRawEnum, long lRawID );
  451. virtual ~CEnumeratorManager();
  452. HRESULT GetInithResult(){ return m_InithRes; };
  453. WMISTATUS Refresh( DWORD dwRefreshStamp );
  454. long GetRawId(void){ return m_lRawID; };
  455. LONG AddRef();
  456. LONG Release();
  457. };
  458. ///////////////////////////////////////////////////////////////
  459. //
  460. // CEnumeratorCache
  461. // ================
  462. //
  463. ///////////////////////////////////////////////////////////////
  464. class CEnumeratorCache
  465. {
  466. DWORD m_dwRefreshStamp; // The refresh counter
  467. DWORD m_dwEnum; // The enumerator index
  468. std::vector<CEnumeratorManager*, wbem_allocator<CEnumeratorManager*> > m_apEnumerators;
  469. CCritSec m_cs;
  470. WMISTATUS Initialize();
  471. public:
  472. CEnumeratorCache();
  473. virtual ~CEnumeratorCache();
  474. WMISTATUS AddEnum(
  475. LPCWSTR wszCookingClass,
  476. IWbemClassObject* pCookedClass,
  477. IWbemClassObject* pRawClass,
  478. IWbemHiPerfEnum* pCookedEnum,
  479. IWbemHiPerfEnum* pRawEnum,
  480. long lID,
  481. DWORD* pdwID );
  482. WMISTATUS RemoveEnum( DWORD dwID , long * pRawId);
  483. WMISTATUS Refresh(DWORD dwRefreshStamp);
  484. };
  485. //
  486. // Simple Cache based on the std::map
  487. // It will use the ID semantics:
  488. // Insertion will return an ID that need to be
  489. // used for deletion
  490. // ids are unique for the lifetime of the Cache object
  491. //
  492. template <class T>
  493. class IdCache
  494. {
  495. private:
  496. std::map<DWORD, T, std::less<DWORD>, wbem_allocator<T> > m_map;
  497. DWORD m_NextId;
  498. typename std::map<DWORD, T, std::less<DWORD>, wbem_allocator<T> >::iterator m_it;
  499. CCritSec m_cs;
  500. public:
  501. IdCache():m_NextId(0){};
  502. virtual ~IdCache(){};
  503. void Lock(){ m_cs.Enter(); }
  504. void Unlock(){m_cs.Leave();};
  505. // traditional interfaces
  506. HRESULT Add( DWORD * pId, T Elem);
  507. HRESULT GetData(DWORD Id, T * pElem);
  508. HRESULT Remove(DWORD Id, T * pRemovedElem);
  509. // before calling this, delete the elements
  510. HRESULT RemoveAll(void);
  511. // Enumerator Style
  512. HRESULT BeginEnum(void);
  513. HRESULT Next(T * pElem);
  514. HRESULT EndEnum(void);
  515. };
  516. template <class T>
  517. HRESULT
  518. IdCache<T>::Add( DWORD * pId, T Elem)
  519. {
  520. HRESULT hr;
  521. CInCritSec ics(&m_cs);
  522. if (pId)
  523. {
  524. std::map<DWORD, T , std::less<DWORD>, wbem_allocator<T> >::iterator it = m_map.find(m_NextId);
  525. if (it != m_map.end())
  526. hr = E_FAIL;
  527. else
  528. {
  529. try
  530. {
  531. m_map[m_NextId] = Elem;
  532. *pId = m_NextId;
  533. InterlockedIncrement((PLONG)&m_NextId);
  534. hr = WBEM_S_NO_ERROR;
  535. }
  536. catch(...)
  537. {
  538. hr = E_FAIL;
  539. }
  540. }
  541. }
  542. else
  543. {
  544. hr = WBEM_E_INVALID_PARAMETER;
  545. }
  546. return hr;
  547. }
  548. template <class T>
  549. HRESULT
  550. IdCache<T>::GetData(DWORD Id, T * pElem)
  551. {
  552. CInCritSec ics(&m_cs);
  553. HRESULT hr = WBEM_S_NO_ERROR;
  554. std::map<DWORD, T , std::less<DWORD>, wbem_allocator<T> >::iterator it = m_map.find(Id);
  555. if (it != m_map.end())
  556. *pElem = (*it).second;
  557. else
  558. hr = WBEM_E_NOT_FOUND;
  559. return hr;
  560. }
  561. template <class T>
  562. HRESULT
  563. IdCache<T>::Remove(DWORD Id, T * pRemovedElem)
  564. {
  565. CInCritSec ics(&m_cs);
  566. HRESULT hr = WBEM_S_NO_ERROR;
  567. if (pRemovedElem)
  568. {
  569. std::map<DWORD, T , std::less<DWORD>, wbem_allocator<T> >::iterator it = m_map.find(Id);
  570. if (it != m_map.end())
  571. {
  572. *pRemovedElem = (*it).second;
  573. m_map.erase(it);
  574. } else
  575. hr = WBEM_E_NOT_FOUND;
  576. }
  577. else
  578. hr = WBEM_E_INVALID_PARAMETER;
  579. return hr;
  580. }
  581. //
  582. // DEVDEV Empty the cache before removing from std::map
  583. //
  584. template <class T>
  585. HRESULT
  586. IdCache<T>::RemoveAll(void)
  587. {
  588. CInCritSec ics(&m_cs);
  589. m_map.erase(m_map.begin(),m_map.end());
  590. return WBEM_S_NO_ERROR;
  591. };
  592. template <class T>
  593. HRESULT
  594. IdCache<T>::BeginEnum(void)
  595. {
  596. Lock();
  597. m_it = m_map.begin();
  598. return WBEM_S_NO_ERROR;
  599. }
  600. //
  601. // assume inside CritSec
  602. //
  603. /////////////////////////////
  604. template <class T>
  605. HRESULT
  606. IdCache<T>::Next(T * pElem)
  607. {
  608. HRESULT hr;
  609. if (pElem)
  610. {
  611. if (m_it == m_map.end())
  612. hr = WBEM_S_NO_MORE_DATA;
  613. else
  614. {
  615. *pElem = (*m_it).second;
  616. ++m_it;
  617. hr = WBEM_S_NO_ERROR;
  618. }
  619. } else
  620. hr = WBEM_E_INVALID_PARAMETER;
  621. return hr;
  622. }
  623. template <class T>
  624. HRESULT
  625. IdCache<T>::EndEnum(void)
  626. {
  627. Unlock();
  628. return WBEM_S_NO_ERROR;
  629. }
  630. #endif //_CACHE_H_