3247 lines
76 KiB
C++
3247 lines
76 KiB
C++
// 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__
|