// This is a part of the Active Template Library.
// Copyright (C) Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Active Template Library Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Active Template Library product.

#ifndef __ATLCACHE_H__
#define __ATLCACHE_H__

#pragma once

#include <atltime.h>
#include <atlutil.h>
#include <atlcoll.h>
#include <atlperf.h>
#include <atlcom.h>
#include <atlstr.h>
#include <atlsrvres.h>
#include <atldbcli.h>
#include <atlspriv.h>
#include <atlutil.h>

#pragma warning (push)
#ifndef _ATL_NO_PRAGMA_WARNINGS
#pragma warning(disable: 4511) // copy constructor could not be generated
#pragma warning(disable: 4512) // assignment operator could not be generated
#endif //!_ATL_NO_PRAGMA_WARNINGS

#pragma warning(disable: 4625) // copy constructor could not be generated because a base class copy constructor is inaccessible
#pragma warning(disable: 4626) // assignment operator could not be generated because a base class assignment operator is inaccessible

#ifndef _CPPUNWIND
#pragma warning(disable: 4702) // unreachable code
#endif
#pragma pack(push,_ATL_PACKING)
namespace ATL {

//forward declarations;
class CStdStatClass;
class CPerfStatClass;

typedef struct __CACHEITEM
{
} *HCACHEITEM;

//Implementation of a cache that stores pointers to void
extern "C" __declspec(selectany) const IID IID_IMemoryCacheClient = {0xb721b49d, 0xbb57, 0x47bc, { 0xac, 0x43, 0xa8, 0xd4, 0xc0, 0x7d, 0x18, 0x3d } };
extern "C" __declspec(selectany) const IID IID_IMemoryCache = { 0x9c6cfb46, 0xfbde, 0x4f8b, { 0xb9, 0x44, 0x2a, 0xa0, 0x5d, 0x96, 0xeb, 0x5c } };
extern "C" __declspec(selectany) const IID IID_IMemoryCacheControl = { 0x7634b28b, 0xd819, 0x409d, { 0xb9, 0x6e, 0xfc, 0x9f, 0x3a, 0xba, 0x32, 0x9f } };
extern "C" __declspec(selectany) const IID IID_IMemoryCacheStats = { 0xd4b6df2d, 0x4bc0, 0x4734, { 0x8a, 0xce, 0xb7, 0x3a, 0xb, 0x97, 0x59, 0x56 } };

__interface ATL_NO_VTABLE __declspec(uuid("b721b49d-bb57-47bc-ac43-a8d4c07d183d")) 
	IMemoryCacheClient : public IUnknown
{
	// IMemoryCacheClient methods
	STDMETHOD( Free )(const void *pvData);
};

__interface ATL_NO_VTABLE __declspec(uuid("9c6cfb46-fbde-4f8b-b944-2aa05d96eb5c")) 
	IMemoryCache : public IUnknown
{
	// IMemoryCache Methods
	STDMETHOD(Add)(LPCSTR szKey, void *pvData, DWORD dwSize, 
				FILETIME *pftExpireTime,
				HINSTANCE hInstClient, HCACHEITEM *phEntry,
				IMemoryCacheClient *pClient);

	STDMETHOD(LookupEntry)(LPCSTR szKey, HCACHEITEM * phEntry);
	STDMETHOD(GetData)(const HCACHEITEM hEntry, void **ppvData, DWORD *pdwSize) const;
	STDMETHOD(ReleaseEntry)(const HCACHEITEM hEntry);
	STDMETHOD(RemoveEntry)(const HCACHEITEM hEntry);
	STDMETHOD(RemoveEntryByKey)(LPCSTR szKey);

	STDMETHOD(Flush)();
};

__interface ATL_NO_VTABLE __declspec(uuid("7634b28b-d819-409d-b96e-fc9f3aba329f")) 
	IMemoryCacheControl : public IUnknown
{
	// IMemoryCacheControl Methods
	STDMETHOD(SetMaxAllowedSize)(DWORD dwSize);
	STDMETHOD(GetMaxAllowedSize)(DWORD *pdwSize);
	STDMETHOD(SetMaxAllowedEntries)(DWORD dwSize);
	STDMETHOD(GetMaxAllowedEntries)(DWORD *pdwSize);
	STDMETHOD(ResetCache)();
};

__interface ATL_NO_VTABLE __declspec(uuid("d4b6df2d-4bc0-4734-8ace-b73a0b975956")) 
	IMemoryCacheStats : public IUnknown
{
	// IMemoryCacheStats Methods
	STDMETHOD(ClearStats)();
	STDMETHOD(GetHitCount)(DWORD *pdwSize);
	STDMETHOD(GetMissCount)(DWORD *pdwSize);
	STDMETHOD(GetCurrentAllocSize)(DWORD *pdwSize);
	STDMETHOD(GetMaxAllocSize)(DWORD *pdwSize);
	STDMETHOD(GetCurrentEntryCount)(DWORD *pdwSize);
	STDMETHOD(GetMaxEntryCount)(DWORD *pdwSize);

};

struct DLL_CACHE_ENTRY
{
	HINSTANCE hInstDll;
	DWORD dwRefs;
	BOOL bAlive;
	CHAR szDllName[MAX_PATH];
};

inline bool operator==(const DLL_CACHE_ENTRY& entry1, const DLL_CACHE_ENTRY& entry2)
{
	return (entry1.hInstDll == entry2.hInstDll);
}

//
// IDllCache
// An interface that is used to load and unload Dlls.
//
__interface ATL_NO_VTABLE __declspec(uuid("A12478AB-D261-42f9-B525-7589143C1C97")) 
	IDllCache : public IUnknown
{
	// IDllCache methods
	virtual HINSTANCE Load(LPCSTR szFileName, void *pPeerInfo);
	virtual BOOL Free(HINSTANCE hInstance);
	virtual BOOL AddRefModule(HINSTANCE hInstance);
	virtual BOOL ReleaseModule(HINSTANCE hInstance);
	virtual HRESULT GetEntries(DWORD dwCount, DLL_CACHE_ENTRY *pEntries, DWORD *pdwCopied);
	virtual HRESULT Flush();
};

#ifndef ATL_CACHE_KEY_LENGTH
#define ATL_CACHE_KEY_LENGTH 128
#endif

typedef CFixedStringT<CStringA, ATL_CACHE_KEY_LENGTH> CFixedStringKey;

struct CFlusherCacheData
{
	CFlusherCacheData *pNext;
	CFlusherCacheData *pPrev;
	DWORD dwAccessed;

	CFlusherCacheData()
	{
		pNext = NULL;
		pPrev = NULL;
		dwAccessed = 0;
	}
};

// No flusher -- only expired entries will be removed from the cache
// Also gives the skeleton for all of the flushers
class CNoFlusher
{
public:
	void Add(CFlusherCacheData * /*pItem*/) { }
	void Remove(CFlusherCacheData * /*pItem*/) { }
	void Access(CFlusherCacheData * /*pItem*/) { }
	CFlusherCacheData * GetStart() const { return NULL; }
	CFlusherCacheData * GetNext(CFlusherCacheData * /*pCur*/) const { return NULL; }
   	void Release(CFlusherCacheData * /*pItem*/){  }
};

// Old flusher -- oldest items are flushed first
class COldFlusher
{
public:
	CFlusherCacheData * pHead;
	CFlusherCacheData * pTail;

	COldFlusher() : pHead(NULL), pTail(NULL)
	{
	}

	// Add it to the tail of the list
	void Add(CFlusherCacheData * pItem)
	{
		ATLENSURE(pItem);

		pItem->pNext = NULL;
		pItem->pPrev = pTail;
		if (pHead)
		{
			pTail->pNext = pItem;
			pTail = pItem;
		}
		else
		{
			pHead = pItem;
			pTail = pItem;
		}
	}

	void Remove(CFlusherCacheData * pItem)
	{
		ATLENSURE(pItem);

		CFlusherCacheData * pPrev = pItem->pPrev;
		CFlusherCacheData * pNext = pItem->pNext;

		if (pPrev)
			pPrev->pNext = pNext;
		else
			pHead = pNext;

		if (pNext)
			pNext->pPrev = pPrev;
		else
			pTail = pPrev;

	}

	void Access(CFlusherCacheData * /*pItem*/)
	{
	}

	void Release(CFlusherCacheData * /*pItem*/)
	{
	}

	CFlusherCacheData * GetStart() const
	{
		return pHead;
	}

	CFlusherCacheData * GetNext(CFlusherCacheData * pCur) const
	{
		if (pCur != NULL)
			return pCur->pNext;
		else
			return NULL;
	}
};

// Least recently used flusher -- the item that was accessed the longest time ago is flushed
class CLRUFlusher : public COldFlusher
{
public:
	// Move it to the tail of the list
	void Access(CFlusherCacheData * pItem)
	{
		ATLASSERT(pItem);

		Remove(pItem);
		Add(pItem);
	}
};

// Least often used flusher
class CLOUFlusher : public COldFlusher
{
public:
	// Adds to the tail of the list
	void Add(CFlusherCacheData * pItem)
	{
		ATLENSURE(pItem);
		pItem->dwAccessed = 1;
		COldFlusher::Add(pItem);
	}

	void Access(CFlusherCacheData * pItem)
	{
		ATLENSURE(pItem);
		pItem->dwAccessed++;

		CFlusherCacheData * pMark = static_cast<CFlusherCacheData *>(pItem->pPrev);
		if (!pMark)   // The item is already at the head
			return;

		if (pMark->dwAccessed >= pItem->dwAccessed) // The element before it has
			return;                                 // been accessed more times

		Remove(pItem);

		while (pMark && (pMark->dwAccessed < pItem->dwAccessed))
			pMark = static_cast<CFlusherCacheData *>(pMark->pPrev);

		// pMark points to the first element that has been accessed more times,
		// so add pItem after pMark
		if (pMark)
		{
			CFlusherCacheData *pNext = static_cast<CFlusherCacheData *>(pMark->pNext);
			pMark->pNext = pItem;
			pItem->pPrev = pMark;

			pItem->pNext = pNext;
			pNext->pPrev = pItem;
		}
		else // Ran out of items -- put it on the head
		{
			pItem->pNext = pHead;
			pItem->pPrev = NULL;
			if (pHead)
				pHead->pPrev = pItem;
			else // the list was empty
				pTail = pItem;
			pHead = pItem;
		}
	}

	// We start at the tail and move forward for this flusher
	CFlusherCacheData * GetStart() const
	{
		return pTail;
	}

	CFlusherCacheData * GetNext(CFlusherCacheData * pCur) const
	{
		if (pCur != NULL)
			return static_cast<CFlusherCacheData *>(pCur->pPrev);
		else
			return NULL;
	}
};

template <class CFirst, class CSecond>
class COrFlushers 
{
	CFirst m_First;
	CSecond m_Second;
	BOOL m_bWhich;
public:
	COrFlushers()
	{
		m_bWhich = FALSE;
	}

	BOOL Switch()
	{
		m_bWhich = !m_bWhich;
		return m_bWhich;
	}

	void Add(CFlusherCacheData * pItem) 
	{
		ATLASSERT(pItem);
		m_First.Add(pItem);
		m_Second.Add(pItem);
	}

	void Remove(CFlusherCacheData * pItem) 
	{
		ATLASSERT(pItem);
		m_First.Remove(pItem);
		m_Second.Remove(pItem);
	}

	void Access(CFlusherCacheData * pItem) 
	{
		ATLASSERT(pItem);
		m_First.Access(pItem);
		m_Second.Access(pItem);
	}
	void Release(CFlusherCacheData * pItem)
	{
		ATLASSERT(pItem);
		m_First.Release(pItem);
		m_Second.Release(pItem);
	}

	CFlusherCacheData * GetStart() const 
	{ 
		if (m_bWhich)
			return m_First.GetStart();
		else
			return m_Second.GetStart();
	}

