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

1067 lines
26 KiB

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// GENCACHE.H
//
// Header for generic cache classes.
//
// Copyright 1997-1998 Microsoft Corporation, All Rights Reserved
//
// Documenting my dependencies
// These must be included BEFORE this file is included.
//#include "caldbg.h"
//#include "autoptr.h"
#ifndef _EX_GENCACHE_H_
#define _EX_GENCACHE_H_
#pragma warning(disable:4200) // zero-sized array
// Exdav-safe allocators --------------------------------------------------
//
// The classed declared here use the EXDAV-safe allocators (ExAlloc, ExFree)
// for all allocations.
// NOTE: These allocators can FAIL. You must check for failure on
// ExAlloc and ExRealloc!
//
#include <ex\exmem.h>
#include <align.h>
// ========================================================================
//
// TEMPLATE CLASS CPoolAllocator
//
// A generic type-specific pool allocator template. Items in the pool
// are allocated in chunks and handed out upon request.
// Items are recycled on a free chain.
// All items are the same size, so reuse is relatively easy.
//
// NOTE: I have NOT optimized the heck out of this thing. To really
// optimize locality of mem usage, we'd want to always grow & shrink
// "at the tail". To that end, I always check the freechain first --
// reuse an item before using a new one from the current buffer.
// More optimization would require sorting the freechain & stuff.
//
template<class T>
class CPoolAllocator
{
// CHAINBUFHDR ----------------------------------------
// Header struct for chaining together pool buffers.
//
struct CHAINBUFHDR
{
CHAINBUFHDR * phbNext;
int cItems;
int cItemsUsed;
// Remainder of buffer is set of items of type T.
T rgtPool[0];
// Just to quiet our noisy compiler.
CHAINBUFHDR() {};
~CHAINBUFHDR() {};
};
// CHAINITEM ------------------------------------------
// Struct "applied over" free items to chain them together.
// The actual items MUST be large enough to accomodate this.
//
struct CHAINITEM
{
CHAINITEM * piNext;
};
// Chain of buffers.
CHAINBUFHDR * m_phbCurrent;
// Chain of free'd items to reuse.
CHAINITEM * m_piFreeChain;
// Size of chunk to alloc.
int m_ciChunkSize;
// Constant (enum) for our default starting chunk size (in items).
//
enum { CHUNKSIZE_START = 20 };
public:
// Constructor takes count of items for initial chunk size.
//
CPoolAllocator (int ciChunkSize = CHUNKSIZE_START) :
m_phbCurrent(NULL),
m_piFreeChain(NULL),
m_ciChunkSize(ciChunkSize)
{
// A CHAINITEM struct will be "applied over" free items to chain
// them together.
// The actual items MUST be large enough to accomodate this.
//
Assert (sizeof(T) >= sizeof(CHAINITEM));
};
~CPoolAllocator()
{
// Walk the list of blocks we allocated and free them.
//
while (m_phbCurrent)
{
CHAINBUFHDR * phbTemp = m_phbCurrent->phbNext;
ExFree (m_phbCurrent);
m_phbCurrent = phbTemp;
}
}
// ------------------------------------------------------------------------
// GetItem
// Return an item from the pool to our caller.
// We get the item from the free chain, or from the next block of items.
//
T * GetItem()
{
T * ptToReturn;
if (m_piFreeChain)
{
// The free chain is non-empty. Return the first item here.
//
ptToReturn = reinterpret_cast<T *>(m_piFreeChain);
m_piFreeChain = m_piFreeChain->piNext;
}
else
{
// The free chain is empty. We must grab a never-used item from
// the current block.
//
if (!m_phbCurrent ||
(m_phbCurrent->cItemsUsed == m_phbCurrent->cItems))
{
// There are no more items in the current block.
// Allocate a whole new block of items.
//
CHAINBUFHDR * phbNew = static_cast<CHAINBUFHDR *>(
ExAlloc (sizeof(CHAINBUFHDR) +
(m_ciChunkSize * sizeof(T))));
// The allocators CAN FAIL. Handle this case!
if (!phbNew)
return NULL;
phbNew->cItems = m_ciChunkSize;
phbNew->cItemsUsed = 0;
phbNew->phbNext = m_phbCurrent;
m_phbCurrent = phbNew;
}
// Now we should have a block with an unused item for us to return.
//
Assert (m_phbCurrent &&
(m_phbCurrent->cItemsUsed < m_phbCurrent->cItems));
ptToReturn = & m_phbCurrent->rgtPool[ m_phbCurrent->cItemsUsed++ ];
}
Assert (ptToReturn);
return ptToReturn;
}
// ------------------------------------------------------------------------
// FreeItem
// The caller is done with this item. Add it to our free chain.
//
void FreeItem (T * pi)
{
// Add the item to the free chain.
// To do this without allocating more memory, we use the item's
// storage to hold our next-pointer.
// The actual items MUST be large enough to accomodate this.
//
reinterpret_cast<CHAINITEM *>(pi)->piNext = m_piFreeChain;
m_piFreeChain = reinterpret_cast<CHAINITEM *>(pi);
}
};
// ========================================================================
//
// TEMPLATE CLASS CCache
//
// A generic hash cache template. Items in the cache uniquely map keys of
// type _K to values of type _Ty. Keys and values are copied when
// they are added to the cache; there is no "ownership".
//
// The key (type _K) must provide methods hash and isequal. These methods
// will be used to hash and compare the keys.
//
//
// Add()
// Adds an item (key/value pair) to the cache. Returns a reference
// to the added item's value.
//
// Set()
// Sets an item's value, adding the item if it doesn't already exist.
// Returns a reference to the added item's value.
//
// Lookup()
// Looks for an item with the specified key. If the item exists,
// returns a pointer to its value, otherwise returns NULL.
//
// FFetch()
// Boolean version of Lookup().
//
// Remove()
// Removes the item associated with a particular key.
// Does nothing if there is no item with that key.
//
// Clear()
// Removes all items from the cache.
//
// ForEach()
// Applies an operation, specified by an operator object passed in
// as a parameter, to all of the items in the cache.
//
// ForEachMatch()
// Applies an operation, specified by an operator object passed in
// as a parameter, to each item in the cache that matches the provided key.
//
// Additional functions proposed
// Rehash - currently ITP only
// Resize the table & re-add all items.
// DumpCacheUsage() - NYI
// Dump the bookkeeping data about the cache.
//
template<class _K, class _Ty>
class CCache
{
// ---------------------------------------------------------------------
// Cache Entry structures
//
struct Entry
{
struct Entry * peNext;
_K key;
_Ty data;
#ifdef DBG
BOOL fValid; // Is this entry valid?
#endif // DBG
// CONSTRUCTORS
Entry (const _K& k, const _Ty& d) :
key(k),
data(d)
{
};
//
// The following is to get around the fact that the store has
// defined a "new" macro which makes using the new operator to
// do in place initialization very difficult
//
void EntryInit (const _K& k, const _Ty& d) {
key = k;
data = d;
};
};
struct TableEntry //HashLine
{
BOOL fLineValid; // Is this cache line valid?
Entry * peChain;
#ifdef DBG
int cEntries; // Number of entries in this line in the cache.
mutable BYTE cAccesses; // Bookkeeping
#endif // DBG
};
// ---------------------------------------------------------------------
// The hash table data
//
int m_cSize; // Size of the hash table.
auto_heap_ptr<TableEntry> m_argTable; // The hash table.
int m_cItems; // Current number of items in the cache.
// ---------------------------------------------------------------------
// Pool allocator to alloc nodes
//
CPoolAllocator<Entry> m_poolalloc;
// ---------------------------------------------------------------------
// Constant (enum) for our default initial count of lines in the cache.
// NOTE: Callers should really pick their own best size.
// This size is prime to try to force fewer collisions.
//
enum { CACHESIZE_START = 37 };
#ifdef DBG
// ---------------------------------------------------------------------
// Bookkeeping bits
//
int m_cCollisions; // Adds that hit the same chain
mutable int m_cHits; // Lookup/Set hits
mutable int m_cMisses; // Lookup/Set misses
#endif // DBG
// ---------------------------------------------------------------------
// Helper function to build the table
//
BOOL FBuildTable()
{
Assert (!m_argTable.get());
// Allocate space for the number of cache lines we need (m_cSize).
//
m_argTable = reinterpret_cast<TableEntry *>(ExAlloc (
m_cSize * sizeof(TableEntry)));
// The allocators CAN FAIL. Handle this case!
if (!m_argTable.get())
return FALSE;
ZeroMemory (m_argTable.get(), m_cSize * sizeof(TableEntry));
return TRUE;
}
// ---------------------------------------------------------------------
// CreateEntry
// Create a new entry to add to the cache.
//
Entry * CreateEntry(const _K& k, const _Ty& d)
{
Entry * peNew = m_poolalloc.GetItem();
// The allocators CAN FAIL. Handle this case!
if (!peNew)
return NULL;
ZeroMemory (peNew, sizeof(Entry));
// peNew = new (peNew) Entry(k,d);
peNew->EntryInit (k,d);
#ifdef DBG
peNew->fValid = TRUE;
#endif // DBG
return peNew;
}
void DeleteEntry(Entry * pe)
{
pe->~Entry();
#ifdef DBG
pe->fValid = FALSE;
#endif // DBG
m_poolalloc.FreeItem (pe);
}
// NOT IMPLEMENTED
//
CCache (const CCache&);
CCache& operator= (const CCache&);
public:
// =====================================================================
//
// TEMPLATE CLASS IOp
//
// Operator base class interface used in ForEach() operations
// on the cache.
// The operator can return FALSE to cancel iteration, or TRUE to
// continue walking the cache.
//
class IOp
{
// NOT IMPLEMENTED
//
IOp& operator= (const IOp&);
public:
virtual BOOL operator() (const _K& key,
const _Ty& value) = 0;
};
// =====================================================================
//
// CREATORS
//
CCache (int cSize = CACHESIZE_START) :
m_cSize(cSize),
m_cItems(0)
{
Assert (m_cSize); // table size of zero is invalid!
// (and would cause div-by-zero errors later!)
#ifdef DBG
m_cCollisions = 0;
m_cHits = 0;
m_cMisses = 0;
#endif // DBG
};
~CCache()
{
// If we have a table (FInit was successfully called), clear it.
//
if (m_argTable.get())
Clear();
// Auto-pointer will clean up the table.
};
BOOL FInit()
{
// Set up the cache with the provided initial size.
// When running under the store (exdav.dll) THIS CAN FAIL!
//
return FBuildTable();
}
// =====================================================================
//
// ACCESSORS
//
// --------------------------------------------------------------------
// CItems
// Returns the number of items in the cache.
//
int CItems() const
{
return m_cItems;
}
// --------------------------------------------------------------------
// Lookup
// Find the first item in the cache that matches this key.
// key.hash is used to find the correct line of the cache.
// key.isequal is called on each item in the collision chain until a
// match is found.
//
_Ty * Lookup (const _K& key) const
{
// Find the index of the correct cache line for this key.
//
int iHash = key.hash(m_cSize);
Assert (iHash < m_cSize);
Assert (m_argTable.get());
#ifdef DBG
TableEntry * pte = &m_argTable[iHash];
pte->cAccesses++;
#endif // DBG
// Do we have any entries in this cache line?
// If this cache line is not valid, we have no entries -- NOT found.
//
if (m_argTable[iHash].fLineValid)
{
Entry * pe = m_argTable[iHash].peChain;
while (pe)
{
Assert (pe->fValid);
if (key.isequal (pe->key))
{
#ifdef DBG
m_cHits++;
#endif // DBG
return &pe->data;
}
pe = pe->peNext;
}
}
#ifdef DBG
m_cMisses++;
#endif // DBG
return NULL;
}
// --------------------------------------------------------------------
// FFetch
// Boolean-returning wrapper for Lookup.
//
BOOL FFetch (const _K& key, _Ty * pValueRet) const
{
_Ty * pValueFound = Lookup (key);
if (pValueFound)
{
*pValueRet = *pValueFound;
return TRUE;
}
return FALSE;
}
// --------------------------------------------------------------------
// ForEach
// Seek through the cache, calling the provided operator on each item.
// The operator can return FALSE to cancel iteration, or TRUE to continue
// walking the cache.
//
// NOTE: This function is built to allow deletion of the item being
// visited (see the comment inside the while loop -- fetch a pointer
// to the next item BEFORE calling the visitor op), BUT other
// deletes and adds are not "supported" and will have undefined results.
// Two specific disaster scenarios: delete of some other item could
// actually delete the item we pre-fetched, and we will crash on the
// next time around the loop. add of any item during the op callback
// could end up adding the item either before or after our current loop
// position, and thus might get visited, or might not.
//
void ForEach (IOp& op) const
{
// If we don't have any items, quit now!
//
if (!m_cItems)
return;
Assert (m_argTable.get());
// Loop through all items in the cache, calling the
// provided operator on each item.
//
for (int iHash = 0; iHash < m_cSize; iHash++)
{
// Look for valid cache entries.
//
if (m_argTable[iHash].fLineValid)
{
Entry * pe = m_argTable[iHash].peChain;
while (pe)
{
// To support deleting inside the op,
// fetch the next item BEFORE calling the op.
//
Entry * peNext = pe->peNext;
Assert (pe->fValid);
// Found a valid entry. Call the operator.
// If the operator returns TRUE, keep looping.
// If he returns FALSE, quit the loop.
//
if (!op (pe->key, pe->data))
return;
pe = peNext;
}
}
}
}
// --------------------------------------------------------------------
// ForEachMatch
// Seek through the cache, calling the provided operator on each item
// that has a matching key. This is meant to be used with a cache
// that may have duplicate items.
// The operator can return FALSE to cancel iteration, or TRUE to continue
// walking the cache.
//
void ForEachMatch (const _K& key, IOp& op) const
{
// If we don't have any items, quit now!
//
if (!m_cItems)
return;
// Find the index of the correct cache line for this key.
//
int iHash = key.hash(m_cSize);
Assert (iHash < m_cSize);
Assert (m_argTable.get());
#ifdef DBG
TableEntry * pte = &m_argTable[iHash];
pte->cAccesses++;
#endif // DBG
// Only process if this row of the cache is valid.
//
if (m_argTable[iHash].fLineValid)
{
// Loop through all items in this row of the cache, calling the
// provided operator on each item.
//
Entry * pe = m_argTable[iHash].peChain;
while (pe)
{
// To support deleting inside the op,
// fetch the next item BEFORE calling the op.
//
Entry * peNext = pe->peNext;
Assert (pe->fValid);
if (key.isequal (pe->key))
{
// Found a matching entry. Call the operator.
// If the operator returns TRUE, keep looping.
// If he returns FALSE, quit the loop.
//
if (!op (pe->key, pe->data))
return;
}
pe = peNext;
}
}
}
// =====================================================================
//
// MANIPULATORS
//
// --------------------------------------------------------------------
// FSet
// Reset the value of an item in the cache, adding it if the item
// does not yet exist.
//
BOOL FSet (const _K& key, const _Ty& value)
{
// Find the index of the correct cache line for this key.
//
int iHash = key.hash (m_cSize);
Assert (iHash < m_cSize);
Assert (m_argTable.get());
#ifdef DBG
TableEntry * pte = &m_argTable[iHash];
pte->cAccesses++;
#endif // DBG
// Look for valid cache entries.
//
if (m_argTable[iHash].fLineValid)
{
Entry * pe = m_argTable[iHash].peChain;
while (pe)
{
Assert (pe->fValid);
if (key.isequal (pe->key))
{
#ifdef DBG
m_cHits++;
#endif // DBG
pe->data = value;
return TRUE;
}
pe = pe->peNext;
}
}
#ifdef DBG
m_cMisses++;
#endif // DBG
// The items does NOT exist in the cache. Add it now.
//
return FAdd (key, value);
}
// --------------------------------------------------------------------
// FAdd
// Add an item to the cache.
// WARNING: Duplicate keys will be blindly added here! Use FSet()
// if you want to change the value for an existing item. Use Lookup()
// to check if a matching item already exists.
//$LATER: On DBG, scan the list for duplicate keys.
//
BOOL FAdd (const _K& key, const _Ty& value)
{
// Create a new element to add to the chain.
// NOTE: This calls the copy constructors for the key & value!
//
Entry * peNew = CreateEntry (key, value);
// The allocators CAN FAIL. Handle this case!
if (!peNew)
return FALSE;
// Find the index of the correct cache line for this key.
//
int iHash = key.hash (m_cSize);
Assert (iHash < m_cSize);
Assert (m_argTable.get());
#ifdef DBG
TableEntry * pte = &m_argTable[iHash];
pte->cEntries++;
if (m_argTable[iHash].peChain)
m_cCollisions++;
else
pte->cAccesses = 0;
#endif // DBG
// Link this new element into the chain.
//
peNew->peNext = m_argTable[iHash].peChain;
m_argTable[iHash].peChain = peNew;
m_argTable[iHash].fLineValid = TRUE;
m_cItems++;
return TRUE;
}
// --------------------------------------------------------------------
// Remove
// Remove an item from the cache.
//$REVIEW: Does this need to return a "found" boolean??
//
void Remove (const _K& key)
{
// Find the index of the correct cache line for this key.
//
int iHash = key.hash (m_cSize);
Assert (iHash < m_cSize);
Assert (m_argTable.get());
#ifdef DBG
TableEntry * pte = &m_argTable[iHash];
pte->cAccesses++;
#endif // DBG
// If this cache line is not valid, we have no entries --
// nothing to remove.
//
if (m_argTable[iHash].fLineValid)
{
Entry * pe = m_argTable[iHash].peChain;
Entry * peNext = pe->peNext;
Assert (pe->fValid);
// Delete first item in chain.
//
if (key.isequal (pe->key))
{
// Snip the item to delete (pe) out of the chain.
m_argTable[iHash].peChain = peNext;
if (!peNext)
{
// We deleted the last item. This line is empty.
//
m_argTable[iHash].fLineValid = FALSE;
}
// Delete entry to destroy the copied data (value) object.
DeleteEntry (pe);
m_cItems--;
#ifdef DBG
pte->cEntries--;
#endif // DBG
}
else
{
// Lookahead compare & delete.
//
while (peNext)
{
Assert (peNext->fValid);
if (key.isequal (peNext->key))
{
// Snip peNext out of the chain.
pe->peNext = peNext->peNext;
// Delete entry to destroy the copied data (value) object.
DeleteEntry (peNext);
m_cItems--;
#ifdef DBG
pte->cEntries--;
#endif // DBG
break;
}
// Advance
pe = peNext;
peNext = pe->peNext;
}
}
}
}
// --------------------------------------------------------------------
// Clear
// Clear all items from the cache.
// NOTE: This does not destroy the table -- the cache is still usable
// after this call.
//
void Clear()
{
if (m_argTable.get())
{
// Walk the cache, checking for valid lines.
//
for (int iHash = 0; iHash < m_cSize; iHash++)
{
// If the line if valid, look for items to clear out.
//
if (m_argTable[iHash].fLineValid)
{
Entry * pe = m_argTable[iHash].peChain;
// The cache line was marked as valid. There should be
// at least one item here.
Assert (pe);
// Walk the chain of items in this cache line.
//
while (pe)
{
Entry * peTemp = pe->peNext;
Assert (pe->fValid);
// Delete entry to destroy the copied data (value) object.
DeleteEntry (pe);
pe = peTemp;
}
}
// Clear out our cache line.
//
m_argTable[iHash].peChain = NULL;
m_argTable[iHash].fLineValid = FALSE;
#ifdef DBG
// Clear out the bookkeeping bits in the cache line.
//
m_argTable[iHash].cEntries = 0;
m_argTable[iHash].cAccesses = 0;
#endif // DBG
}
// We have no more items.
//
m_cItems = 0;
}
}
#ifdef ITP_USE_ONLY
// ---------------------------------------------------------------------
// Rehash
// Re-allocates the cache's hash table and re-hashes all items.
// NOTE: If this call fails (due to memory failure), the old hash table
// is restored so that we don't lose any the items.
// **RA** This call has NOT been tested in production (shipping) code!
// **RA** It is provided here for ITP use only!!!
//
BOOL FRehash (int cNewSize)
{
Assert (m_argTable.get());
// Swap out the old table and build a new one.
//
auto_heap_ptr<TableEntry> pOldTable ( m_argTable.relinquish() );
int cOldSize = m_cSize;
Assert (pOldTable.get());
m_cSize = cNewSize;
if (!FBuildTable())
{
Assert (pOldTable.get());
Assert (!m_argTable.get());
// Restore the old table.
//
m_cSize = cOldSize;
m_argTable = pOldTable.relinquish();
return FALSE;
}
// If no items in the cache, we're done!
//
if (!m_cItems)
{
return TRUE;
}
// Loop through all items in the cache (old table), placing them
// into the new table.
//
for ( int iHash = 0; iHash < cOldSize; iHash++ )
{
// Look for valid cache entries.
//
if (pOldTable[iHash].fLineValid)
{
Entry * pe = pOldTable[iHash].peChain;
while (pe)
{
// Keep track of next item.
Entry * peNext = pe->peNext;
Assert (pe->fValid);
// Found a valid entry. Place it in the new hash table.
//
int iHashNew = pe->key.hash (m_cSize);
pe->peNext = m_argTable[iHashNew].peChain;
m_argTable[iHashNew].peChain = pe;
m_argTable[iHashNew].fLineValid = TRUE;
#ifdef DBG
m_argTable[iHashNew].cEntries++;
#endif // DBG
pe = peNext;
}
}
}
// We're done re-filling the cache.
//
return TRUE;
}
#endif // ITP_USE_ONLY
};
// ========================================================================
// COMMON KEY CLASSES
// ========================================================================
// ========================================================================
// class DwordKey
// Key class for any dword data that can be compared with ==.
//
class DwordKey
{
private:
DWORD m_dw;
public:
DwordKey (DWORD dw) : m_dw(dw) {}
DWORD Dw() const
{
return m_dw;
}
int DwordKey::hash (const int rhs) const
{
return (m_dw % rhs);
}
bool DwordKey::isequal (const DwordKey& rhs) const
{
return (rhs.m_dw == m_dw);
}
};
// ========================================================================
// class PvoidKey
// Key class for any pointer data that can be compared with ==.
//
class PvoidKey
{
private:
PVOID m_pv;
public:
PvoidKey (PVOID pv) : m_pv(pv) {}
// operators for use with the hash cache
//
int PvoidKey::hash (const int rhs) const
{
// Since we are talking about ptrs, we want
// to shift the pointer such that the hash
// values don't tend to overlap due to alignment
// NOTE: This shouldn't matter if you choose your hash table size
// (rhs) well.... but it also doesn't hurt.
//
return (int)((reinterpret_cast<UINT_PTR>(m_pv) >> ALIGN_NATURAL) % rhs);
}
bool PvoidKey::isequal (const PvoidKey& rhs) const
{
// Just check if the values are equal.
//
return (rhs.m_pv == m_pv);
}
};
// ========================================================================
//
// CLASS Int64Key
//
// __int64-based key class for use with the CCache (hashcache).
//
class Int64Key
{
private:
__int64 m_i64;
// NOT IMPLEMENTED
//
bool operator< (const Int64Key& rhs) const;
public:
Int64Key (__int64 i64) :
m_i64(i64)
{
};
// operators for use with the hash cache
//
int hash (const int rhs) const
{
// Don't even bother with the high part of the int64.
// The mod operation would lose that part anyway....
//
return ( static_cast<UINT>(m_i64) % rhs );
}
BOOL isequal (const Int64Key& rhs) const
{
// Just check if the ids are equal.
//
return ( m_i64 == rhs.m_i64 );
}
};
// ========================================================================
// CLASS GuidKey
// Key class for per-MDB cache of prop-mapping tables.
//
class GuidKey
{
private:
const GUID * m_pguid;
public:
GuidKey (const GUID * pguid) :
m_pguid(pguid)
{
}
// operators for use with the hash cache
//
int hash (const int rhs) const
{
return (m_pguid->Data1 % rhs);
}
bool isequal (const GuidKey& rhs) const
{
return (!!IsEqualGUID (*m_pguid, *rhs.m_pguid));
}
};
// ========================================================================
// CLASS StoredGuidKey
// Key class for per-MDB cache of prop-mapping tables.
//
class StoredGuidKey
{
private:
GUID m_guid;
public:
StoredGuidKey (const GUID guid)
{
CopyMemory(&m_guid, &guid, sizeof(GUID));
}
// operators for use with the hash cache
//
int hash (const int rhs) const
{
return (m_guid.Data1 % rhs);
}
bool isequal (const StoredGuidKey& rhs) const
{
return (!!IsEqualGUID (m_guid, rhs.m_guid));
}
};
#endif // !_EX_GENCACHE_H_