	CFlusherCacheData * GetNext(CFlusherCacheData * pCur) const 
	{ 
		if (m_bWhich)
			return m_First.GetNext(pCur);
		else
			return m_Second.GetNext(pCur);
	}
};

struct CCullerCacheData
{
	CCullerCacheData()
	{
		pNext = NULL;
		pPrev = NULL;
		nLifespan = 0;
	}
	CCullerCacheData *pNext;
	CCullerCacheData *pPrev;
	ULONGLONG nLifespan;
	CFileTime cftExpireTime;
};

class CNoExpireCuller
{
public:
	void Add(CCullerCacheData * /*pItem*/) { }
	void Commit(CCullerCacheData * /*pItem*/) { }
	void Access(CCullerCacheData * /*pItem*/) { }
	void Remove(CCullerCacheData * /*pItem*/) { }
	void Start() { }
	BOOL IsExpired(CCullerCacheData * /*pItem*/) { return FALSE; }
	CCullerCacheData * GetExpired() { return NULL; }
	void Release(CCullerCacheData * /*pItem*/){}

};

class CExpireCuller
{
public:
	CFileTime m_cftCurrent;
	CCullerCacheData *pHead;
	CCullerCacheData *pTail;

	CExpireCuller()
	{
		pHead = NULL;
		pTail = NULL;
	}

	// Element is being added -- perform necessary initialization
	void Add(CCullerCacheData * pItem)
	{
		(pItem);
		ATLASSERT(pItem);
	}

	// Expiration data has been set -- add to main list
	// Head is the first item to expire
	// a FILETIME of 0 indicates that the item should never expire
	void Commit(CCullerCacheData * pItem)
	{
		ATLENSURE(pItem);
		if (!pHead)
		{
			pHead = pItem;
			pTail = pItem;
			pItem->pNext = NULL;
			pItem->pPrev = NULL;
			return;
		}

		if (CFileTime(pItem->cftExpireTime) == 0)
		{
			pTail->pNext = pItem;
			pItem->pPrev = pTail;
			pItem->pNext = NULL;
			pTail = pItem;
			return;
		}

		CCullerCacheData * pMark = pHead;
		while (pMark && (pMark->cftExpireTime < pItem->cftExpireTime))
			pMark = pMark->pNext;

		if (pMark) // An entry was found that expires after the added entry
		{
			CCullerCacheData *pPrev = pMark->pPrev;
			if (pPrev)
				pPrev->pNext = pItem;
			else
				pHead = pItem;

			pItem->pNext = pMark;
			pItem->pPrev = pPrev;
			pMark->pPrev = pItem;
		}
		else // Ran out of items -- put it on the tail
		{
			if (pTail)
				pTail->pNext = pItem;
			pItem->pPrev = pTail;
			pItem->pNext = NULL;
			pTail = pItem;
		}
	}

	void Access(CCullerCacheData * /*pItem*/)
	{
	}

	void Release(CCullerCacheData * /*pItem*/)
	{
	}

	void Remove(CCullerCacheData * pItem)
	{
		ATLENSURE(pItem);
		CCullerCacheData *pPrev = pItem->pPrev;
		CCullerCacheData *pNext = pItem->pNext;

		if (pPrev)
			pPrev->pNext = pNext;
		else
			pHead = pNext;

		if (pNext)
			pNext->pPrev = pPrev;
		else
			pTail = pPrev;

	}

	// About to start culling
	void Start()
	{
		m_cftCurrent = CFileTime::GetCurrentTime();
	}

	BOOL IsExpired(CCullerCacheData *pItem)
	{
		if ((pItem->cftExpireTime != 0) && 
			m_cftCurrent > pItem->cftExpireTime)
			return TRUE;

		return FALSE;
	}

	// Get the next expired entry
	CCullerCacheData * GetExpired()
	{
		if (!pHead)
			return NULL;
		if (IsExpired(pHead))
			return pHead;

		return NULL;
	}
};

class CLifetimeCuller : public CExpireCuller
{
public:
	void Add(CCullerCacheData * pItem)
	{
		ATLENSURE(pItem);
		pItem->nLifespan = 0;
		CExpireCuller::Add(pItem);
	}

	void Commit(CCullerCacheData * pItem)
	{        
		ATLENSURE(pItem);
		if (pItem->nLifespan == 0)
			pItem->cftExpireTime = 0;
		else
			pItem->cftExpireTime = CFileTime(CFileTime::GetCurrentTime().GetTime() + pItem->nLifespan);
		CExpireCuller::Commit(pItem);
	}

	void Access(CCullerCacheData * pItem)
	{
		ATLASSERT(pItem);
		CExpireCuller::Remove(pItem);
		Commit(pItem);
	}

	CCullerCacheData * GetExpired() 
	{ 
		return static_cast<CCullerCacheData *>(CExpireCuller::GetExpired());
	}
};

template <__int64 ftLifespan>
class CFixedLifetimeCuller : public CExpireCuller
{
public:
	void Commit(CCullerCacheData * pItem)
	{
		ATLASSERT(pItem);
		__int64 nLifeSpan = ftLifespan;
		if (nLifeSpan == 0)
			pItem->cftExpireTime = 0;
		else
			pItem->cftExpireTime = CFileTime::GetCurrentTime() + CFileTimeSpan(ftLifespan);

		CExpireCuller::Commit(pItem);
	}

	void Access(CCullerCacheData * pItem)
	{
		ATLASSERT(pItem);
		CExpireCuller::Remove(pItem);
		Commit(pItem);
	}

	CCullerCacheData * GetExpired() 
	{ 
		return static_cast<CCullerCacheData *>(CExpireCuller::GetExpired());
	}
};


template <class CFirst, class CSecond>
class COrCullers
{
	CFirst m_First;
	CSecond m_Second;
public:
	void Add(CCullerCacheData * pItem)
	{
		m_First.Add(pItem);
		m_Second.Add(pItem);
	}

	void Access(CCullerCacheData * pItem)
	{
		m_First.Access(pItem);
		m_Second.Access(pItem);
	}

	void Remove(CCullerCacheData * pItem)
	{
		m_First.Remove(pItem);
		m_Second.Remove(pItem);
	}

	void Start()
	{
		m_First.Start();
		m_Second.Start();
	}

	void Release(CCullerCacheData *pItem)
	{
		m_First.Release(pItem);
		m_Second.Release(pItem);
	}

	void Commit(CCullerCacheData * pItem)
	{
		m_First.Commit(pItem);
		m_Second.Commit(pItem);
	}
	CCullerCacheData * GetExpired() 
	{ 
		CCullerCacheData *pItem = m_First.GetExpired();
		if (!pItem)
			pItem = m_Second.GetExpired();

		return pItem;
	}

	BOOL IsExpired(CCullerCacheData * pItem)
	{
		return (m_First.IsExpired(pItem) || m_Second.IsExpired(pItem));
	}
};

//
//CMemoryCacheBase
// Description:
//  This class provides the implementation of a generic cache that stores
//  elements in memory. CMemoryCacheBase uses the CCacheDataBase generic
//  cache element structure to hold items in the cache. The cache is
//  implemented using the CAtlMap map class. CMemoryCache uses a wide
//  character string as it's Key type to identify entries. Entries must
//  have unique key values. If you try to add an entry with a key that
//  is exactly the same as an existing key, the existing entry will be
//  overwritten.
//
// Template Parameters:
//  T: The class that inherits from this class. This class must implement
//     void OnDestroyEntry(NodeType *pEntry);
//  DataType: Specifies the type of the element to be stored in the memory
//            cache such as CString or void*
//  NodeInfo: Specifies any additional data that should be stored in each item
//            in the cache
//  keyType, keyTrait : specifies the key type and traits (see CAtlMap)
//  Flusher : the class responsible for determining which data should be flushed
//            when the cache is at a configuration limit
//  Culler  : the class responsible for determining which data should be removed
//            from the cache due to expiration
//  SyncClass:Specifies the class that will be used for thread synchronization
//            when accessing the cache. The class interface for SyncClass must
//            be identical to that of CComCriticalSection (see atlbase.h)
//  StatClass: Class used to contain statistics about this cache.
template <class T,
		 class DataType,
		 class NodeInfo=CCacheDataBase,
		 class keyType=CFixedStringKey,
		 class KeyTrait=CStringElementTraits<CFixedStringKey >,
		 class Flusher=COldFlusher,
		 class Culler=CExpireCuller,
		 class SyncClass=CComCriticalSection,
		 class StatClass=CStdStatClass >
		 class CMemoryCacheBase
{
protected:
	typedef keyType keytype;
	struct NodeType : public __CACHEITEM,
					  public NodeInfo, 
	   				  public CFlusherCacheData,
					  public CCullerCacheData
	{
		NodeType()
		{
			pos = NULL;
			dwSize = 0;
			dwRef = 0;
		}

		DataType Data;
		POSITION pos;
		DWORD dwSize;   
		DWORD dwRef;
	};

	typedef CAtlMap<keyType, NodeType *, KeyTrait> mapType;
	SyncClass m_syncObj;
	StatClass m_statObj;
	Flusher m_flusher;
	Culler m_culler;

	//memory cache configuration parameters
	DWORD m_dwMaxAllocationSize;
	DWORD m_dwMaxEntries;

	BOOL m_bInitialized;
public:

	mapType m_hashTable;
	CMemoryCacheBase() :
	  m_dwMaxAllocationSize(0xFFFFFFFF),
	  m_dwMaxEntries(0xFFFFFFFF),
	  m_bInitialized(FALSE)
	{

	}

	//Initializes the cache and the cache synchronization object
	//Also the performance monitoring
	HRESULT Initialize()
	{
		if (m_bInitialized)
			return HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED);
		HRESULT hr;
		hr =  m_syncObj.Init();

		if (hr == S_OK)
			hr = m_statObj.Initialize();

		m_bInitialized = TRUE;

		return hr;
	}

	//removes all entries whether or not they are initialized.
	HRESULT Uninitialize()
	{
		if (!m_bInitialized)
			return S_OK;

		//clear out the hash table
		HRESULT hr = m_syncObj.Lock();
		if (FAILED(hr))
			return hr;

		RemoveAllEntries();
		m_statObj.Uninitialize();

		m_syncObj.Unlock();
		m_syncObj.Term();

		m_bInitialized = FALSE;

		return S_OK;
	}

	//Adds an entry to the cache.
	//Also, adds an initial reference on the entry if phEntry is not NULL
	HRESULT AddEntry(
					const keyType &Key,             //key for entry
					const DataType &data,               //See the DataType template parameter
					DWORD dwSize,                       //Size of memory to be stored in the cache 
					HCACHEITEM *phEntry = NULL              //out pointer that will contain a handle to the new
														//cache entry on success.
					)
	{
		_ATLTRY
		{
			ATLASSUME(m_bInitialized);

			CAutoPtr<NodeType> spEntry(new NodeType);
			
			if (!spEntry)
				return E_OUTOFMEMORY;

			NodeType *pEntry = spEntry;

			//fill entry
			if (phEntry)
			{
				*phEntry = static_cast<HCACHEITEM>(pEntry);
				pEntry->dwRef++;
			}
			pEntry->Data = data;
			pEntry->dwSize = dwSize;

			CComCritSecLock<SyncClass> lock(m_syncObj, false);

			HRESULT hr = lock.Lock();
			if (FAILED(hr))
			{
				return hr;
			}

			POSITION pos = (POSITION)m_hashTable.Lookup(Key);

			if (pos != NULL)
			{
				RemoveAt(pos, FALSE);
				m_hashTable.GetValueAt(pos) = pEntry;
			}
			else
			{
				pos = m_hashTable.SetAt(Key, pEntry);
			}
			spEntry.Detach();

			pEntry->pos = pos;
			m_statObj.AddElement(dwSize);
			m_flusher.Add(pEntry);
			m_culler.Add(pEntry);

			lock.Unlock();

			if (!phEntry)
				return CommitEntry(static_cast<HCACHEITEM>(pEntry));

			return S_OK;
		}
		_ATLCATCHALL()
		{
			return E_FAIL;
		}
	}

		// Commits the entry to the cache
	HRESULT CommitEntry(const HCACHEITEM hEntry)
	{
		ATLASSUME(m_bInitialized);
		if (!hEntry || hEntry == INVALID_HANDLE_VALUE)
			return E_INVALIDARG;

		HRESULT hr = m_syncObj.Lock();
		if (FAILED(hr))
		{
			return hr;
		}

		NodeType *pEntry = static_cast<NodeType *>(hEntry);
		m_culler.Commit(pEntry);
		m_syncObj.Unlock();
		return S_OK;
	}

	// Looks up an entry and returns a handle to it,
	// also updates access count and reference count
	HRESULT LookupEntry(const keyType &Key, HCACHEITEM * phEntry)
	{
		ATLASSUME(m_bInitialized);
		HRESULT hr = m_syncObj.Lock();
		if (FAILED(hr))
		{
			return hr;
		}

		hr = E_FAIL;

		POSITION pos = (POSITION)m_hashTable.Lookup(Key);
		if (pos != NULL)
		{
			NodeType * pEntry = m_hashTable.GetValueAt(pos);
			m_flusher.Access(pEntry);
			m_culler.Access(pEntry);
			if (phEntry)
			{
				pEntry->dwRef++;
				*phEntry = static_cast<HCACHEITEM>(pEntry);
			}

			m_statObj.Hit();

			hr = S_OK;
		}
		else
		{
			*phEntry = NULL;
			m_statObj.Miss();
		}
		m_syncObj.Unlock();

		return hr;
	}

	// Gets the data based on the handle.  Is thread-safe as long as there is a
	// reference on the data
	HRESULT GetEntryData(const HCACHEITEM hEntry, DataType *pData, DWORD *pdwSize) const
	{
		ATLASSUME(m_bInitialized);
		ATLASSERT(pData != NULL || pdwSize != NULL);  // At least one should not be NULL

		if (!hEntry || hEntry == INVALID_HANDLE_VALUE)
			return E_INVALIDARG;

		NodeType * pEntry = static_cast<NodeType *>(hEntry);
		if (pData) 
			*pData = pEntry->Data;
		if (pdwSize) 
			*pdwSize = pEntry->dwSize;

		return S_OK;
	}

	// Unreferences the entry based on the handle
	DWORD ReleaseEntry(const HCACHEITEM hEntry)
	{
		ATLASSUME(m_bInitialized);
		if (!hEntry || hEntry == INVALID_HANDLE_VALUE)
			return (DWORD)-1;

		HRESULT hr = m_syncObj.Lock();
		if (FAILED(hr))
			return (DWORD)-1;

		NodeType * pEntry = static_cast<NodeType *>(hEntry);
		m_flusher.Release(pEntry);
		m_culler.Release(pEntry);
		ATLASSERT(pEntry->dwRef > 0);

		DWORD dwRef = --pEntry->dwRef;
		if ((pEntry->pos == NULL) && (pEntry->dwRef == 0))
				InternalRemoveEntry(pEntry);

		m_syncObj.Unlock();

		return dwRef;
	}

	// Increments the entry's reference count
	DWORD AddRefEntry(const HCACHEITEM hEntry)
	{
		ATLASSUME(m_bInitialized);
		if (!hEntry || hEntry == INVALID_HANDLE_VALUE)
			return (DWORD)-1;

		HRESULT hr = m_syncObj.Lock();
		if (FAILED(hr))
			return (DWORD)-1;

		NodeType * pEntry = static_cast<NodeType *>(hEntry);
		m_flusher.Access(pEntry);
		m_culler.Access(pEntry);
		DWORD dwRef = ++pEntry->dwRef;
		m_syncObj.Unlock();

		return dwRef;
	}

	// Removes an entry from the cache regardless of whether or 
	// not it has expired.  If there are references, it detaches
	// the entry so that future lookups will fail, and when
	// the ref count drops to zero, it will be deleted
	HRESULT RemoveEntryByKey(const keyType &Key)
	{
		ATLASSUME(m_bInitialized);
		HCACHEITEM hEntry;
		HRESULT hr = LookupEntry(Key, &hEntry);
		if (hr == S_OK)
			hr = RemoveEntry(hEntry);

		return hr;
	}

	// Removes the element from the cache.  If there are still
	// references, then the entry is detached.
	HRESULT RemoveEntry(const HCACHEITEM hEntry)
	{
		ATLASSUME(m_bInitialized);
		if (!hEntry || hEntry == INVALID_HANDLE_VALUE)
			return E_INVALIDARG;

		_ATLTRY
		{
			CComCritSecLock<SyncClass> lock(m_syncObj, false);

			HRESULT hr = lock.Lock();
			if (FAILED(hr))
				return hr;

			NodeType * pEntry = static_cast<NodeType *>(hEntry);
			m_flusher.Release(pEntry);
			m_culler.Release(pEntry);
			ATLASSERT(pEntry->dwRef > 0);
			pEntry->dwRef--;
			if (pEntry->pos)
				RemoveAt(pEntry->pos, TRUE);
			else if ((long)pEntry->dwRef == 0)
				InternalRemoveEntry(pEntry);
			lock.Unlock();
		}
		_ATLCATCHALL()
		{
			return E_OUTOFMEMORY;
		}

		return S_OK;
	}

	// CullEntries removes all expired items
	HRESULT CullEntries()
	{
		ATLASSUME(m_bInitialized);

		_ATLTRY
		{
			CComCritSecLock<SyncClass> lock(m_syncObj, false);
			HRESULT hr = lock.Lock();
			if (FAILED(hr))
				return hr;

			m_culler.Start();

			while (NodeType *pNode = static_cast<NodeType *>(m_culler.GetExpired()))
				RemoveAt(pNode->pos, TRUE);

			lock.Unlock();
		}
		_ATLCATCHALL()
		{
			return E_OUTOFMEMORY;
		}

		return S_OK;
	}

	// FlushEntries reduces the cache to meet the configuration requirements
	HRESULT FlushEntries()
	{        
		ATLASSUME(m_bInitialized);
		HRESULT hr = CullEntries();
		if (FAILED(hr))
			return hr;

		_ATLTRY
		{
			CComCritSecLock<SyncClass> lock(m_syncObj, false);
			hr = lock.Lock();
			if (FAILED(hr))
				return hr;

			NodeType * pNode = static_cast<NodeType *>(m_flusher.GetStart());

			while (pNode &&
				   (((m_statObj.GetCurrentEntryCount() > m_dwMaxEntries)) ||
					((m_statObj.GetCurrentAllocSize() > m_dwMaxAllocationSize))))
			{
				NodeType *pNext = static_cast<NodeType *>(m_flusher.GetNext(pNode));

				if (pNode->dwRef == 0)
					RemoveAt(pNode->pos, TRUE);

				pNode = pNext;
			}
			lock.Unlock();
		}
		_ATLCATCHALL()
		{
			return E_OUTOFMEMORY;
		}

		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE SetMaxAllowedSize(DWORD dwSize)
	{
		m_dwMaxAllocationSize = dwSize;
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetMaxAllowedSize(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_dwMaxAllocationSize;
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE SetMaxAllowedEntries(DWORD dwSize)
	{
		m_dwMaxEntries = dwSize;
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetMaxAllowedEntries(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_dwMaxEntries;
		return S_OK;
	}


	HRESULT ResetCache()
	{
		ATLASSUME(m_bInitialized);
		HRESULT hr = E_UNEXPECTED;
		if (SUCCEEDED(ClearStats()))
			hr = RemoveAllEntries(); 
		return hr;
	}

	HRESULT ClearStats()
	{
		m_statObj.ResetCounters();
		return S_OK;
	}

	HRESULT RemoveAllEntries()
	{
		ATLASSUME(m_bInitialized);
		HRESULT hr = m_syncObj.Lock();
		if (FAILED(hr))
			return hr;

		m_hashTable.DisableAutoRehash();
		POSITION pos = m_hashTable.GetStartPosition();
		POSITION oldpos;
		while (pos != NULL)
		{
			oldpos = pos;
			m_hashTable.GetNext(pos);
			RemoveAt(oldpos, TRUE);
		}
		m_hashTable.EnableAutoRehash();
		m_syncObj.Unlock();

		return S_OK;
	}

protected:

	// Checks to see if the cache can accommodate any new entries within
	// its allocation and entry count limits.  
	bool CanAddEntry(DWORD dwSizeToAdd)
	{
		return CheckAlloc(dwSizeToAdd) && CheckEntryCount(1);
	}

	// Checks to see if the cache can accommodate dwSizeToAdd additional
	// allocation within its allocation limit.
	bool CheckAlloc(DWORD dwSizeToAdd)
	{
		if (m_dwMaxAllocationSize == 0xFFFFFFFF)
			return true; //max allocation size setting hasn't been set
		DWORD dwNew = m_statObj.GetCurrentAllocSize() + dwSizeToAdd;
		return dwNew < m_dwMaxAllocationSize;
	}


	// Checks to see if the cache can accommodate dwNumEntriesToAdd
	// additional entries within its limits.
	bool CheckEntryCount(DWORD dwNumEntriesToAdd)
	{
		if (m_dwMaxEntries == 0xFFFFFFFF)
			return true; //max entry size hasn't been set
		DWORD dwNew = m_statObj.GetCurrentEntryCount() + dwNumEntriesToAdd;
		return dwNew < m_dwMaxEntries;

	}

protected:
	// Takes the element at pos in the hash table and removes it from
	// the cache.  If there are no references, then the entry is
	// deleted, otherwise it is deleted by ReleaseEntry when the
	// refcount goes to zero.
	HRESULT RemoveAt(POSITION pos, BOOL bDelete)
	{
		HRESULT hr = S_OK;
		ATLASSERT(pos != NULL);
		NodeType * pEntry = m_hashTable.GetValueAt(pos);
		m_flusher.Remove(pEntry);
		m_culler.Remove(pEntry);
		if (bDelete)
			m_hashTable.RemoveAtPos(pos);

		if ((long)pEntry->dwRef == 0)
			hr = InternalRemoveEntry(pEntry);
		else
			pEntry->pos = NULL;

		return S_OK;
	}

	// Does the actual destruction of the node.  Deletes the
	// NodeType struct and calls the inherited class's 
	// OnDestroyEntry function, where other necessary destruction
	// can take place.  Also updates the cache statistics.
	// Inherited classes should call RemoveAt unless the element's
	// refcount is zero and it has been removed from the
	// culler and flusher lists.
	HRESULT InternalRemoveEntry(NodeType * pEntry)
	{
		ATLENSURE(pEntry != NULL);

		T* pT = static_cast<T*>(this);

		ATLASSERT((long)pEntry->dwRef == 0);

		pT->OnDestroyEntry(pEntry);

		m_statObj.ReleaseElement(pEntry->dwSize);

		delete pEntry;

		return S_OK;
	}
}; // CMemoryCacheBase

class CCacheDataBase
{
};

struct CCacheDataEx : public CCacheDataBase
{
	CCacheDataEx()
	{
		hInstance = NULL;
		pClient = NULL;
	}

	HINSTANCE hInstance;
	IMemoryCacheClient * pClient;
};


template <typename DataType, 
		class StatClass=CStdStatClass,
		class FlushClass=COldFlusher,
		class keyType=CFixedStringKey,  class KeyTrait=CStringElementTraits<CFixedStringKey >,
		class SyncClass=CComCriticalSection,
		class CullClass=CExpireCuller >
class CMemoryCache:
	public CMemoryCacheBase<CMemoryCache<DataType, StatClass, FlushClass, keyType, KeyTrait, SyncClass, CullClass>, DataType, CCacheDataEx, 
		keyType, KeyTrait, FlushClass, CullClass, SyncClass, StatClass>
{
protected:
	CComPtr<IServiceProvider> m_spServiceProv;
	CComPtr<IDllCache> m_spDllCache;
	typedef CMemoryCacheBase<CMemoryCache<DataType, StatClass, FlushClass, keyType, KeyTrait, SyncClass, CullClass>, DataType, CCacheDataEx, 
		keyType, KeyTrait, FlushClass, CullClass, SyncClass, StatClass> baseClass;
public:
	virtual ~CMemoryCache()
	{
	}

	HRESULT Initialize(IServiceProvider * pProvider)
	{
		baseClass::Initialize();
		m_spServiceProv = pProvider;
		if (pProvider)
			return m_spServiceProv->QueryService(__uuidof(IDllCache), __uuidof(IDllCache), (void**)&m_spDllCache);
		else
			return S_OK;
	}

	HRESULT AddEntry(
					const keyType &Key,
					const DataType &data,
					DWORD dwSize,
					FILETIME * pftExpireTime = NULL,
					HINSTANCE hInstance = NULL,
					IMemoryCacheClient * pClient = NULL,
					HCACHEITEM *phEntry = NULL
					)
	{
		_ATLTRY
		{
			HRESULT hr;
			NodeType * pEntry = NULL;
			hr = baseClass::AddEntry(Key, data, dwSize, (HCACHEITEM *)&pEntry);
			if (hr != S_OK)
				return hr;

			pEntry->hInstance = hInstance;
			pEntry->pClient = pClient;
			if (pftExpireTime)
				pEntry->cftExpireTime = *pftExpireTime;

			if (hInstance && m_spDllCache)
				m_spDllCache->AddRefModule(hInstance);

			baseClass::CommitEntry(static_cast<HCACHEITEM>(pEntry));

			if (phEntry)
				*phEntry = static_cast<HCACHEITEM>(pEntry);
			else
				baseClass::ReleaseEntry(static_cast<HCACHEITEM>(pEntry));

			return S_OK;
		}
		_ATLCATCHALL()
		{
			return E_FAIL;
		}
	}

	virtual void OnDestroyEntry(const NodeType * pEntry)
	{
		ATLASSERT(pEntry);
		if (!pEntry)
			return;

		if (pEntry->pClient)
			pEntry->pClient->Free((void *)&pEntry->Data);
		if (pEntry->hInstance && m_spDllCache)
			m_spDllCache->ReleaseModule(pEntry->hInstance);
	}
}; // CMemoryCache

// CStdStatData - contains the data that CStdStatClass keeps track of
#define ATL_PERF_CACHE_OBJECT 100

struct CPerfStatObject : public CPerfObject
{
	DECLARE_PERF_CATEGORY(CPerfStatObject, ATL_PERF_CACHE_OBJECT, IDS_PERFMON_CACHE, IDS_PERFMON_CACHE_HELP, -1);

	BEGIN_COUNTER_MAP(CPerfStatObject)
		DEFINE_COUNTER(m_nHitCount, IDS_PERFMON_HITCOUNT, IDS_PERFMON_HITCOUNT_HELP, PERF_COUNTER_RAWCOUNT, -1)
		DEFINE_COUNTER(m_nMissCount, IDS_PERFMON_MISSCOUNT, IDS_PERFMON_MISSCOUNT_HELP, PERF_COUNTER_RAWCOUNT, -1)
		DEFINE_COUNTER(m_nCurrentAllocations, IDS_PERFMON_CURRENTALLOCATIONS, IDS_PERFMON_CURRENTALLOCATIONS_HELP, PERF_COUNTER_RAWCOUNT, -3)
		DEFINE_COUNTER(m_nMaxAllocations, IDS_PERFMON_MAXALLOCATIONS, IDS_PERFMON_MAXALLOCATIONS_HELP, PERF_COUNTER_RAWCOUNT, -3)
		DEFINE_COUNTER(m_nCurrentEntries, IDS_PERFMON_CURRENTENTRIES, IDS_PERFMON_CURRENTENTRIES_HELP, PERF_COUNTER_RAWCOUNT, -1)
		DEFINE_COUNTER(m_nMaxEntries, IDS_PERFMON_MAXENTRIES, IDS_PERFMON_MAXENTRIES_HELP, PERF_COUNTER_RAWCOUNT, -1)
	END_COUNTER_MAP()

	long m_nHitCount;
	long m_nMissCount;
	long m_nCurrentAllocations;
	long m_nMaxAllocations;
	long m_nCurrentEntries;
	long m_nMaxEntries;
};

// CCachePerfMon - the interface to CPerfMon, with associated definitions
class CCachePerfMon : public CPerfMon
{
public:
	BEGIN_PERF_MAP(_T("ATL Server:Cache"))
		CHAIN_PERF_CATEGORY(CPerfStatObject)
	END_PERF_MAP()
};

//
//CStdStatClass
// Description
// This class provides the implementation of a standard cache statistics accounting class
class CStdStatClass
{ 
protected:
	CPerfStatObject* m_pStats;
	CPerfStatObject m_stats;

public:

	CStdStatClass()
	{
		m_pStats = &m_stats;
	}

	// This function is not thread safe by design
	HRESULT Initialize(CPerfStatObject* pStats = NULL)
	{
		if (pStats)
			m_pStats = pStats;
		else
			m_pStats = &m_stats;

		ResetCounters();
		return S_OK;
	}

	// This function is not thread safe by design
	HRESULT Uninitialize()
	{
		m_pStats = &m_stats;
		return S_OK;
	}

	void Hit()
	{
		InterlockedIncrement(&m_pStats->m_nHitCount);
	}

	void Miss()
	{ 
		InterlockedIncrement(&m_pStats->m_nMissCount);
	}

	void AddElement(DWORD dwBytes)
	{
		DWORD nCurrentEntries = InterlockedIncrement(&m_pStats->m_nCurrentEntries);
		AtlInterlockedUpdateMax(nCurrentEntries, &m_pStats->m_nMaxEntries);

		DWORD nCurrentAllocations = dwBytes + AtlInterlockedExchangeAdd(&m_pStats->m_nCurrentAllocations, dwBytes);
		AtlInterlockedUpdateMax(nCurrentAllocations, &m_pStats->m_nMaxAllocations);
	}

	void ReleaseElement(DWORD dwBytes)
	{
		InterlockedDecrement(&m_pStats->m_nCurrentEntries);
		AtlInterlockedExchangeAdd(&m_pStats->m_nCurrentAllocations, -((long)dwBytes));
	}

	DWORD GetHitCount()
	{
		return m_pStats->m_nHitCount;
	}

	DWORD GetMissCount()
	{
		return m_pStats->m_nMissCount;
	}

	DWORD GetCurrentAllocSize()
	{
		return m_pStats->m_nCurrentAllocations;
	}

	DWORD GetMaxAllocSize()
	{
		return m_pStats->m_nMaxAllocations;
	}

	DWORD GetCurrentEntryCount()
	{
		return m_pStats->m_nCurrentEntries;
	}

	DWORD GetMaxEntryCount()
	{   
		return m_pStats->m_nMaxEntries;
	}

	void ResetCounters()
	{
		m_pStats->m_nHitCount = 0;
		m_pStats->m_nMissCount = 0;
		m_pStats->m_nCurrentAllocations = 0;
		m_pStats->m_nMaxAllocations = 0;
		m_pStats->m_nCurrentEntries = 0;
		m_pStats->m_nMaxEntries = 0;
	}
}; // CStdStatClass

//
// CNoStatClass
// This is a noop stat class
class CNoStatClass
{ 
public:
	HRESULT Initialize(){ return S_OK; }
	HRESULT Uninitialize(){ return S_OK; }
	void Hit(){ }
	void Miss(){ }
	void AddElement(DWORD){ }
	void ReleaseElement(DWORD){ }
	DWORD GetHitCount(){ return 0; }
	DWORD GetMissCount(){ return 0; }
	DWORD GetCurrentAllocSize(){ return 0; }
	DWORD GetMaxAllocSize(){ return 0; }
	DWORD GetCurrentEntryCount(){ return 0; }
	DWORD GetMaxEntryCount(){ return 0; }
	void ResetCounters(){ }
}; // CNoStatClass

//
//CPerfStatClass
// Description
// This class provides the implementation of a cache statistics gathering class
// with PerfMon support
class CPerfStatClass : public CStdStatClass
{
	CPerfStatObject * m_pPerfObject;
	CCachePerfMon m_PerfMon;

public:

	HRESULT Initialize(__in_z_opt LPWSTR szName=NULL)
	{
		HRESULT hr;
		WCHAR szPath[MAX_PATH];

		if (!szName)
		{
			// default name is the name of the module
			// we don't care about possible truncation if longer than max_path
			// we just need an identifier
			HINSTANCE hInst = _AtlBaseModule.GetModuleInstance();
			if (::GetModuleFileNameW(hInst, szPath, MAX_PATH) == 0)
			{
				return E_FAIL;
			}
			szPath[MAX_PATH-1] = 0;
			szName = szPath;
		}

		m_pPerfObject = NULL;
		ATLTRACE(atlTraceCache, 2, _T("Initializing m_PerfMon\n"));
		hr = m_PerfMon.Initialize();
		if (SUCCEEDED(hr))
		{
			CPerfLock lock(&m_PerfMon);
			if (FAILED(hr = lock.GetStatus()))
			{
				return hr;
			}

			hr = m_PerfMon.CreateInstance(ATL_PERF_CACHE_OBJECT, 0, szName, reinterpret_cast<CPerfObject**>(&m_pPerfObject));
			if (FAILED(hr))
			{
				return hr;
			}

			CStdStatClass::Initialize(m_pPerfObject);
		}
		else
			ATLASSUME(m_pPerfObject == NULL);

		return hr;
	}

	HRESULT Uninitialize()
	{
		CStdStatClass::Uninitialize();

		if (m_pPerfObject != NULL) // Initialized m_pPerfObject successfully above
		{
			HRESULT hr = m_PerfMon.ReleaseInstance(m_pPerfObject);
			if (hr != S_OK)
				return hr;

			m_PerfMon.UnInitialize();
		}

		return S_OK;
	}
}; // CPerfStatClass

#ifndef ATL_BLOB_CACHE_TIMEOUT
#ifdef _DEBUG
#define ATL_BLOB_CACHE_TIMEOUT 1000
#else
#define ATL_BLOB_CACHE_TIMEOUT 5000
#endif // _DEBUG
#endif // ATL_BLOB_CACHE_TIMEOUT

//
//CBlobCache
// Description:
// Implements a cache that stores pointers to void. Uses the generic CMemoryCacheBase class
// as the implementation.
template <class MonitorClass,
		class StatClass=CStdStatClass,
		class SyncObj=CComCriticalSection,
		class FlushClass=COldFlusher,
		class CullClass=CExpireCuller >
class CBlobCache : public CMemoryCache<void*, StatClass, FlushClass, CFixedStringKey, 
	CStringElementTraits<CFixedStringKey >, SyncObj, CullClass>,
	public IMemoryCache,
	public IMemoryCacheControl,
	public IMemoryCacheStats,
	public IWorkerThreadClient
{
	typedef CMemoryCache<void*, StatClass, FlushClass, CFixedStringKey, 
		CStringElementTraits<CFixedStringKey>, SyncObj, CullClass> cacheBase;

	MonitorClass m_Monitor;

protected:
	HANDLE m_hTimer;

public:
	CBlobCache() : m_hTimer(NULL)
	{
	}

	HRESULT Initialize(IServiceProvider *pProv)
	{
		HRESULT hr = cacheBase::Initialize(pProv);
		if (FAILED(hr))
			return hr;
		hr = m_Monitor.Initialize();
		if (FAILED(hr))
			return hr;
		return m_Monitor.AddTimer(ATL_BLOB_CACHE_TIMEOUT, 
			static_cast<IWorkerThreadClient*>(this), (DWORD_PTR) this, &m_hTimer);
	}

	template <class ThreadTraits>
	HRESULT Initialize(IServiceProvider *pProv, CWorkerThread<ThreadTraits> *pWorkerThread)
	{
		ATLASSERT(pWorkerThread);

		HRESULT hr = cacheBase::Initialize(pProv);
		if (FAILED(hr))
			return hr;

		hr = m_Monitor.Initialize(pWorkerThread);
		if (FAILED(hr))
			return hr;

		return m_Monitor.AddTimer(ATL_BLOB_CACHE_TIMEOUT, 
			static_cast<IWorkerThreadClient*>(this), (DWORD_PTR) this, &m_hTimer);
	}

	HRESULT Execute(DWORD_PTR dwParam, HANDLE /*hObject*/)
	{
		CBlobCache* pCache = (CBlobCache*)dwParam;

		if (pCache)
			pCache->Flush();
		return S_OK;
	}

	HRESULT CloseHandle(HANDLE hObject)
	{
		ATLASSUME(m_hTimer == hObject);
		m_hTimer = NULL;
		::CloseHandle(hObject);
		return S_OK;
	}

	virtual ~CBlobCache()
	{
		if (m_hTimer)
		{
			ATLENSURE(SUCCEEDED(m_Monitor.RemoveHandle(m_hTimer)));
		}
	}

	HRESULT Uninitialize()
	{
		HRESULT hrMonitor=S_OK;
		if (m_hTimer)
		{
			hrMonitor=m_Monitor.RemoveHandle(m_hTimer);
			m_hTimer = NULL;
		}
		HRESULT hrShut=m_Monitor.Shutdown();
		HRESULT hrCache=cacheBase::Uninitialize();
		if(FAILED(hrMonitor))
		{
			return hrMonitor;
		}
		if(FAILED(hrShut))
		{
			return hrShut;
		}
		return hrCache;
	}
	// IUnknown methods
	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv)
	{
		HRESULT hr = E_NOINTERFACE;
		if (!ppv)
			hr = E_POINTER;
		else
		{
			if (InlineIsEqualGUID(riid, __uuidof(IUnknown)) ||
				InlineIsEqualGUID(riid, __uuidof(IMemoryCache)))
			{
				*ppv = (IUnknown *) (IMemoryCache *) this;
				AddRef();
				hr = S_OK;
			}
			if (InlineIsEqualGUID(riid, __uuidof(IMemoryCacheStats)))
			{
				*ppv = (IUnknown *) (IMemoryCacheStats*)this;
				AddRef();
				hr = S_OK;
			}
			if (InlineIsEqualGUID(riid, __uuidof(IMemoryCacheControl)))
			{
				*ppv = (IUnknown *) (IMemoryCacheControl*)this;
				AddRef();
				hr = S_OK;
			}

		}
		return hr;
	}

	ULONG STDMETHODCALLTYPE AddRef()
	{
		return 1;
	}

	ULONG STDMETHODCALLTYPE Release()
	{
		return 1;
	}

	// IMemoryCache Methods
	HRESULT STDMETHODCALLTYPE Add(LPCSTR szKey, void *pvData, DWORD dwSize, 
		FILETIME *pftExpireTime, 
		HINSTANCE hInstClient,
		HCACHEITEM *phEntry,
		IMemoryCacheClient *pClient)
	{
		HRESULT hr = E_FAIL;
		//if it's a multithreaded cache monitor we'll let the monitor take care of
		//cleaning up the cache so we don't overflow our configuration settings.
		//if it's not a threaded cache monitor, we need to make sure we don't
		//overflow the configuration settings by adding a new element
		if (m_Monitor.GetThreadHandle()==NULL)
		{
			if (!cacheBase::CanAddEntry(dwSize))
			{
				//flush the entries and check again to see if we can add
				cacheBase::FlushEntries();
				if (!cacheBase::CanAddEntry(dwSize))
					return E_OUTOFMEMORY;
			}
		}
		_ATLTRY
		{
			hr = cacheBase::AddEntry(szKey, pvData, dwSize,
				pftExpireTime, hInstClient, pClient, phEntry);
			return hr;
		}
		_ATLCATCHALL()
		{
			return E_FAIL;
		}
	}

	HRESULT STDMETHODCALLTYPE LookupEntry(LPCSTR szKey, HCACHEITEM * phEntry)
	{
		return cacheBase::LookupEntry(szKey, phEntry);
	}

	HRESULT STDMETHODCALLTYPE GetData(const HCACHEITEM hKey, void **ppvData, DWORD *pdwSize) const
	{
		return cacheBase::GetEntryData(hKey, ppvData, pdwSize);
	}

	HRESULT STDMETHODCALLTYPE ReleaseEntry(const HCACHEITEM hKey)
	{
		return cacheBase::ReleaseEntry(hKey);
	}

	HRESULT STDMETHODCALLTYPE RemoveEntry(const HCACHEITEM hKey)
	{
		return cacheBase::RemoveEntry(hKey);
	}

	HRESULT STDMETHODCALLTYPE RemoveEntryByKey(LPCSTR szKey)
	{
		return cacheBase::RemoveEntryByKey(szKey);
	}

	HRESULT STDMETHODCALLTYPE Flush()
	{
		return cacheBase::FlushEntries();
	}


	HRESULT STDMETHODCALLTYPE SetMaxAllowedSize(DWORD dwSize)
	{
		return cacheBase::SetMaxAllowedSize(dwSize);
	}

	HRESULT STDMETHODCALLTYPE GetMaxAllowedSize(DWORD *pdwSize)
	{
		return cacheBase::GetMaxAllowedSize(pdwSize);
	}

	HRESULT STDMETHODCALLTYPE SetMaxAllowedEntries(DWORD dwSize)
	{
		return cacheBase::SetMaxAllowedEntries(dwSize);
	}

	HRESULT STDMETHODCALLTYPE GetMaxAllowedEntries(DWORD *pdwSize)
	{
		return cacheBase::GetMaxAllowedEntries(pdwSize);
	}

	HRESULT STDMETHODCALLTYPE ResetCache()
	{
		return cacheBase::ResetCache();
	}

	// IMemoryCacheStats methods
	HRESULT STDMETHODCALLTYPE ClearStats()
	{
		m_statObj.ResetCounters();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetHitCount(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetHitCount();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetMissCount(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetMissCount();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetMaxAllocSize(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetMaxAllocSize();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetCurrentAllocSize(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetCurrentAllocSize();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetMaxEntryCount(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetMaxEntryCount();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetCurrentEntryCount(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetCurrentEntryCount();
		return S_OK;
	}

}; // CBlobCache


//
// CDllCache
// This class manages a cache to handle calls to LoadLibrary
// and FreeLibrary.  
// It keeps dlls loaded even after the last call to free library
// a worker thread then calls FreeLibrary on unused dlls
//
#ifndef ATL_DLL_CACHE_TIMEOUT
	#ifdef _DEBUG
		#define ATL_DLL_CACHE_TIMEOUT   1000        // 1 sec default for debug builds
	#else
		#define ATL_DLL_CACHE_TIMEOUT   10*60000    // 10 minute default for retail builds
	#endif
#endif

class CNoDllCachePeer
{
public:
	struct DllInfo
	{
	};

	BOOL Add(HINSTANCE /*hInst*/, DllInfo * /*pInfo*/)
	{
		return TRUE;
	}

	void Remove(HINSTANCE /*hInst*/, DllInfo * /*pInfo*/)
	{
	}
};

// CDllCache
// Implements IDllCache, an interface that is used to load and unload Dlls.
// To use it, construct an instance of a CDllCache and call Initialize.
// The Initialize call has to match with the type of monitor class you 
// templatize on. The monitor thread will call IWorkerThreadClient::Execute
// after its timeout expires. Make sure to Uninitialize the object before
// it is destroyed by calling Uninitialize
//
template <class MonitorClass, class Peer=CNoDllCachePeer>
class CDllCache : public IDllCache, 
	public IWorkerThreadClient
{
protected:
	CComCriticalSection m_critSec;
	CSimpleArray<DLL_CACHE_ENTRY> m_Dlls;
	CSimpleArray<typename Peer::DllInfo> m_DllInfos;
	MonitorClass m_Monitor;
	HANDLE m_hTimer;

	void RemoveDllEntry(DLL_CACHE_ENTRY& entry)
	{
		::FreeLibrary(entry.hInstDll);
		entry.hInstDll = NULL;
		m_Dlls.RemoveAt(m_Dlls.GetSize()-1);
	}

public:
	Peer m_Peer;

	CDllCache() :
		m_hTimer(NULL)
	{

	}

	HRESULT Initialize(DWORD dwTimeout=ATL_DLL_CACHE_TIMEOUT)
	{
		HRESULT hr = m_critSec.Init();
		if (FAILED(hr))
			return hr;
		hr = m_Monitor.Initialize();
		if (FAILED(hr))
			return hr;
		return m_Monitor.AddTimer(dwTimeout, this, 0, &m_hTimer);
	}

	template <class ThreadTraits>
	HRESULT Initialize(CWorkerThread<ThreadTraits> *pWorkerThread,
			DWORD dwTimeout=ATL_DLL_CACHE_TIMEOUT)
	{
		HRESULT hr = m_critSec.Init();
		if (FAILED(hr))
			return hr;
		hr = m_Monitor.Initialize(pWorkerThread);
		if (FAILED(hr))
			return hr;
		return m_Monitor.AddTimer(dwTimeout, this, 0, &m_hTimer);
	}

	HRESULT Uninitialize()
	{
		HRESULT hr = S_OK;
		HRESULT hrLatest = S_OK;
		if (m_hTimer)
		{
			hrLatest=m_Monitor.RemoveHandle(m_hTimer);
			if(FAILED(hrLatest) && SUCCEEDED(hr))
			{
				hr=hrLatest;
			}
			m_hTimer = NULL;
		}
		m_Monitor.Shutdown();

		// free all the libraries we've cached
		int nLen = m_Dlls.GetSize();
		for (int i=0; i<nLen; i++)
		{
			DLL_CACHE_ENTRY& entry = m_Dlls[i];
			ATLASSERT(entry.dwRefs == 0);
			BOOL bRet = ::FreeLibrary(entry.hInstDll);

			if (!bRet)
			{
				hrLatest = AtlHresultFromLastError();
				if(FAILED(hrLatest) && SUCCEEDED(hr))
				{
					hr=hrLatest;
				}
				ATLTRACE(atlTraceCache, 0, _T("Free library failed on shutdown of dll cache : hr = 0x%08x)"), hr);
			}
		}
		m_Dlls.RemoveAll();
		m_critSec.Term();
		return hr;

	}

	// IUnknown methods
	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv)
	{
		if (!ppv)
			return E_POINTER;
		if (InlineIsEqualGUID(riid, __uuidof(IUnknown)) ||
			InlineIsEqualGUID(riid, __uuidof(IDllCache)))
		{
			*ppv = (IUnknown *) this;
			AddRef();
			return S_OK;
		}
		return E_NOINTERFACE;
	}

	ULONG STDMETHODCALLTYPE AddRef()
	{
		return 1;
	}

	ULONG STDMETHODCALLTYPE Release()
	{
		return 1;
	}

	// IDllCache methods
	HINSTANCE Load(LPCSTR szDllName, void *pPeerInfo) throw(...)
	{
		HRESULT hr = m_critSec.Lock();
		if (FAILED(hr))
			return NULL;

		int nLen = m_Dlls.GetSize();
		for (int i=0; i<nLen; i++)
		{
			DLL_CACHE_ENTRY& entry = m_Dlls[i];
			if (!_stricmp(entry.szDllName, szDllName))
			{
				entry.dwRefs++;
				m_critSec.Unlock();
				if (pPeerInfo)
				{
					Peer::DllInfo *pl = (Peer::DllInfo*)pPeerInfo;
					*pl = m_DllInfos[i];
				}
				return entry.hInstDll;
			}
		}
		DLL_CACHE_ENTRY entry;
		entry.hInstDll = ::LoadLibraryA(szDllName);
		if (!entry.hInstDll)
		{
			m_critSec.Unlock();
			return NULL;
		}
		if (!SafeStringCopy(entry.szDllName, szDllName))
		{
			::FreeLibrary(entry.hInstDll);
			m_critSec.Unlock();
			return NULL;
		}
		entry.dwRefs = 1;
		entry.bAlive = TRUE;
		m_Dlls.Add(entry);

		Peer::DllInfo *pdllInfo = (Peer::DllInfo*)pPeerInfo;

		// m_Peer could throw an exception from user code. We
		// pass that exception from here to a higher context (we
		// won't deal with user exception here).
		if (!m_Peer.Add(entry.hInstDll, pdllInfo))
		{
			RemoveDllEntry(entry);
		}


		if ((entry.hInstDll != NULL) && (!m_DllInfos.Add(*pdllInfo)))
		{
			RemoveDllEntry(entry);
		}

		m_critSec.Unlock();
		return entry.hInstDll;
	}

	BOOL Free(HINSTANCE hInstDll)
	{
		HRESULT hr = m_critSec.Lock();
		if (FAILED(hr))
			return FALSE;

		int nLen = m_Dlls.GetSize();
		for (int i=0; i<nLen; i++)
		{
			DLL_CACHE_ENTRY &entry = m_Dlls[i];
			if (entry.hInstDll == hInstDll)
			{
				ATLASSERT(entry.dwRefs > 0);
				entry.bAlive = TRUE;
				entry.dwRefs--;
				m_critSec.Unlock();
				return TRUE;
			}
		}

		m_critSec.Unlock();
		// the dll wasn't found
		// in the cache, so just
		// pass along to ::FreeLibrary
		return ::FreeLibrary(hInstDll);
	}

	BOOL AddRefModule(HINSTANCE hInstDll)
	{
		HRESULT hr = m_critSec.Lock();
		if (FAILED(hr))
			return FALSE;

		int nLen = m_Dlls.GetSize();
		for (int i=0; i<nLen; i++)
		{
			DLL_CACHE_ENTRY &entry = m_Dlls[i];
			if (entry.hInstDll == hInstDll)
			{
				ATLASSERT(entry.dwRefs > 0);
				entry.dwRefs++;
				m_critSec.Unlock();
				return TRUE;
			}
		}

		m_critSec.Unlock();
		return FALSE;
	}

	BOOL ReleaseModule(HINSTANCE hInstDll)
	{
		HRESULT hr = m_critSec.Lock();
		if (FAILED(hr))
			return FALSE;

		int nLen = m_Dlls.GetSize();
		for (int i=0; i<nLen; i++)
		{
			DLL_CACHE_ENTRY &entry = m_Dlls[i];
			if (entry.hInstDll == hInstDll)
			{
				ATLASSERT(entry.dwRefs > 0);
				entry.bAlive = TRUE;
				entry.dwRefs--;
				m_critSec.Unlock();
				return TRUE;
			}
		}
		m_critSec.Unlock();
		return FALSE;
	}

	HRESULT GetEntries(DWORD dwCount, DLL_CACHE_ENTRY *pEntries, DWORD *pdwCopied)
	{
		if (!pdwCopied)
			return E_POINTER;

		HRESULT hr = m_critSec.Lock();
		if (FAILED(hr))
			return hr;

		if (dwCount==0 || pEntries==NULL)
		{
			// just return the required size
			*pdwCopied = m_Dlls.GetSize();
			m_critSec.Unlock();
			return S_OK;
		}

		if (dwCount > (DWORD) m_Dlls.GetSize())
			dwCount = m_Dlls.GetSize();
		Checked::memcpy_s(pEntries, dwCount*sizeof(DLL_CACHE_ENTRY), m_Dlls.GetData(), dwCount*sizeof(DLL_CACHE_ENTRY));
		*pdwCopied = dwCount;
		m_critSec.Unlock();
		return S_OK;
	}

	HRESULT Flush()
	{
		HRESULT hr = m_critSec.Lock();
		if (FAILED(hr))
			return hr;

		int nLen = m_Dlls.GetSize();
		for (int i=0; i<nLen; i++)
		{
			DLL_CACHE_ENTRY &entry = m_Dlls[i];
			if (entry.dwRefs == 0 && !entry.bAlive)
			{
				_ATLTRY
				{
					m_Peer.Remove(entry.hInstDll, &m_DllInfos[i]);
				}
				_ATLCATCHALL()
				{
					ATLTRACE(atlTraceCache, 2, _T("Exception thrown from user code in CDllCache::Flush\n"));
				}

				::FreeLibrary(entry.hInstDll);
				m_Dlls.RemoveAt(i);
				m_DllInfos.RemoveAt(i);
				i--;
				nLen--;
			}
			entry.bAlive = FALSE;
		}

		m_critSec.Unlock();
		return S_OK;
	}

	HRESULT Execute(DWORD_PTR /*dwParam*/, HANDLE /*hObject*/)
	{
		Flush();
		return S_OK;
	}

	HRESULT CloseHandle(HANDLE hObject)
	{
		ATLASSUME(m_hTimer == hObject);
		m_hTimer = NULL;
		::CloseHandle(hObject);
		return S_OK;
	}
}; // CDllCache


//
//IStencilCache
//IStencilCache is used by a stencil processor to cache pointers to CStencil
//derived objects.


// {8702269B-707D-49cc-AEF8-5FFCB3D6891B}
extern "C" __declspec(selectany) const IID IID_IStencilCache = { 0x8702269b, 0x707d, 0x49cc, { 0xae, 0xf8, 0x5f, 0xfc, 0xb3, 0xd6, 0x89, 0x1b } };

__interface ATL_NO_VTABLE __declspec(uuid("8702269B-707D-49cc-AEF8-5FFCB3D6891B")) 
	IStencilCache : public IUnknown
{
	// IStencilCache methods
	STDMETHOD(CacheStencil)(LPCSTR szName, //a name for this cache entry
								void *pStencil, //a pointer to a CStencil derived object
								DWORD dwSize, //sizeof pStencil
								HCACHEITEM *pHandle, //out pointer to a handle to the this cache entry
								HINSTANCE hInst, //HINSTANCE of the module putting this entry
												 //in the cache.
								IMemoryCacheClient *pClient //Interface used to free this instance
								);
	STDMETHOD(LookupStencil)(LPCSTR szName, HCACHEITEM * phStencil);
	STDMETHOD(GetStencil)(const HCACHEITEM hStencil, void ** ppStencil) const;
	STDMETHOD(AddRefStencil)(const HCACHEITEM hStencil);
	STDMETHOD(ReleaseStencil)(const HCACHEITEM hStencil);
};

// {55DEF119-D7A7-4eb7-A876-33365E1C5E1A}
extern "C" __declspec(selectany) const IID IID_IStencilCacheControl = { 0x55def119, 0xd7a7, 0x4eb7, { 0xa8, 0x76, 0x33, 0x36, 0x5e, 0x1c, 0x5e, 0x1a } };
__interface ATL_NO_VTABLE __declspec(uuid("55DEF119-D7A7-4eb7-A876-33365E1C5E1A"))
IStencilCacheControl : public IUnknown
{
	//IStencilCacheControl
	STDMETHOD(RemoveStencil)(const HCACHEITEM hStencil); // Removes the stencil if there are no references,
														 // otherwise detaches it
	STDMETHOD(RemoveStencilByName)(LPCSTR szStencil); //removes a stencil if there are no
													//references to it
	STDMETHOD(RemoveAllStencils)(); //removes all stencils that don't have references on them
	STDMETHOD(SetDefaultLifespan)(unsigned __int64 dwdwLifespan); //sets the lifespan for all stencils
															 //in the cache (in 100 nanosecond units (10,000,000=1 second)).
	STDMETHOD(GetDefaultLifespan)(unsigned __int64 *pdwdwLifespan);
};

#ifndef ATL_STENCIL_CACHE_TIMEOUT
#ifdef _DEBUG
	#define ATL_STENCIL_CACHE_TIMEOUT 1000
#else
	#define ATL_STENCIL_CACHE_TIMEOUT 5000
#endif // _DEBUG
#endif // ATL_STENCIL_CACHE_TIMEOUT

#ifndef ATL_STENCIL_LIFESPAN
#ifdef _DEBUG                    
	#define ATL_STENCIL_LIFESPAN CFileTime::Second
#else
	#define ATL_STENCIL_LIFESPAN CFileTime::Hour
#endif
#endif

// timeout before we check if the file
// has changed in m.s.
#ifndef ATL_STENCIL_CHECK_TIMEOUT
	#define ATL_STENCIL_CHECK_TIMEOUT 1000
#endif

template <class MonitorClass,
		class StatClass=CStdStatClass,
		class SyncClass=CComCriticalSection,
		class FlushClass=COldFlusher,
		class CullClass=CLifetimeCuller >
class CStencilCache :
	public CMemoryCacheBase<CStencilCache<MonitorClass, StatClass, SyncClass, FlushClass, CullClass>, void *, CCacheDataEx, 
		CFixedStringKey,  CStringElementTraitsI<CFixedStringKey >, 
		FlushClass, CullClass, SyncClass, StatClass>,
	public IStencilCache,
	public IStencilCacheControl,
	public IWorkerThreadClient,
	public IMemoryCacheStats,
	public CComObjectRootEx<CComGlobalsThreadModel>
{
protected:
	typedef CMemoryCacheBase<CStencilCache<MonitorClass, StatClass, SyncClass, FlushClass, CullClass>, void *, CCacheDataEx, 
		CFixedStringKey,  CStringElementTraitsI<CFixedStringKey >, 
		FlushClass, CullClass, SyncClass, StatClass> cacheBase;
	unsigned __int64 m_dwdwStencilLifespan;

	MonitorClass m_Monitor;
	HANDLE m_hTimer;
	CComPtr<IDllCache> m_spDllCache;

public:

	CStencilCache() :
		m_dwdwStencilLifespan(ATL_STENCIL_LIFESPAN),
		m_hTimer(NULL)
	{

	}

	~CStencilCache()
	{
		if (m_hTimer)
		{
			ATLENSURE(SUCCEEDED(m_Monitor.RemoveHandle(m_hTimer)));
		}
	}

	HRESULT Execute(DWORD_PTR dwParam, HANDLE /*hObject*/)
	{
		CStencilCache* pCache = (CStencilCache*)dwParam;
		if (pCache)
			pCache->FlushEntries();
		return S_OK;
	}

	HRESULT CloseHandle(HANDLE hObject)
	{
		ATLASSUME(m_hTimer == hObject);
		m_hTimer = NULL;
		::CloseHandle(hObject);
		return S_OK;
	}

	HRESULT Initialize(IServiceProvider *pProv, DWORD dwStencilCacheTimeout=ATL_STENCIL_CACHE_TIMEOUT, 
		__int64 dwdwStencilLifespan=ATL_STENCIL_LIFESPAN)
	{
		m_dwdwStencilLifespan = dwdwStencilLifespan;
		HRESULT hr = cacheBase::Initialize();
		if (FAILED(hr))
			return hr;
		hr = E_FAIL;
		if (pProv)
			hr = pProv->QueryService(__uuidof(IDllCache), __uuidof(IDllCache), (void**)&m_spDllCache);
		if (FAILED(hr))
			return hr;
		hr = m_Monitor.Initialize();
		if (FAILED(hr))
			return hr;
		return m_Monitor.AddTimer(dwStencilCacheTimeout, this, (DWORD_PTR) this, &m_hTimer);
	}

	template <class ThreadTraits>
	HRESULT Initialize(IServiceProvider *pProv, CWorkerThread<ThreadTraits> *pWorkerThread, 
		DWORD dwStencilCacheTimeout=ATL_STENCIL_CACHE_TIMEOUT, __int64 dwdwStencilLifespan=ATL_STENCIL_LIFESPAN)
	{
		m_dwdwStencilLifespan = dwdwStencilLifespan;
		HRESULT hr = cacheBase::Initialize();
		if (FAILED(hr))
			return hr;
		hr = E_FAIL;
		if (pProv)
			hr = pProv->QueryService(__uuidof(IDllCache), __uuidof(IDllCache), (void**)&m_spDllCache);
		if (FAILED(hr))
			return hr;
		hr = m_Monitor.Initialize(pWorkerThread);
		if (FAILED(hr))
			return hr;
		return m_Monitor.AddTimer(dwStencilCacheTimeout, this, (DWORD_PTR) this, &m_hTimer);
	}


	BEGIN_COM_MAP(CStencilCache)
		COM_INTERFACE_ENTRY(IMemoryCacheStats)
		COM_INTERFACE_ENTRY(IStencilCache)
		COM_INTERFACE_ENTRY(IStencilCacheControl)
	END_COM_MAP()
//IStencilCache methods
	STDMETHOD(CacheStencil)(LPCSTR szName, void *pStencil, DWORD dwSize, HCACHEITEM *phEntry,
				HINSTANCE hInstance, IMemoryCacheClient *pClient)
	{
		NodeType * pEntry = NULL;
		HRESULT hr = m_syncObj.Lock();
		if (FAILED(hr))
			return hr;

		_ATLTRY
		{
			hr = cacheBase::AddEntry(szName, pStencil, dwSize,  (HCACHEITEM *)&pEntry);
		}
		_ATLCATCHALL()
		{
			hr = E_FAIL;
		}
		if (hr != S_OK)
		{
			m_syncObj.Unlock();
			return hr;
		}

		pEntry->hInstance = hInstance;
		pEntry->pClient = pClient;
		pEntry->nLifespan = m_dwdwStencilLifespan;
		if (hInstance && m_spDllCache)
			m_spDllCache->AddRefModule(hInstance);

		cacheBase::CommitEntry(static_cast<HCACHEITEM>(pEntry));

		if (phEntry)
			*phEntry = static_cast<HCACHEITEM>(pEntry);
		else
			cacheBase::ReleaseEntry(static_cast<HCACHEITEM>(pEntry));

		m_syncObj.Unlock();
		return hr;
	}

	STDMETHOD(LookupStencil)(LPCSTR szName, HCACHEITEM * phStencil)
	{
		return cacheBase::LookupEntry(szName, phStencil);
	}

	STDMETHOD(GetStencil)(const HCACHEITEM hStencil, void ** pStencil) const
	{
		return cacheBase::GetEntryData(hStencil, pStencil, NULL);
	}

	STDMETHOD(AddRefStencil)(const HCACHEITEM hStencil)
	{
		return cacheBase::AddRefEntry(hStencil);
	}

	STDMETHOD(ReleaseStencil)(const HCACHEITEM hStencil)
	{
		return cacheBase::ReleaseEntry(hStencil);
	}

	//IStencilCacheControl

	STDMETHOD(RemoveStencil)(const HCACHEITEM hStencil)
	{
		return cacheBase::RemoveEntry(hStencil);
	}

	STDMETHOD(RemoveStencilByName)(LPCSTR szStencil)
	{
		return cacheBase::RemoveEntryByKey(szStencil);
	}

	STDMETHOD(RemoveAllStencils)()
	{
		return cacheBase::RemoveAllEntries();
	}

	STDMETHOD(SetDefaultLifespan)(unsigned __int64 dwdwLifespan)
	{
		m_dwdwStencilLifespan = dwdwLifespan;
		return S_OK;
	}

	STDMETHOD(GetDefaultLifespan)(unsigned __int64 *pdwdwLifepsan)
	{
		HRESULT hr = E_POINTER;
		if (pdwdwLifepsan)
		{
			*pdwdwLifepsan = m_dwdwStencilLifespan;
			hr = S_OK;
		}
		return hr;
	}

	virtual void OnDestroyEntry(const NodeType * pEntry)
	{
		ATLASSERT(pEntry);
		if (!pEntry)
			return;

		if (pEntry->pClient)
			pEntry->pClient->Free((void *)&pEntry->Data);
		if (pEntry->hInstance && m_spDllCache)
			m_spDllCache->ReleaseModule(pEntry->hInstance);
	}

	HRESULT Uninitialize()
	{
		HRESULT hrMonitor=S_OK;
		if (m_hTimer)
		{
			hrMonitor=m_Monitor.RemoveHandle(m_hTimer);
			m_hTimer = NULL;
		}
		m_Monitor.Shutdown();
		HRESULT hrCache=cacheBase::Uninitialize();
		if(FAILED(hrMonitor))
		{
			return hrMonitor;
		}
		return hrCache;
	}
	// IMemoryCacheStats methods
	HRESULT STDMETHODCALLTYPE ClearStats()
	{
		m_statObj.ResetCounters();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetHitCount(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetHitCount();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetMissCount(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetMissCount();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetMaxAllocSize(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetMaxAllocSize();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetCurrentAllocSize(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetCurrentAllocSize();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetMaxEntryCount(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetMaxEntryCount();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetCurrentEntryCount(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetCurrentEntryCount();
		return S_OK;
	}
}; // CStencilCache

// {105A8866-4059-45fe-86AE-FA0EABBFBBB4}
extern "C" __declspec(selectany) const IID IID_IFileCache = { 0x105a8866, 0x4059, 0x45fe, { 0x86, 0xae, 0xfa, 0xe, 0xab, 0xbf, 0xbb, 0xb4 } };

__interface ATL_NO_VTABLE __declspec(uuid("105A8866-4059-45fe-86AE-FA0EABBFBBB4"))
	IFileCache : public IUnknown
{
	// IFileCache Methods

	STDMETHOD(AddFile)( 
		LPCSTR szFileName,
		LPCSTR szTempFileName,
		FILETIME *pftExpireTime,
		void *pPeerInfo,
		HCACHEITEM * phKey);
	STDMETHOD(LookupFile)(LPCSTR szFileName, HCACHEITEM * phKey);
	STDMETHOD(GetFile)(const HCACHEITEM hKey, LPSTR * pszFileName, void **ppPeerInfo);
	STDMETHOD(ReleaseFile)(const HCACHEITEM hKey);
	STDMETHOD(RemoveFile)(const HCACHEITEM hKey);
	STDMETHOD(RemoveFileByName)(LPCSTR szFileName);
	STDMETHOD(Flush)();
};

#ifndef ATL_FILE_CACHE_TIMEOUT
	#define ATL_FILE_CACHE_TIMEOUT 1000
#endif

class CNoFileCachePeer
{
public:
	struct PeerInfo
	{
	};

	static BOOL Add(PeerInfo* /*pDest*/, PeerInfo * /*pSrc*/)
	{
		return TRUE;
	}

	static BOOL Remove(const PeerInfo* /*pFileInfo*/)
	{
		return TRUE;
	}
};

template <class Peer>
struct CCacheDataPeer : public CCacheDataBase
{
	typename Peer::PeerInfo PeerData; 
};

// A class to keep track of files, with maintenance -- maximum size of cache,
// maximum number of entries, expiration of entries, etc. -- inherits from
// CMemoryCacheBase
template <
		class MonitorClass,
		class StatClass=CStdStatClass,
		class FileCachePeer=CNoFileCachePeer,
		class FlushClass=COldFlusher,
		class SyncClass=CComCriticalSection,
		class CullClass=CExpireCuller >
class CFileCache:
	public CMemoryCacheBase<CFileCache<MonitorClass, StatClass, FileCachePeer, FlushClass, SyncClass, CullClass>, LPSTR, CCacheDataPeer<FileCachePeer>, 
			CFixedStringKey,  CStringElementTraits<CFixedStringKey >, 
			FlushClass, CullClass, SyncClass, StatClass>, 
	public IWorkerThreadClient,
	public IFileCache,
	public IMemoryCacheControl,
	public IMemoryCacheStats
{
	typedef CMemoryCacheBase<CFileCache<MonitorClass, StatClass, FileCachePeer, FlushClass, SyncClass, CullClass>, LPSTR, CCacheDataPeer<FileCachePeer>, 
			CFixedStringKey,  CStringElementTraits<CFixedStringKey >, 
			FlushClass, CullClass, SyncClass, StatClass> cacheBase;

	MonitorClass m_Monitor;

protected:
	HANDLE m_hTimer;

public:
	
	CFileCache() : m_hTimer(NULL)
	{
	}

	HRESULT Initialize()
	{
		HRESULT hr = cacheBase::Initialize();
		if (FAILED(hr))
			return hr;
		hr = m_Monitor.Initialize();
		if (FAILED(hr))
			return hr;
		return m_Monitor.AddTimer(ATL_FILE_CACHE_TIMEOUT, 
			static_cast<IWorkerThreadClient*>(this), (DWORD_PTR) this, &m_hTimer);
	}

	template <class ThreadTraits>
	HRESULT Initialize(CWorkerThread<ThreadTraits> *pWorkerThread)
	{
		ATLASSERT(pWorkerThread);

		HRESULT hr = cacheBase::Initialize();
		if (FAILED(hr))
			return hr;
		hr = m_Monitor.Initialize(pWorkerThread);
		if (FAILED(hr))
			return hr;
		return m_Monitor.AddTimer(ATL_FILE_CACHE_TIMEOUT, 
			static_cast<IWorkerThreadClient*>(this), (DWORD_PTR) this, &m_hTimer);
	}


	// Callback for CWorkerThread
	HRESULT Execute(DWORD_PTR dwParam, HANDLE /*hObject*/)
	{
		CFileCache* pCache = (CFileCache*)dwParam;

		if (pCache)
			pCache->Flush();
		return S_OK;
	}

	HRESULT CloseHandle(HANDLE hObject)
	{
		ATLASSUME(m_hTimer == hObject);
		m_hTimer = NULL;
		::CloseHandle(hObject);
		return S_OK;
	}

	~CFileCache()
	{
		if (m_hTimer)
		{
			ATLENSURE(SUCCEEDED(m_Monitor.RemoveHandle(m_hTimer)));
			m_hTimer = NULL;
		}
	}

	HRESULT Uninitialize()
	{
		HRESULT hrMonitor=S_OK;
		if (m_hTimer)
		{
			hrMonitor=m_Monitor.RemoveHandle(m_hTimer);
			m_hTimer = NULL;
		}
		m_Monitor.Shutdown();
		HRESULT hrCache=cacheBase::Uninitialize();
		if(FAILED(hrMonitor))
		{
			return hrMonitor;
		}
		return hrCache;
	}


	// IUnknown methods
	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv)
	{
		HRESULT hr = E_NOINTERFACE;
		if (!ppv)
			hr = E_POINTER;
		else
		{
			if (InlineIsEqualGUID(riid, __uuidof(IUnknown)) ||
				InlineIsEqualGUID(riid, __uuidof(IFileCache)))
			{
				*ppv = (IUnknown *) (IFileCache *) this;
				AddRef();
				hr = S_OK;
			}
			if (InlineIsEqualGUID(riid, __uuidof(IMemoryCacheStats)))
			{
				*ppv = (IMemoryCacheStats*)this;
				AddRef();
				hr = S_OK;
			}
			if (InlineIsEqualGUID(riid, __uuidof(IMemoryCacheControl)))
			{
				*ppv = (IMemoryCacheControl*)this;
				AddRef();
				hr = S_OK;
			}

		}
		return hr;
	}

	ULONG STDMETHODCALLTYPE AddRef()
	{
		return 1;
	}

	ULONG STDMETHODCALLTYPE Release()
	{
		return 1;
	}

	// Adds a file to the cache.  A file is created with a 
	// temporary name, and then Add is called with the temp
	// file name and the final file name, along with expiration data,
	// etc.  A search on the file name will return the name of
	// the file on disk (i.e. the temporary file)
	HRESULT STDMETHODCALLTYPE AddFile(
		LPCSTR szFileName,
		LPCSTR szTempFileName, 
		FILETIME *pftExpireTime, 
		void* pPeerInfo,
		HCACHEITEM * phKey = NULL)
	{
		WIN32_FILE_ATTRIBUTE_DATA fadData;
		BOOL bRet = GetFileAttributesExA(szTempFileName, GetFileExInfoStandard, &fadData);
		if (!bRet)
			return AtlHresultFromLastError();

		__int64 ddwFileSize = (static_cast<__int64>(fadData.nFileSizeHigh) << 32) + fadData.nFileSizeLow;

		DWORD dwRecordedFileSize = (DWORD) (ddwFileSize >> 10);
		// Round the file size up to 1K if it is < 1K
		if (dwRecordedFileSize == 0) 
			dwRecordedFileSize = 1;

		if (m_Monitor.GetThreadHandle()==NULL)
		{
			if (!cacheBase::CanAddEntry(dwRecordedFileSize))
			{
				cacheBase::FlushEntries();
				if (!cacheBase::CanAddEntry(dwRecordedFileSize))
					return E_OUTOFMEMORY;
			}
		}

		NodeType *pEntry = NULL;
		HRESULT hr = m_syncObj.Lock();
		if (FAILED(hr))
			return hr;

		hr = E_FAIL;

		// Make a private copy of the file name
		CHeapPtr<char> szTempFileCopy;
		if (szTempFileCopy.Allocate(MAX_PATH))
		{
			if (strlen(szTempFileName) >= MAX_PATH)
			{
				m_syncObj.Unlock();
				return E_FAIL;
			}
			Checked::strncpy_s(szTempFileCopy, MAX_PATH, szTempFileName, _TRUNCATE);

			_ATLTRY
			{
				hr = cacheBase::AddEntry(szFileName, szTempFileCopy, dwRecordedFileSize, (HCACHEITEM*)&pEntry);
				szTempFileCopy.Detach();
			}
			_ATLCATCHALL()
			{
				hr = E_FAIL;
			}
		}

		if (hr != S_OK)
		{
			m_syncObj.Unlock();
			return hr;
		}


		hr = (TRUE == FileCachePeer::Add(&pEntry->PeerData, 
							static_cast<FileCachePeer::PeerInfo*>(pPeerInfo)) ? S_OK : E_FAIL);
		if (hr == S_OK)
			hr = cacheBase::CommitEntry(static_cast<HCACHEITEM>(pEntry));

		if (hr != S_OK)
		{
			cacheBase::RemoveEntry(static_cast<HCACHEITEM>(pEntry));
			m_syncObj.Unlock();
			return hr;
		}


		if (pftExpireTime)
			pEntry->cftExpireTime = *pftExpireTime;

		if (phKey)
			*phKey = static_cast<HCACHEITEM>(pEntry);
		else
			cacheBase::ReleaseEntry(pEntry);

		m_syncObj.Unlock();
		return hr;
	}

	// Action to take when the entry is removed from the cache
	virtual void OnDestroyEntry(const NodeType * pEntry)
	{
		ATLASSERT(pEntry);
		if (!pEntry)
			return;
		FileCachePeer::Remove(&pEntry->PeerData);
		if (pEntry->Data)
		{
			DeleteFileA(pEntry->Data);
			free(pEntry->Data);
			const_cast<NodeType*>(pEntry)->Data = NULL;
		}
	}

	// Looks up a file by name.  Must be released after use
	HRESULT STDMETHODCALLTYPE LookupFile(LPCSTR szFileName, HCACHEITEM * phKey)
	{
		return cacheBase::LookupEntry(szFileName, phKey);
	}

	// Gets the name of the file on disk
	HRESULT STDMETHODCALLTYPE GetFile(__in const HCACHEITEM hKey, __deref_out_z_opt LPSTR * pszFileName, __deref_out_opt void **ppPeerInfo)
	{
		NodeType *pEntry = (NodeType *)hKey;
		if (ppPeerInfo)
			*ppPeerInfo = &pEntry->PeerData;
		return cacheBase::GetEntryData(hKey, pszFileName, NULL);
	}

	// Releases a file
	HRESULT STDMETHODCALLTYPE ReleaseFile(const HCACHEITEM hKey)
	{
		return cacheBase::ReleaseEntry(hKey);
	}

	// Releases a file and marks it for deletion
	HRESULT STDMETHODCALLTYPE RemoveFile(const HCACHEITEM hKey)
	{
		return cacheBase::RemoveEntry(hKey);
	}

	// Removes a file by name -- this calls IMemoryCacheClient->Free
	// on the file name, which by default (for CFileCache) deletes the
	// file.
	HRESULT STDMETHODCALLTYPE RemoveFileByName(LPCSTR szFileName)
	{
		return cacheBase::RemoveEntryByKey(szFileName);
	}

	// Flushes the entries in the cache, eliminates expired entries,
	// or if the cache exceeds the parameters (alloc size, num entries),
	// culls items based on the sweep mode
	HRESULT STDMETHODCALLTYPE Flush()
	{
		return cacheBase::FlushEntries();
	}

	// IMemoryCacheControl methods
	HRESULT STDMETHODCALLTYPE SetMaxAllowedSize(DWORD dwSize)
	{
		return cacheBase::SetMaxAllowedSize(dwSize);
	}

	HRESULT STDMETHODCALLTYPE GetMaxAllowedSize(DWORD *pdwSize)
	{
		return cacheBase::GetMaxAllowedSize(pdwSize);
	}

	HRESULT STDMETHODCALLTYPE SetMaxAllowedEntries(DWORD dwSize)
	{
		return cacheBase::SetMaxAllowedEntries(dwSize);
	}

	HRESULT STDMETHODCALLTYPE GetMaxAllowedEntries(DWORD *pdwSize)
	{
		return cacheBase::GetMaxAllowedEntries(pdwSize);
	}

	HRESULT STDMETHODCALLTYPE ResetCache()
	{
		return cacheBase::ResetCache();
	}


	// IMemoryCacheStats methods
	HRESULT STDMETHODCALLTYPE ClearStats()
	{
		m_statObj.ResetCounters();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetHitCount(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetHitCount();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetMissCount(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetMissCount();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetMaxAllocSize(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetMaxAllocSize();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetCurrentAllocSize(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetCurrentAllocSize();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetMaxEntryCount(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetMaxEntryCount();
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE GetCurrentEntryCount(DWORD *pdwSize)
	{
		if (!pdwSize)
			return E_POINTER;
		*pdwSize = m_statObj.GetCurrentEntryCount();
		return S_OK;
	}
}; // CFileCache

class CDataConnection; // see atldbcli.h
__interface __declspec(uuid("52E7759B-D6CC-4a03-BDF3-80A6BDCA1F94")) 
IDataSourceCache : public IUnknown
{   
	// Method: Add
	// Params:
	//   szConn: Connection string of data source to connect to
	//   ppDS: Out pointer to the newly added data source
	// Comments:
	//   Attempts to open a connection to the specified data source
	//   using a CDataSource object. Once the connection is open, the
	//   CDatasource is cached.
	STDMETHOD(Add)(LPCTSTR szID, LPCOLESTR szConn, CDataConnection *pDS);

	// Method: Remove
	// Params:
	//   szConn: Specifies the connection string of the connection to close
	// Comments:
	//   Closes the specified connection and removes it's entry from the cache
	STDMETHOD(Remove)(LPCTSTR szID);

	// Method: Lookup
	// Params:
	//   szConn: Specifies the connection string of the connection to look up
	//   ppDS: Out pointer to CDataSource object that is connected to the specified
	//        data source.
	STDMETHOD(Lookup)(LPCTSTR szID, CDataConnection *pDS);

	// Method: Uninitialize
	// Params: 
	//   None
	// Comments:
	//   Closes removes all connections from the cache.
	STDMETHOD(Uninitialize)();

};
#ifndef ATL_DS_CONN_STRING_LEN
	#define ATL_DS_CONN_STRING_LEN 512
#endif

template <>
class CElementTraits< CDataConnection > :
	public CElementTraitsBase< CDataConnection >
{
public:
	static ULONG Hash( INARGTYPE t )
	{
		return( ULONG( ULONG_PTR( &t ) ) );
	}

	static bool CompareElements( INARGTYPE element1, INARGTYPE element2 )
	{
		return( element1.m_session.m_spOpenRowset == element2.m_session.m_spOpenRowset);
	}

	static int CompareElementsOrdered( INARGTYPE /*element1*/, INARGTYPE /*element2*/ )
	{
		ATLASSERT(FALSE);
		return -1;
	}

};

typedef CFixedStringT<CString, ATL_DS_CONN_STRING_LEN> atlDataSourceKey;
typedef CAtlMap<atlDataSourceKey, CDataConnection, 
			CStringElementTraits<atlDataSourceKey>, CElementTraits<CDataConnection> > atlDataSourceCacheMap;

template <class TCritSec=CComFakeCriticalSection>
class CDataSourceCache :
	public IDataSourceCache,
	public CComObjectRootEx<CComGlobalsThreadModel>
{
public:
	BEGIN_COM_MAP(CDataSourceCache)
		COM_INTERFACE_ENTRY(IDataSourceCache)
	END_COM_MAP()

	CDataSourceCache()
	{
		m_cs.Init();
	}

	virtual ~CDataSourceCache ()
	{
		Uninitialize();
	}

	STDMETHOD(Uninitialize)()
	{
		HRESULT hr = m_cs.Lock();
		if (SUCCEEDED(hr))
		{
			m_ConnectionMap.RemoveAll();
			m_cs.Unlock();
		}

		return hr;
	}

	STDMETHOD(Add)(LPCTSTR szID, LPCOLESTR szConn, CDataConnection *pSession)
	{
		HRESULT hr = E_FAIL;

		if (!szID)
			return E_INVALIDARG; // must have session name

		// Do a lookup to make sure we don't add multiple entries
		// with the same name. Adding multiple entries with the same name
		// could cause some entries to get orphaned.
		hr = m_cs.Lock();
		if (FAILED(hr))
		{
			return hr;
		}

		const atlDataSourceCacheMap::CPair *pPair = 
			m_ConnectionMap.Lookup(szID);
		if (!pPair)
		{
			// try to open connection
			CDataConnection DS;
			hr = DS.Open(szConn);       
			if (hr == S_OK)
			{
				_ATLTRY
				{
					if (m_ConnectionMap.SetAt(szID, DS))
					{
						if (pSession) // we allow NULL here
							*pSession = DS; // copy connection to output.
						hr = S_OK;
					}
					else
						hr = E_FAIL; // map add failed
				}
				_ATLCATCHALL()
				{
					hr = E_FAIL;
				}
			}
		}
		else // lookup succeeded, entry is already in cache
		{
			// Instead of opening a new connection, just copy
			// the one we already have in the cache.
			if (pSession)
				*pSession = pPair->m_value;
			hr = S_OK;
		}
		m_cs.Unlock();
		return hr;
	}

	STDMETHOD(Remove)(LPCTSTR szID)
	{
		HRESULT hr = E_INVALIDARG;
		if (!szID)
			return hr; // must have session name

		hr = m_cs.Lock();
		if (SUCCEEDED(hr))
		{
			hr = m_ConnectionMap.RemoveKey(szID) ? S_OK : E_FAIL;
			m_cs.Unlock();
		}

		return hr;
	}

	STDMETHOD(Lookup)(LPCTSTR szID, CDataConnection *pSession)
	{
		if (!szID||!pSession)
			return E_POINTER;

		HRESULT hr = m_cs.Lock();
		bool bRet = true;
		if (SUCCEEDED(hr))
		{
			bRet = m_ConnectionMap.Lookup(szID, *pSession);
			m_cs.Unlock();
		}

		return (bRet && (bool)*pSession)? hr : E_FAIL;
	}

protected:
	atlDataSourceCacheMap m_ConnectionMap;
	TCritSec m_cs;
};


// Some helpers for using the datasource cache.
//
// Function: GetDataSource
// Params:
//   pProvider: Pointer to IServiceProvider that provides the 
//              data source cache service
//   szID: The name of the connection (can be same as szDS)
//   szDS: OLEDB connection string for data source
//   ppDS: Out pointer to CDataSource. The CDataSource will be connected
//         to the OLEDB provider specified by szDS on successful return.
// RetVal:
//   Returns S_OK on success.
static HRESULT ATL_NOINLINE GetDataSource(IServiceProvider *pProvider, 
										  LPCTSTR szID, LPCOLESTR szConn, 
										  CDataConnection *pSession)
{
	if (!pProvider || !szID || !szConn)
		return E_POINTER;

	CComPtr<IDataSourceCache> spDSCache;
	HRESULT hr;
	hr = pProvider->QueryService(__uuidof(IDataSourceCache), __uuidof(IDataSourceCache), (void**)&spDSCache);
	if (hr == S_OK && spDSCache)
	{
		hr = spDSCache->Add(szID, szConn, pSession);
	}
	return hr;
}

//
// Function: RemoveDataSource
// Params:
//   pProvider: Pointer to IServiceProvider that provides the
//              data source cache service
//   szID: Name of the datasource connection to remove from the cache
// RetVal:
//   none
// Comments:
//   Removes the datasource entry from the datasource cache. Since entries are
//   copied to the client on calls to lookup and add, removing an entry will not
//   release the connections of existing clients.
static HRESULT ATL_NOINLINE RemoveDataSource(IServiceProvider *pProvider, LPCTSTR szID)
{
	if (!pProvider || !szID)
		return E_POINTER;

	CComPtr<IDataSourceCache> spDSCache;
	HRESULT hr = pProvider->QueryService(__uuidof(IDataSourceCache), __uuidof(IDataSourceCache), (void**)&spDSCache);
	if (spDSCache)
		hr = spDSCache->Remove(szID);
	return hr;
}

} // namespace ATL
#pragma pack(pop)

#pragma warning (pop)

#endif // __ATLCACHE_H__
