// This is a part of the Active Template Library.
// Copyright (C) 1996-2001 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 __ATLPERF_INL__
#define __ATLPERF_INL__

#pragma once

#ifndef __ATLPERF_H__
	#error atlperf.inl requires atlperf.h to be included first
#endif

#include <atlsecurity.h>

#pragma warning(push)

#ifndef _CPPUNWIND
#pragma warning(disable: 4702) // unreachable code
#endif

namespace ATL
{

__declspec(selectany) LPCTSTR c_szAtlPerfCounter = _T("Counter");
__declspec(selectany) LPCTSTR c_szAtlPerfFirstCounter = _T("First Counter");
__declspec(selectany) LPCTSTR c_szAtlPerfLastCounter = _T("Last Counter");
__declspec(selectany) LPCTSTR c_szAtlPerfHelp = _T("Help");
__declspec(selectany) LPCTSTR c_szAtlPerfFirstHelp = _T("First Help");
__declspec(selectany) LPCTSTR c_szAtlPerfLastHelp = _T("Last Help");

__declspec(selectany) LPCWSTR c_szAtlPerfGlobal = L"Global";
__declspec(selectany) LPCTSTR c_szAtlPerfLibrary = _T("Library");
__declspec(selectany) LPCTSTR c_szAtlPerfOpen = _T("Open");
__declspec(selectany) LPCTSTR c_szAtlPerfCollect = _T("Collect");
__declspec(selectany) LPCTSTR c_szAtlPerfClose = _T("Close");
__declspec(selectany) LPCTSTR c_szAtlPerfLanguages = _T("Languages");
__declspec(selectany) LPCTSTR c_szAtlPerfMap = _T("Map");
__declspec(selectany) LPCTSTR c_szAtlPerfServicesKey = _T("SYSTEM\\CurrentControlSet\\Services");
__declspec(selectany) LPCTSTR c_szAtlPerfPerformanceKey = _T("SYSTEM\\CurrentControlSet\\Services\\%s\\Performance");
__declspec(selectany) LPCTSTR c_szAtlPerfPerfLibKey = _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib");
__declspec(selectany) LPCTSTR c_szAtlPerfPerfLibLangKey = _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib\\%3.3x");

inline CPerfMon::~CPerfMon() throw()
{
	UnInitialize();
}

inline HRESULT CPerfMon::CreateMap(LANGID language, HINSTANCE hResInstance, UINT* pSampleRes) throw()
{
	language; // unused
	hResInstance; // unused
	pSampleRes; // unused
	return S_OK;
}

inline CPerfMapEntry& CPerfMon::_GetMapEntry(UINT nIndex) throw()
{
	ATLASSERT(nIndex < _GetNumMapEntries());
	return m_map[nIndex];
}

inline UINT CPerfMon::_GetNumMapEntries() throw()
{
	return (UINT) m_map.GetCount();
}

inline CPerfObject* CPerfMon::_GetFirstObject(CAtlFileMappingBase* pBlock) throw()
{
	ATLASSERT(pBlock != NULL);

	// should never happen if Initialize succeeded
	// are you checking return codes?
	ATLASSERT(pBlock->GetData() != NULL);

	return reinterpret_cast<CPerfObject*>(LPBYTE(pBlock->GetData()) + m_nHeaderSize);
}

inline CPerfObject* CPerfMon::_GetNextObject(CPerfObject* pInstance) throw()
{
	ATLASSERT(pInstance != NULL);

	return reinterpret_cast<CPerfObject*>(LPBYTE(pInstance) + pInstance->m_nAllocSize);
}

inline CAtlFileMappingBase* CPerfMon::_GetNextBlock(CAtlFileMappingBase* pBlock) throw()
{
	// calling _GetNextBlock(NULL) will return the first block
	DWORD dwNextBlockIndex = 0;
	if (pBlock)
	{
		dwNextBlockIndex= _GetBlockId(pBlock) +1;
		if (DWORD(m_aMem.GetCount()) == dwNextBlockIndex)
			return NULL;
	}
	return m_aMem[dwNextBlockIndex];
}

inline CAtlFileMappingBase* CPerfMon::_AllocNewBlock(CAtlFileMappingBase* pPrev, BOOL* pbExisted /* == NULL */) throw()
{
	// initialize a security descriptor to give everyone access to objects we create
	CSecurityDescriptor sd;
	sd.InitializeFromThreadToken();
	SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), sd, FALSE };

	CAutoPtr<CAtlFileMappingBase> spMem;
	CAtlFileMappingBase* pMem = NULL;
	ATLTRY(spMem.Attach(new CAtlFileMappingBase));
	if (spMem == NULL)
		return NULL;

	// create a unique name for the shared mem segment based on the index
	DWORD dwNextBlockIndex;
	if (pPrev != NULL)
		dwNextBlockIndex = _GetBlockId(pPrev) +1;
	else
	{
		// use the system allocation granularity (65536 currently. may be different in the future)
		SYSTEM_INFO si;
		GetSystemInfo(&si);
		m_nAllocSize = si.dwAllocationGranularity;

		dwNextBlockIndex = 0;
	}

	BOOL bExisted = FALSE;
	_ATLTRY
	{
		CString strName;
		strName.Format(_T("ATLPERF_%s_%3.3d"), GetAppName(), dwNextBlockIndex);

		HRESULT hr = spMem->MapSharedMem(m_nAllocSize, strName, &bExisted, &sa);
		if (FAILED(hr))
			return NULL;

		// save the index of this block
		// don't for first block since we don't know m_nSchemaSize yet
		if (dwNextBlockIndex)
			_GetBlockId(spMem) = dwNextBlockIndex;

		if (!bExisted)
			memset(spMem->GetData(), 0, m_nAllocSize);

		if (pbExisted)
			*pbExisted = bExisted;

		pMem = spMem;
		m_aMem.Add(spMem);

		OnBlockAlloc(pMem);
	}
	_ATLCATCHALL()
	{
		return NULL;
	}

	return pMem;
}

inline HRESULT CPerfMon::_LoadMap() throw()
{
	_ATLTRY
	{
		HRESULT hr;

		ClearMap();

		DWORD* pData = LPDWORD(m_aMem[0]->GetData());

		DWORD dwDataSize = *pData++; // blob size
		DWORD dwNumItems = *pData++; // number of items

		// see if we have name data
		DWORD* pNameData = NULL;
		if (dwDataSize > (2+dwNumItems*9) * sizeof(DWORD))
			pNameData = pData + dwNumItems*9; // blob size and item count already skipped. skip item data

		for (DWORD i=0; i<dwNumItems; i++)
		{
			DWORD dwIsObject = *pData++;
			DWORD dwPerfId = *pData++;
			DWORD dwDetailLevel = *pData++;

			CString strName;
			if (pNameData)
			{
				strName = CString(LPWSTR(pNameData+1), *pNameData);
				pNameData += AtlAlignUp(sizeof(WCHAR) * *pNameData, sizeof(DWORD))/sizeof(DWORD) + 1;
			}

			if (dwIsObject)
			{
				DWORD dwDefaultCounter = *pData++;
				DWORD dwInstanceLess = *pData++;
				DWORD dwStructSize = *pData++;
				DWORD dwMaxInstanceNameLen = *pData++;

				hr = AddObjectDefinition(
					dwPerfId,
					strName,
					NULL,
					dwDetailLevel,
					dwDefaultCounter,
					dwInstanceLess,
					dwStructSize,
					dwMaxInstanceNameLen);
				if (FAILED(hr))
				{
					ClearMap();
					return hr;
				}
			}
			else
			{
				DWORD dwCounterType = *pData++;
				DWORD dwMaxCounterSize = *pData++;
				DWORD dwDataOffset = *pData++;
				DWORD dwDefaultScale = *pData++;

				hr = AddCounterDefinition(
					dwPerfId,
					strName,
					NULL,
					dwDetailLevel,
					dwCounterType,
					dwMaxCounterSize,
					dwDataOffset,
					dwDefaultScale);
				if (FAILED(hr))
				{
					ClearMap();
					return hr;
				}
			}

			DWORD dwNameId = *pData++;
			DWORD dwHelpId = *pData++;
			CPerfMapEntry& entry = _GetMapEntry(_GetNumMapEntries()-1);
			entry.m_nNameId = dwNameId;
			entry.m_nHelpId = dwHelpId;
		}

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

inline HRESULT CPerfMon::_SaveMap() throw()
{
	_ATLTRY
	{
		// figure out how much memory we need
		size_t nSize = (2 + 9*_GetNumMapEntries()) * sizeof(DWORD);
		for (UINT i=0; i<_GetNumMapEntries(); i++)
		{
			// if any of the entries have names, they'd better all have names
			CPerfMapEntry& entry = _GetMapEntry(i);
			if (!entry.m_strName.IsEmpty())
				nSize += sizeof(DWORD) + AtlAlignUp(sizeof(WCHAR) * entry.m_strName.GetLength(), sizeof(DWORD));
		}

		CHeapPtr<BYTE> blob;
		if (!blob.Allocate(nSize))
			return E_OUTOFMEMORY;

		// start with blob size and number of items in the blob
		DWORD* pCurrent = reinterpret_cast<DWORD*>(blob.m_pData);
		*pCurrent++ = (DWORD) nSize; // blob size
		*pCurrent++ = _GetNumMapEntries(); // number of items

		for (UINT i=0; i<_GetNumMapEntries(); i++)
		{
			// add all the relevant runtime info to the blob for each item
			CPerfMapEntry& entry = _GetMapEntry(i);
			*pCurrent++ = entry.m_bIsObject;
			*pCurrent++ = entry.m_dwPerfId;
			*pCurrent++ = entry.m_dwDetailLevel;
			if (entry.m_bIsObject)
			{
				*pCurrent++ = entry.m_nDefaultCounter;
				*pCurrent++ = entry.m_nInstanceLess;
				*pCurrent++ = entry.m_nStructSize;
				*pCurrent++ = entry.m_nMaxInstanceNameLen;
			}
			else
			{
				*pCurrent++ = entry.m_dwCounterType;
				*pCurrent++ = entry.m_nMaxCounterSize;
				*pCurrent++ = entry.m_nDataOffset;
				*pCurrent++ = entry.m_nDefaultScale;
			}
			*pCurrent++ = entry.m_nNameId;
			*pCurrent++ = entry.m_nHelpId;
		}

		// add names to the blob
		for (UINT i=0; i<_GetNumMapEntries(); i++)
		{
			// if any of the entries have names, they'd better all have names
			CPerfMapEntry& entry = _GetMapEntry(i);
			if (!entry.m_strName.IsEmpty())
			{
				// copy the len of the string (in characters) then the wide-char version of the string
				// pad the string to a dword boundary
				int nLen = entry.m_strName.GetLength();
				*pCurrent++ = nLen;
				memcpy(pCurrent, CT2CW(entry.m_strName), sizeof(WCHAR)*nLen);
				pCurrent += AtlAlignUp(sizeof(WCHAR) * nLen, sizeof(DWORD))/sizeof(DWORD);
			}
		}

		CRegKey rkApp;
		CString str;
		DWORD dwErr;

		str.Format(c_szAtlPerfPerformanceKey, GetAppName());
		dwErr = rkApp.Open(HKEY_LOCAL_MACHINE, str);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		rkApp.SetBinaryValue(c_szAtlPerfMap, blob, *LPDWORD(blob.m_pData));

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

inline CPerfMapEntry* CPerfMon::_FindObjectInfo(DWORD dwObjectId) throw()
{
	for (UINT i=0; i<_GetNumMapEntries(); i += _GetMapEntry(i).m_nNumCounters+1)
	{
		CPerfMapEntry& object = _GetMapEntry(i);
		if (object.m_dwPerfId == dwObjectId)
			return &object;
	}

	return NULL;
}

inline CPerfMapEntry* CPerfMon::_FindCounterInfo(CPerfMapEntry* pObjectEntry, DWORD dwCounterId) throw()
{
	ATLASSERT(pObjectEntry != NULL);

	for (DWORD i=0; i<pObjectEntry->m_nNumCounters; i++)
	{
		CPerfMapEntry* pCounter = pObjectEntry+i+1;
		if (pCounter->m_dwPerfId == dwCounterId)
			return pCounter;
	}

	return NULL;
}

inline CPerfMapEntry* CPerfMon::_FindCounterInfo(DWORD dwObjectId, DWORD dwCounterId) throw()
{
	CPerfMapEntry* pObjectEntry = _FindObjectInfo(dwObjectId);
	if (pObjectEntry != NULL)
		return _FindCounterInfo(pObjectEntry, dwCounterId);

	return NULL;
}

inline BOOL CPerfMon::_WantObjectType(LPWSTR szValue, DWORD dwObjectId) throw(...)
{
	ATLASSERT(szValue != NULL);

	if (lstrcmpiW(c_szAtlPerfGlobal, szValue) == 0)
		return TRUE;

	CString strList(szValue);
	int nStart = 0;

	CString strNum = strList.Tokenize(_T(" "), nStart);
	while (!strNum.IsEmpty())
	{
		if (_ttoi(strNum) == int(dwObjectId))
			return TRUE;

		strNum = strList.Tokenize(_T(" "), nStart);
	}

	return FALSE;
}

inline LPBYTE CPerfMon::_AllocData(LPBYTE& pData, ULONG nBytesAvail, ULONG* pnBytesUsed, size_t nBytesNeeded) throw()
{
	ATLASSERT(pnBytesUsed != NULL);

	if (nBytesAvail < *pnBytesUsed + (ULONG) nBytesNeeded)
		return NULL;

	LPBYTE p = pData;
	pData += nBytesNeeded;
	*pnBytesUsed += (ULONG) nBytesNeeded;

	return p;
}

inline DWORD& CPerfMon::_GetBlockId(CAtlFileMappingBase* pBlock) throw()
{
	ATLASSERT(pBlock != NULL);

	return *LPDWORD(LPBYTE(pBlock->GetData()) + m_nSchemaSize);
}

inline void CPerfMon::_FillObjectType(PERF_OBJECT_TYPE* pObjectType, CPerfMapEntry* pObjectEntry) throw()
{
	ATLASSERT(pObjectType != NULL);
	ATLASSERT(pObjectEntry != NULL);

    pObjectType->DefinitionLength = sizeof(PERF_OBJECT_TYPE) + sizeof(PERF_COUNTER_DEFINITION) * pObjectEntry->m_nNumCounters;
    pObjectType->TotalByteLength = pObjectType->DefinitionLength; // we will add the instance definitions/counter blocks as we go
    pObjectType->HeaderLength = sizeof(PERF_OBJECT_TYPE);
    pObjectType->ObjectNameTitleIndex = pObjectEntry->m_nNameId;
    pObjectType->ObjectNameTitle = NULL;
    pObjectType->ObjectHelpTitleIndex = pObjectEntry->m_nHelpId;
    pObjectType->ObjectHelpTitle = NULL;
    pObjectType->DetailLevel = pObjectEntry->m_dwDetailLevel;
    pObjectType->NumCounters = pObjectEntry->m_nNumCounters;
    pObjectType->DefaultCounter = pObjectEntry->m_nDefaultCounter;
	if (pObjectEntry->m_nInstanceLess == PERF_NO_INSTANCES)
		pObjectType->NumInstances = PERF_NO_INSTANCES;
	else
		pObjectType->NumInstances = 0; // this will be calculated as we go
    pObjectType->CodePage = 0;
    pObjectType->PerfTime.QuadPart = 0;
    pObjectType->PerfFreq.QuadPart = 0;
}

inline void CPerfMon::_FillCounterDef(
	PERF_COUNTER_DEFINITION* pCounterDef,
	CPerfMapEntry* pCounterEntry,
	ULONG& nCBSize
	) throw()
{
	ATLASSERT(pCounterDef != NULL);
	ATLASSERT(pCounterEntry != NULL);

	pCounterDef->ByteLength = sizeof(PERF_COUNTER_DEFINITION);
	pCounterDef->CounterNameTitleIndex = pCounterEntry->m_nNameId;
	pCounterDef->CounterNameTitle = NULL;
	pCounterDef->CounterHelpTitleIndex = pCounterEntry->m_nHelpId;
	pCounterDef->CounterHelpTitle = NULL;
	pCounterDef->DefaultScale = pCounterEntry->m_nDefaultScale;
	pCounterDef->DetailLevel = pCounterEntry->m_dwDetailLevel;
	pCounterDef->CounterType = pCounterEntry->m_dwCounterType;
	switch (pCounterEntry->m_dwCounterType & ATLPERF_SIZE_MASK)
	{
	case PERF_SIZE_DWORD:
		pCounterDef->CounterSize = sizeof(DWORD);
		break;
	case PERF_SIZE_LARGE:
		pCounterDef->CounterSize = sizeof(__int64);
		break;
	case PERF_SIZE_ZERO:
		pCounterDef->CounterSize = 0;
		break;
	case PERF_SIZE_VARIABLE_LEN:
		ATLASSERT((pCounterEntry->m_dwCounterType & ATLPERF_TYPE_MASK) == PERF_TYPE_TEXT);
		if ((pCounterEntry->m_dwCounterType & ATLPERF_TEXT_MASK) == PERF_TEXT_UNICODE)
			pCounterDef->CounterSize = (DWORD) AtlAlignUp(pCounterEntry->m_nMaxCounterSize * sizeof(WCHAR), sizeof(DWORD));
		else
			pCounterDef->CounterSize = (DWORD) AtlAlignUp(pCounterEntry->m_nMaxCounterSize * sizeof(char), sizeof(DWORD));
		break;
	}
	pCounterDef->CounterOffset = sizeof(PERF_COUNTER_BLOCK) + nCBSize;
	nCBSize += pCounterDef->CounterSize;
}

inline HRESULT CPerfMon::_CollectObjectType(
	CPerfMapEntry* pObjectEntry,
	LPBYTE pData,
	ULONG nBytesAvail,
	ULONG* pnBytesUsed
	) throw()
{
	ATLASSERT(pObjectEntry != NULL);
	ATLASSERT(pnBytesUsed != NULL);

	ATLASSERT(m_aMem.GetCount() != 0);

	*pnBytesUsed = 0;

	// write the object definition out
	PERF_OBJECT_TYPE* pObjectType = _AllocStruct(pData, nBytesAvail, pnBytesUsed, (PERF_OBJECT_TYPE*) NULL);
	if (pObjectType == NULL)
		return E_OUTOFMEMORY;

	_FillObjectType(pObjectType, pObjectEntry);

	// save a pointer to the first counter entry and counter definition.
	// we'll need them when we create the PERF_COUNTER_BLOCK data
	CPerfMapEntry* pCounterEntries = pObjectEntry + 1;
	PERF_COUNTER_DEFINITION* pCounterDefs = reinterpret_cast<PERF_COUNTER_DEFINITION*>(pData);
	ULONG nCBSize = 0; // counter block size

	// write the counter definitions out
	for (DWORD i=0; i<pObjectEntry->m_nNumCounters; i++)
	{
		CPerfMapEntry* pCounterEntry = pObjectEntry+i+1;

		PERF_COUNTER_DEFINITION* pCounterDef = _AllocStruct(pData, nBytesAvail, pnBytesUsed, (PERF_COUNTER_DEFINITION*) NULL);
		if (pCounterDef == NULL)
			return E_OUTOFMEMORY;

		_FillCounterDef(pCounterDef, pCounterEntry, nCBSize);
	}

	// search for objects of the appropriate type and write out their instance/counter data
	CAtlFileMappingBase* pCurrentBlock = m_aMem[0];
	CPerfObject* pInstance = _GetFirstObject(pCurrentBlock);
	while (pInstance && pInstance->m_nAllocSize != 0)
	{
		if (pInstance->m_dwObjectId == pObjectEntry->m_dwPerfId)
		{
			PERF_INSTANCE_DEFINITION* pInstanceDef = NULL;

			if (pObjectEntry->m_nInstanceLess == PERF_NO_INSTANCES)
				pObjectType->NumInstances = PERF_NO_INSTANCES;
			else
			{
				pObjectType->NumInstances++;

				// create an instance definition
				pInstanceDef = _AllocStruct(pData, nBytesAvail, pnBytesUsed, (PERF_INSTANCE_DEFINITION*) NULL);
				if (pInstanceDef == NULL)
					return E_OUTOFMEMORY;

				pInstanceDef->ParentObjectTitleIndex = 0;
				pInstanceDef->ParentObjectInstance = 0;
				pInstanceDef->UniqueID = PERF_NO_UNIQUE_ID;

				// handle the instance name
				LPCWSTR szInstNameSrc = LPCWSTR(LPBYTE(pInstance)+pInstance->m_nInstanceNameOffset);
				pInstanceDef->NameLength = (ULONG)(wcslen(szInstNameSrc)+1)*sizeof(WCHAR);
				LPWSTR szInstNameDest = (LPWSTR) _AllocData(pData, nBytesAvail, pnBytesUsed, AtlAlignUp(pInstanceDef->NameLength, sizeof(DWORD)));
				if (szInstNameDest == NULL)
					return E_OUTOFMEMORY;

				memcpy(szInstNameDest, szInstNameSrc, pInstanceDef->NameLength);
				pInstanceDef->NameOffset = ULONG(LPBYTE(szInstNameDest) - LPBYTE(pInstanceDef));

				pInstanceDef->ByteLength = DWORD(sizeof(PERF_INSTANCE_DEFINITION) + AtlAlignUp(pInstanceDef->NameLength, sizeof(DWORD)));
			}

			// create the counter block
			PERF_COUNTER_BLOCK* pCounterBlock = _AllocStruct(pData, nBytesAvail, pnBytesUsed, (PERF_COUNTER_BLOCK*) NULL);
			if (pCounterBlock == NULL)
				return E_OUTOFMEMORY;

			pCounterBlock->ByteLength = sizeof(PERF_COUNTER_BLOCK) + nCBSize;

			LPBYTE pCounterData = _AllocData(pData, nBytesAvail, pnBytesUsed, nCBSize);
			if (pCounterData == NULL)
				return E_OUTOFMEMORY;

			for (ULONG i=0; i<pObjectType->NumCounters; i++)
			{
				switch (pCounterEntries[i].m_dwCounterType & ATLPERF_SIZE_MASK)
				{
				case PERF_SIZE_DWORD:
					*LPDWORD(pCounterData+pCounterDefs[i].CounterOffset-sizeof(PERF_COUNTER_BLOCK)) =
						*LPDWORD(LPBYTE(pInstance)+pCounterEntries[i].m_nDataOffset);
					break;
				case PERF_SIZE_LARGE:
					*PULONGLONG(pCounterData+pCounterDefs[i].CounterOffset-sizeof(PERF_COUNTER_BLOCK)) =
						*PULONGLONG(LPBYTE(pInstance)+pCounterEntries[i].m_nDataOffset);
					break;
				case PERF_SIZE_VARIABLE_LEN:
					{
						LPBYTE pSrc = LPBYTE(pInstance)+pObjectEntry->m_nDataOffset;
						LPBYTE pDest = pCounterData+pCounterDefs[i].CounterOffset-sizeof(PERF_COUNTER_BLOCK);
						if ((pCounterEntries[i].m_dwCounterType & ATLPERF_TEXT_MASK) == PERF_TEXT_UNICODE)
						{
							ULONG nLen = (ULONG)wcslen(LPCWSTR(pSrc));
							nLen = min(nLen, pCounterEntries[i].m_nMaxCounterSize-1);
							wcsncpy(LPWSTR(pDest), LPCWSTR(pSrc), nLen);
							((LPWSTR) pDest)[nLen] = 0;
						}
						else
						{
							ULONG nLen = (ULONG)strlen(LPCSTR(pSrc));
							nLen = min(nLen, pCounterEntries[i].m_nMaxCounterSize-1);
							strncpy(LPSTR(pDest), LPCSTR(pSrc), nLen);
							((LPSTR) pDest)[nLen] = 0;
						}
					}
					break;
				}
			}

			if (pInstanceDef != NULL)
				pObjectType->TotalByteLength += pInstanceDef->ByteLength;
			pObjectType->TotalByteLength += sizeof(PERF_COUNTER_BLOCK) + nCBSize;
		}

		pInstance = _GetNextObject(pInstance);
		if (pInstance->m_nAllocSize == (ULONG) -1)
		{
			pCurrentBlock = _GetNextBlock(pCurrentBlock);
			if (pCurrentBlock == NULL)
				pInstance = NULL;
			else
				pInstance = _GetFirstObject(pCurrentBlock);
		}
	}

	return S_OK;
}

inline DWORD CPerfMon::Open(LPWSTR szDeviceNames) throw()
{
	szDeviceNames; // unused
	return Initialize();
}

inline DWORD CPerfMon::Collect(
	LPWSTR szValue,
	LPVOID* ppData,
	LPDWORD pcbBytes,
	LPDWORD pcObjectTypes
	) throw()
{
	_ATLTRY
	{
		if (m_aMem.GetCount() == 0 || m_aMem[0]->GetData() == NULL || m_lock.m_h == NULL)
		{
			*pcbBytes = 0;
			*pcObjectTypes = 0;
			return ERROR_SUCCESS;
		}

		// get a lock so that other threads don't corrupt the data we're collecting
		CPerfLock lock(this);
		if (FAILED(lock.GetStatus()))
		{
			*pcbBytes = 0;
			*pcObjectTypes = 0;
			return ERROR_SUCCESS;
		}

		LPBYTE pData = LPBYTE(*ppData);
		ULONG nBytesLeft = *pcbBytes;
		ULONG nBytesUsed;
		*pcbBytes = 0;

		for (UINT i=0; i<_GetNumMapEntries(); i += _GetMapEntry(i).m_nNumCounters+1)
		{
			CPerfMapEntry* pObjectEntry = &_GetMapEntry(i);
			if (_WantObjectType(szValue, pObjectEntry->m_nNameId))
			{
				if (FAILED(_CollectObjectType(pObjectEntry, pData, nBytesLeft, &nBytesUsed)))
				{
					*pcbBytes = 0;
					*pcObjectTypes = 0;
					return ERROR_SUCCESS;
				}

				(*pcObjectTypes)++;
				(*pcbBytes) += nBytesUsed;
				nBytesLeft -= nBytesUsed;
				pData += nBytesUsed;
			}
		}

		*ppData = pData;
		return ERROR_SUCCESS;
	}
	_ATLCATCHALL()
	{
		*pcbBytes = 0;
		*pcObjectTypes = 0;
		return ERROR_SUCCESS;
	}
}

inline DWORD CPerfMon::Close() throw()
{
	UnInitialize();
	return ERROR_SUCCESS;
}

#ifdef _ATL_PERF_REGISTER
inline void CPerfMon::_AppendStrings(
	LPTSTR& pszNew,
	CAtlArray<CString>& astrStrings,
	ULONG iFirstIndex
	) throw()
{
	for (UINT iString = 0; iString < astrStrings.GetCount(); iString++)
	{
		INT nFormatChars = _stprintf(pszNew, _T("%d"), iFirstIndex+2*iString);
		pszNew += nFormatChars + 1;
		_tcscpy(pszNew, astrStrings[iString]);
		pszNew += astrStrings[iString].GetLength() + 1;
	}
}

inline HRESULT CPerfMon::_AppendRegStrings(
	CRegKey& rkLang,
	LPCTSTR szValue,
	CAtlArray<CString>& astrStrings,
	ULONG nNewStringSize,
	ULONG iFirstIndex,
	ULONG iLastIndex
	) throw()
{
	_ATLTRY
	{
		// load the existing strings, add the new data, and resave the strings
		ULONG nCharsOrig = 0;
		ULONG nCharsNew;
		DWORD dwErr;

		dwErr = rkLang.QueryMultiStringValue(szValue, NULL, &nCharsOrig);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		nCharsNew = nCharsOrig + nNewStringSize;

		CString strOrig;
		dwErr = rkLang.QueryMultiStringValue(szValue, CStrBuf(strOrig, nCharsOrig, CStrBuf::SET_LENGTH), &nCharsOrig);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);
		LPCTSTR pszOrig = strOrig;

		CString strNew;
		CStrBuf szNew(strNew, nCharsNew, CStrBuf::SET_LENGTH);
		LPTSTR pszNew = szNew;

		bool bNewStringsAdded = false;

		while (*pszOrig != '\0')
		{
			ULONG iIndex = _ttoi(pszOrig);
			int nLen = (int) _tcslen(pszOrig) + 1; // get the length of the index and null
			nLen += (int) _tcslen(pszOrig+nLen) + 1; // add the length of the description and null

			if (!bNewStringsAdded && iIndex >= iFirstIndex)
			{
				_AppendStrings(pszNew, astrStrings, iFirstIndex);
				bNewStringsAdded = true;
			}

			if (iIndex < iFirstIndex || iIndex > iLastIndex)
			{
				memmove(pszNew, pszOrig, nLen*sizeof(TCHAR));
				pszNew += nLen;
			}
			pszOrig += nLen;
		}
		if (!bNewStringsAdded)
			_AppendStrings(pszNew, astrStrings, iFirstIndex);

		*pszNew++ = '\0'; // must have 2 null terminators at end of multi_sz

		dwErr = rkLang.SetMultiStringValue(szValue, strNew);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

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

inline HRESULT CPerfMon::_RemoveRegStrings(
	CRegKey& rkLang,
	LPCTSTR szValue,
	ULONG iFirstIndex,
	ULONG iLastIndex
	) throw()
{
	_ATLTRY
	{
		// load the existing strings, remove the data, and resave the strings
		DWORD nChars = 0;
		DWORD dwErr;
		
		dwErr = rkLang.QueryMultiStringValue(szValue, NULL, &nChars);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		CString str;
		CStrBuf szBuf(str, nChars, CStrBuf::SET_LENGTH);

		dwErr = rkLang.QueryMultiStringValue(szValue, szBuf, &nChars);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		LPCTSTR pszRead = szBuf;
		LPTSTR pszWrite = szBuf;
		while (*pszRead != '\0')
		{
			ULONG iIndex = _ttoi(pszRead);
			int nLen = (int) _tcslen(pszRead) + 1; // get the length of the index and null
			nLen += (int) _tcslen(pszRead+nLen) + 1; // add the length of the description and null
			if (iIndex < iFirstIndex || iIndex > iLastIndex)
			{
				memmove(pszWrite, pszRead, nLen*sizeof(TCHAR));
				pszWrite += nLen;
			}
			pszRead += nLen;
		}
		*pszWrite++ = '\0'; // must have 2 null terminators at end of multi_sz

		dwErr = rkLang.SetMultiStringValue(szValue, szBuf);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

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

inline HRESULT CPerfMon::_ReserveStringRange(DWORD& dwFirstCounter, DWORD& dwFirstHelp) throw()
{
	CRegKey rkApp;
	CString strAppKey;
	DWORD dwErr;

	_ATLTRY
	{
		strAppKey.Format(c_szAtlPerfPerformanceKey, GetAppName());
	}
	_ATLCATCHALL()
	{
		return E_OUTOFMEMORY;
	}

	dwErr = rkApp.Open(HKEY_LOCAL_MACHINE, strAppKey);
	if (dwErr == ERROR_SUCCESS)
	{
		// see if we already have a sufficient range reserved
		DWORD dwFirstAppCounter;
		DWORD dwFirstAppHelp;
		DWORD dwLastAppCounter;
		DWORD dwLastAppHelp;
	
		if (rkApp.QueryDWORDValue(c_szAtlPerfFirstCounter, dwFirstAppCounter) == ERROR_SUCCESS &&
				rkApp.QueryDWORDValue(c_szAtlPerfFirstHelp, dwFirstAppHelp) == ERROR_SUCCESS &&
				rkApp.QueryDWORDValue(c_szAtlPerfLastCounter, dwLastAppCounter) == ERROR_SUCCESS &&
				rkApp.QueryDWORDValue(c_szAtlPerfLastHelp, dwLastAppHelp) == ERROR_SUCCESS &&
				dwLastAppCounter-dwFirstAppCounter+2 >= DWORD(2*_GetNumMapEntries()) &&
				dwLastAppHelp-dwFirstAppHelp+2 >= DWORD(2*_GetNumMapEntries()))
		{
			dwFirstCounter = dwFirstAppCounter;
			dwFirstHelp = dwFirstAppHelp;
			return S_OK;
		}
	}

	CRegKey rkPerfLib;

	dwErr = rkPerfLib.Open(HKEY_LOCAL_MACHINE, c_szAtlPerfPerfLibKey);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	if (!rkApp)
	{
		dwErr = rkApp.Create(HKEY_LOCAL_MACHINE, strAppKey);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);
	}

	// figure out the counter range
	DWORD dwLastCounter;
	DWORD dwLastHelp;

	dwErr = rkPerfLib.QueryDWORDValue(c_szAtlPerfLastCounter, dwLastCounter);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkPerfLib.QueryDWORDValue(c_szAtlPerfLastHelp, dwLastHelp);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwFirstCounter = dwLastCounter + 2;
	dwFirstHelp = dwLastHelp + 2;
	dwLastCounter += 2*_GetNumMapEntries();
	dwLastHelp += 2*_GetNumMapEntries();

	dwErr = rkPerfLib.SetDWORDValue(c_szAtlPerfLastCounter, dwLastCounter);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkPerfLib.SetDWORDValue(c_szAtlPerfLastHelp, dwLastHelp);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	// register the used counter range
	dwErr = rkApp.SetDWORDValue(c_szAtlPerfFirstCounter, dwFirstCounter);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkApp.SetDWORDValue(c_szAtlPerfLastCounter, dwLastCounter);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkApp.SetDWORDValue(c_szAtlPerfFirstHelp, dwFirstHelp);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkApp.SetDWORDValue(c_szAtlPerfLastHelp, dwLastHelp);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	return S_OK;
}

inline HRESULT CPerfMon::Register(
	LPCTSTR szOpenFunc,
	LPCTSTR szCollectFunc,
	LPCTSTR szCloseFunc,
	HINSTANCE hDllInstance /* == _AtlBaseModule.GetModuleInstance() */
	) throw()
{
	ATLASSERT(szOpenFunc != NULL);
	ATLASSERT(szCollectFunc != NULL);
	ATLASSERT(szCloseFunc != NULL);

	CString str;
	DWORD dwErr;
	HRESULT hr;

	hr = CreateMap(LANGIDFROMLCID(GetThreadLocale()), hDllInstance);
	if (FAILED(hr))
		return hr;

	CString strAppKey;
	_ATLTRY
	{
		strAppKey.Format(c_szAtlPerfPerformanceKey, GetAppName());
	}
	_ATLCATCHALL()
	{
		return E_OUTOFMEMORY;
	}

	// if we're already registered, unregister so we can redo registration
	_UnregisterStrings();
	
	// reserve a range for our counter and help strings
	DWORD dwFirstCounter = 0;
	DWORD dwFirstHelp = 0;
	_ReserveStringRange(dwFirstCounter, dwFirstHelp);

	for (UINT i=0; i<_GetNumMapEntries(); i++)
	{
		CPerfMapEntry& entry = _GetMapEntry(i);

		entry.m_nNameId = dwFirstCounter + i*2;
		entry.m_nHelpId = dwFirstHelp + i*2;
	}

	// register the app entry points
	CRegKey rkApp;

	dwErr = rkApp.Create(HKEY_LOCAL_MACHINE, strAppKey);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	_ATLTRY
	{
		if (GetModuleFileName(hDllInstance, CStrBuf(str, MAX_PATH), MAX_PATH) == 0)
			return AtlHresultFromLastError();
	}
	_ATLCATCHALL()
	{
		return E_OUTOFMEMORY;
	}

	dwErr = rkApp.SetStringValue(c_szAtlPerfLibrary, str);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkApp.SetStringValue(c_szAtlPerfOpen, szOpenFunc);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkApp.SetStringValue(c_szAtlPerfCollect, szCollectFunc);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkApp.SetStringValue(c_szAtlPerfClose, szCloseFunc);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkApp.SetStringValue(c_szAtlPerfLanguages, _T(""));
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	hr = _SaveMap();
	if (FAILED(hr))
		return hr;

	return S_OK;
}

inline HRESULT CPerfMon::RegisterStrings(
	LANGID language /* = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) */,
	HINSTANCE hResInstance /* = _AtlBaseModule.GetResourceInstance() */
	) throw()
{
	_ATLTRY
	{
		CString str;
		DWORD dwErr;
		HRESULT hr;
		CRegKey rkLang;
		CRegKey rkApp;

		LANGID wPrimaryLanguage = (LANGID) PRIMARYLANGID(language);

		if (language == MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL))
		{
			language = LANGIDFROMLCID(GetThreadLocale());
			wPrimaryLanguage = (LANGID) PRIMARYLANGID(language);
		}

		hr = CreateMap(language, hResInstance);
		if (FAILED(hr))
			return hr;

		str.Format(c_szAtlPerfPerfLibLangKey, wPrimaryLanguage);
		dwErr = rkLang.Open(HKEY_LOCAL_MACHINE, str);
		if (dwErr == ERROR_FILE_NOT_FOUND)
			return S_FALSE; // the language isn't installed on the system
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		// load list of language strings already registered
		str.Format(c_szAtlPerfPerformanceKey, GetAppName());
		dwErr = rkApp.Open(HKEY_LOCAL_MACHINE, str);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		DWORD dwLangsLen = 0;
		CString strLangs;

		dwErr = rkApp.QueryStringValue(c_szAtlPerfLanguages, NULL, &dwLangsLen);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		CStrBuf szLangs(strLangs, dwLangsLen+4, CStrBuf::SET_LENGTH); // reserve room for adding new language
		dwErr = rkApp.QueryStringValue(c_szAtlPerfLanguages, szLangs, &dwLangsLen);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);
		dwLangsLen--; // don't count '\0'

		// see if this language has already been registered and if so, return
		TCHAR szNewLang[5];
		_stprintf(szNewLang, _T("%3.3x "), wPrimaryLanguage);
		if (strLangs.Find(szNewLang) != -1)
			return S_OK;

		// load the strings we want to append and figure out how much extra space is needed for them
		// (including up to 5-digit index values and 2 null separators)
		CAtlArray<CString> astrCounters;
		CAtlArray<CString> astrHelp;
		ULONG nNewCounterSize = 0;
		ULONG nNewHelpSize = 0;

		for (UINT i=0; i<_GetNumMapEntries(); i++)
		{
			CPerfMapEntry& object = _GetMapEntry(i);

			astrCounters.Add(object.m_strName);
			nNewCounterSize += object.m_strName.GetLength() + 7;

			astrHelp.Add(object.m_strHelp);
			nNewHelpSize += object.m_strHelp.GetLength() + 7;
		}

		DWORD dwFirstCounter;
		DWORD dwFirstHelp;
		DWORD dwLastCounter;
		DWORD dwLastHelp;

		dwErr = rkApp.QueryDWORDValue(c_szAtlPerfFirstCounter, dwFirstCounter);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		dwErr = rkApp.QueryDWORDValue(c_szAtlPerfFirstHelp, dwFirstHelp);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		dwErr = rkApp.QueryDWORDValue(c_szAtlPerfLastCounter, dwLastCounter);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		dwErr = rkApp.QueryDWORDValue(c_szAtlPerfLastHelp, dwLastHelp);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		hr = _AppendRegStrings(rkLang, c_szAtlPerfCounter, astrCounters, nNewCounterSize, dwFirstCounter, dwLastCounter);
		if (FAILED(hr))
			return hr;

		hr = _AppendRegStrings(rkLang, c_szAtlPerfHelp, astrHelp, nNewHelpSize, dwFirstHelp, dwLastHelp);
		if (FAILED(hr))
			return hr;

		// add the language to the list of installed languages
		_tcscpy(szLangs+dwLangsLen, szNewLang);

		dwErr = rkApp.SetStringValue(c_szAtlPerfLanguages, szLangs);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

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

inline BOOL CPerfMon::EnumResLangProc(
	HINSTANCE hModule,
	LPCTSTR szType,
	LPCTSTR szName,
	LANGID wIDLanguage,
	LPARAM lParam
	) throw()
{
	hModule; // unused
	szType; // unused
	szName; // unused

	CAtlArray<LANGID>* pLangs = reinterpret_cast<CAtlArray<LANGID>*>(lParam);
	_ATLTRY
	{
		pLangs->Add(wIDLanguage);
	}
	_ATLCATCHALL()
	{
		return E_OUTOFMEMORY;
	}

	return TRUE;
}

inline HRESULT CPerfMon::RegisterAllStrings(
	HINSTANCE hResInstance /* = NULL */
	) throw()
{
	HRESULT hrReturn = S_FALSE;
	HRESULT hr;

	UINT nRes;
	hr = CreateMap(0, NULL, &nRes);
	if (FAILED(hr))
		return hr;

	if (nRes == 0)
		return RegisterStrings(0, hResInstance);

	if (hResInstance != NULL)
		return _RegisterAllStrings(nRes, hResInstance);

	for (int i = 0; hResInstance = _AtlBaseModule.GetHInstanceAt(i), hResInstance != NULL; i++)
	{
		hr = _RegisterAllStrings(nRes, hResInstance);
		if (FAILED(hr))
			return hr;
		if (hr == S_OK)
			hrReturn = S_OK;
	}

	return hrReturn;
}

inline HRESULT CPerfMon::_RegisterAllStrings(
	UINT nRes,
	HINSTANCE hResInstance
	) throw()
{
	HRESULT hrReturn = S_FALSE;
	HRESULT hr;

	CAtlArray<LANGID> langs;
	if (!EnumResourceLanguages(hResInstance, RT_STRING, MAKEINTRESOURCE((nRes>>4)+1), EnumResLangProc, reinterpret_cast<LPARAM>(&langs)))
		return AtlHresultFromLastError();

	for (UINT i=0; i<langs.GetCount(); i++)
	{
		hr = RegisterStrings(langs[i], hResInstance);
		if (FAILED(hr))
			return hr;
		if (hr == S_OK)
			hrReturn = S_OK;
	}

	return hrReturn;
}

inline HRESULT CPerfMon::_UnregisterStrings() throw()
{
	_ATLTRY
	{
		CString str;
		HRESULT hr;
		DWORD dwErr;

		// unregister the PerfMon counter and help strings
		CRegKey rkApp;

		str.Format(c_szAtlPerfPerformanceKey, GetAppName());
		dwErr = rkApp.Open(HKEY_LOCAL_MACHINE, str);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		DWORD dwFirstAppCounter;
		DWORD dwFirstAppHelp;
		DWORD dwLastAppCounter;
		DWORD dwLastAppHelp;

		dwErr = rkApp.QueryDWORDValue(c_szAtlPerfFirstCounter, dwFirstAppCounter);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		dwErr = rkApp.QueryDWORDValue(c_szAtlPerfFirstHelp, dwFirstAppHelp);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		dwErr = rkApp.QueryDWORDValue(c_szAtlPerfLastCounter, dwLastAppCounter);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		dwErr = rkApp.QueryDWORDValue(c_szAtlPerfLastHelp, dwLastAppHelp);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		// iterate through the installed languages and delete them all
		DWORD nChars = 0;
		dwErr = rkApp.QueryStringValue(c_szAtlPerfLanguages, NULL, &nChars);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		CString strLangs;
		dwErr = rkApp.QueryStringValue(c_szAtlPerfLanguages, CStrBuf(strLangs, nChars, CStrBuf::SET_LENGTH), &nChars);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);

		int nIndex = 0;
		CString strLang = strLangs.Tokenize(_T(" "), nIndex);
		while (!strLang.IsEmpty())
		{
			CRegKey rkLang;

			dwErr = rkLang.Open(HKEY_LOCAL_MACHINE, CString(c_szAtlPerfPerfLibKey) + _T("\\") + strLang);
			if (dwErr != ERROR_SUCCESS)
				return AtlHresultFromWin32(dwErr);

			hr = _RemoveRegStrings(rkLang, c_szAtlPerfCounter, dwFirstAppCounter, dwLastAppCounter);
			if (FAILED(hr))
				return hr;

			hr = _RemoveRegStrings(rkLang, c_szAtlPerfHelp, dwFirstAppHelp, dwLastAppHelp);
			if (FAILED(hr))
				return hr;

			strLang = strLangs.Tokenize(_T(" "), nIndex);
		}

		dwErr = rkApp.SetStringValue(c_szAtlPerfLanguages, _T(""));
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);
			
		return S_OK;
	}
	_ATLCATCHALL()
	{
		return E_OUTOFMEMORY;
	}
}

inline HRESULT CPerfMon::Unregister() throw()
{
	CString str;
	HRESULT hr;
	DWORD dwErr;

	CRegKey rkPerfLib;
	CRegKey rkApp;

	hr = _UnregisterStrings();
	if (FAILED(hr))
		return hr;

	dwErr = rkPerfLib.Open(HKEY_LOCAL_MACHINE, c_szAtlPerfPerfLibKey);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	_ATLTRY
	{
		str.Format(c_szAtlPerfPerformanceKey, GetAppName());
	}
	_ATLCATCHALL()
	{
		return E_OUTOFMEMORY;
	}
	dwErr = rkApp.Open(HKEY_LOCAL_MACHINE, str);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	DWORD dwLastCounter;
	DWORD dwLastHelp;
	DWORD dwFirstAppCounter;
	DWORD dwFirstAppHelp;
	DWORD dwLastAppCounter;
	DWORD dwLastAppHelp;

	dwErr = rkPerfLib.QueryDWORDValue(c_szAtlPerfLastCounter, dwLastCounter);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkPerfLib.QueryDWORDValue(c_szAtlPerfLastHelp, dwLastHelp);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkApp.QueryDWORDValue(c_szAtlPerfFirstCounter, dwFirstAppCounter);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkApp.QueryDWORDValue(c_szAtlPerfFirstHelp, dwFirstAppHelp);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkApp.QueryDWORDValue(c_szAtlPerfLastCounter, dwLastAppCounter);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkApp.QueryDWORDValue(c_szAtlPerfLastHelp, dwLastAppHelp);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	// rewind the Last Help/Last Counter values if possible
	if (dwLastCounter == dwLastAppCounter)
	{
		dwErr = rkPerfLib.SetDWORDValue(c_szAtlPerfLastCounter, dwFirstAppCounter-2);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);
	}

	if (dwLastHelp == dwLastAppHelp)
	{
		dwErr = rkPerfLib.SetDWORDValue(c_szAtlPerfLastHelp, dwFirstAppHelp-2);
		if (dwErr != ERROR_SUCCESS)
			return AtlHresultFromWin32(dwErr);
	}

	// delete the app key
	CRegKey rkServices;

	rkApp.Close();
	dwErr = rkServices.Open(HKEY_LOCAL_MACHINE, c_szAtlPerfServicesKey);
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	dwErr = rkServices.RecurseDeleteKey(GetAppName());
	if (dwErr != ERROR_SUCCESS)
		return AtlHresultFromWin32(dwErr);

	return S_OK;
}
#endif

inline HRESULT CPerfMon::Initialize() throw()
{
	CMutex tempLock;
	CString strAppName;
	HRESULT hr;

	_ATLTRY
	{
		strAppName = GetAppName();

		ATLASSERT(m_aMem.GetCount() == 0);

		// initialize a security descriptor to give everyone access to objects we create
		CSecurityDescriptor sd;
		sd.InitializeFromThreadToken();
		SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), sd, FALSE };

		// create a mutex to handle syncronizing access to the shared memory area
		CString strMutexName;
		strMutexName.Format(_T("ATLPERF_%s_LOCK"), strAppName);
		tempLock.Create(&sa, FALSE, strMutexName);
		if (tempLock.m_h == NULL)
			return AtlHresultFromLastError();

		// create a shared memory area to share data between the app being measured and the client doing the measuring
		{
			CMutexLock lock(tempLock);

			BOOL bExisted = FALSE;

			CAtlFileMappingBase* pMem;
			pMem = _AllocNewBlock(NULL, &bExisted);
			if (pMem == NULL)
				return E_OUTOFMEMORY;

			if (!bExisted)
			{
				// copy the map from the registry to the shared memory
				CRegKey rkApp;
				DWORD dwErr;
				CString strAppKey;

				strAppKey.Format(c_szAtlPerfPerformanceKey, GetAppName());

				dwErr = rkApp.Open(HKEY_LOCAL_MACHINE, strAppKey, KEY_READ);
				if (dwErr != ERROR_SUCCESS)
				{
					m_aMem.RemoveAll();
					return AtlHresultFromWin32(dwErr);
				}

				ULONG nBytes = m_nAllocSize;
				dwErr = rkApp.QueryBinaryValue(c_szAtlPerfMap, pMem->GetData(), &nBytes);
				if (dwErr != ERROR_SUCCESS)
				{
					m_aMem.RemoveAll();
					return AtlHresultFromWin32(dwErr);
				}
			}

			hr = _LoadMap();
			if (FAILED(hr))
			{
				m_aMem.RemoveAll();
				return hr;
			}

			m_nSchemaSize = *LPDWORD(pMem->GetData());
			m_nHeaderSize = m_nSchemaSize + sizeof(DWORD);
		}

		m_lock.Attach(tempLock.Detach());
	}
	_ATLCATCHALL()
	{
		m_aMem.RemoveAll();
		return E_OUTOFMEMORY;
	}

	return S_OK;
}

inline void CPerfMon::UnInitialize() throw()
{
	if (m_lock.m_h != NULL)
		m_lock.Close();
	m_aMem.RemoveAll();
	ClearMap();
}

inline HRESULT CPerfMon::_CreateInstance(
	DWORD dwObjectId,
	DWORD dwInstance,
	LPCWSTR szInstanceName,
	CPerfObject** ppInstance,
	bool bByName
	) throw()
{
	CPerfObject* pEmptyBlock = NULL;

	if (ppInstance == NULL)
		return E_POINTER;
	if (m_aMem.GetCount() == 0 || m_aMem[0]->GetData() == NULL || m_lock.m_h == NULL)
		return E_UNEXPECTED; // Initialize must succeed before calling CreateInstance

	*ppInstance = NULL;

	CPerfMapEntry* pObjectEntry = _FindObjectInfo(dwObjectId);
	if (pObjectEntry == NULL)
		return E_INVALIDARG;
	if (szInstanceName == NULL && bByName)
		return E_INVALIDARG;
	if (pObjectEntry->m_nInstanceLess == PERF_NO_INSTANCES &&
			(dwInstance != 0 || szInstanceName != NULL))
		return E_INVALIDARG;

	CPerfLock lock(this);
	if (FAILED(lock.GetStatus()))
		return lock.GetStatus();

	CAtlFileMappingBase* pCurrentBlock = m_aMem[0];
	CPerfObject* pInstance = _GetFirstObject(pCurrentBlock);
	ULONG nMaxInstance = 0;
	ULONG nUsedSpace = 0;

	// walk all of the existing objects trying to find one that matches the request
	while (pInstance->m_nAllocSize != 0)
	{
		nUsedSpace += pInstance->m_nAllocSize;

		if (pInstance->m_dwObjectId == dwObjectId)
		{
			nMaxInstance = max(nMaxInstance, pInstance->m_dwInstance);

			// check to see if we've found the one the caller wants
			if (!bByName && pInstance->m_dwInstance == dwInstance &&
				(pObjectEntry->m_nInstanceLess == PERF_NO_INSTANCES || dwInstance != 0))
			{
				*ppInstance = pInstance;
				pInstance->m_nRefCount++;
				return S_OK;
			}
			if (bByName)
			{
				LPWSTR szInstName = (LPWSTR(LPBYTE(pInstance)+pInstance->m_nInstanceNameOffset));
				if (wcsncmp(szInstName, szInstanceName, pObjectEntry->m_nMaxInstanceNameLen-1) == 0)
				{
					*ppInstance = pInstance;
					pInstance->m_nRefCount++;
					return S_OK;
				}
			}
		}

		if (pInstance->m_nAllocSize == pObjectEntry->m_nAllocSize && pInstance->m_dwObjectId == 0)
			pEmptyBlock = pInstance;

		pInstance = _GetNextObject(pInstance);

		if (pInstance->m_nAllocSize == 0 &&
			m_nHeaderSize + nUsedSpace + pObjectEntry->m_nAllocSize + sizeof(CPerfObject) > m_nAllocSize)
		{
			// we've reached the end of the block and have no room to allocate an object of this
			// type. cap the block with a sentinel
			pInstance->m_nAllocSize = (ULONG) -1;
		}

		// check for an end-of-shared-mem sentinel
		if (pInstance->m_nAllocSize == (ULONG) -1)
		{
			nUsedSpace = 0;
			CAtlFileMappingBase* pNextBlock = _GetNextBlock(pCurrentBlock);
			if (pNextBlock == NULL)
			{
				// we've reached the last block of shared mem.
				// the instance hasn't been found, so either use a
				// previously freed instance block (pEmptyBlock) or allocate a new
				// shared mem block to hold the new instance
				if (pEmptyBlock == NULL)
				{
					pNextBlock = _AllocNewBlock(pCurrentBlock);
					if (pNextBlock == NULL)
						return E_OUTOFMEMORY;
				}
				else
					break;
			}
			pCurrentBlock = pNextBlock;
			pInstance = _GetFirstObject(pCurrentBlock);
		}
	}

	// allocate a new object
	if (pEmptyBlock != NULL)
		pInstance = pEmptyBlock;
	else
		pInstance->m_nAllocSize = pObjectEntry->m_nAllocSize;

	pInstance->m_dwObjectId = pObjectEntry->m_dwPerfId;
	if (dwInstance == 0 && pObjectEntry->m_nInstanceLess != PERF_NO_INSTANCES)
		pInstance->m_dwInstance = nMaxInstance + 1;
	else
		pInstance->m_dwInstance = dwInstance;

	pInstance->m_nRefCount = 1;

	// copy the instance name, truncate if necessary
	if (pObjectEntry->m_nInstanceLess != PERF_NO_INSTANCES)
	{
		ULONG nNameLen = (ULONG)min(wcslen(szInstanceName), pObjectEntry->m_nMaxInstanceNameLen-1);
		ULONG nNameBytes = (nNameLen+1) * sizeof(WCHAR);
		pInstance->m_nInstanceNameOffset = pInstance->m_nAllocSize-nNameBytes;
		memcpy(LPBYTE(pInstance)+pInstance->m_nInstanceNameOffset, szInstanceName, nNameBytes);
		LPWSTR(LPBYTE(pInstance)+pInstance->m_nInstanceNameOffset)[nNameLen] = 0;
	}

	*ppInstance = pInstance;

	return S_OK;
}

inline HRESULT CPerfMon::CreateInstance(
	DWORD dwObjectId,
	DWORD dwInstance,
	LPCWSTR szInstanceName,
	CPerfObject** ppInstance
	) throw()
{
	return _CreateInstance(dwObjectId, dwInstance, szInstanceName, ppInstance, false);
}

inline HRESULT CPerfMon::CreateInstanceByName(
	DWORD dwObjectId,
	LPCWSTR szInstanceName,
	CPerfObject** ppInstance
	) throw()
{
	return _CreateInstance(dwObjectId, 0, szInstanceName, ppInstance, true);
}

inline HRESULT CPerfMon::ReleaseInstance(CPerfObject* pInstance) throw()
{
	ATLASSERT(pInstance != NULL);
	if (pInstance == NULL)
		return E_INVALIDARG;

	CPerfLock lock(this);
	if (FAILED(lock.GetStatus()))
		return lock.GetStatus();

	if (--pInstance->m_nRefCount == 0)
	{
		pInstance->m_dwInstance = 0;
		pInstance->m_dwObjectId = 0;
	}

	return S_OK;
}

inline HRESULT CPerfMon::LockPerf(DWORD dwTimeout /* == INFINITE */) throw()
{
	if (m_lock.m_h == NULL)
		return E_UNEXPECTED;

	DWORD dwRes = WaitForSingleObject(m_lock.m_h, dwTimeout);
	if (dwRes == WAIT_ABANDONED || dwRes == WAIT_OBJECT_0)
		return S_OK;
	if (dwRes == WAIT_TIMEOUT)
		return HRESULT_FROM_WIN32(ERROR_TIMEOUT);
	return AtlHresultFromLastError();
}

inline void CPerfMon::UnlockPerf() throw()
{
	m_lock.Release();
}

// map building routines
inline HRESULT CPerfMon::AddObjectDefinition(
	DWORD dwObjectId,
	LPCTSTR szObjectName,
	LPCTSTR szHelpString,
	DWORD dwDetailLevel,
	INT nDefaultCounter,
	BOOL bInstanceLess,
	UINT nStructSize,
	UINT nMaxInstanceNameLen) throw()
{
	// must have one and only one of these
	ATLASSERT(!bInstanceLess ^ !nMaxInstanceNameLen);

	CPerfMapEntry entry;

	entry.m_dwPerfId = dwObjectId;
	_ATLTRY
	{
		entry.m_strName = szObjectName;
		entry.m_strHelp = szHelpString;
	}
	_ATLCATCHALL()
	{
		return E_OUTOFMEMORY;
	}
	entry.m_dwDetailLevel = dwDetailLevel;
	entry.m_bIsObject = TRUE;

	// OBJECT INFO
	entry.m_nNumCounters = 0;
	entry.m_nDefaultCounter = nDefaultCounter;
	entry.m_nInstanceLess = bInstanceLess ? PERF_NO_INSTANCES : 0;
	entry.m_nStructSize = nStructSize;
	entry.m_nMaxInstanceNameLen = nMaxInstanceNameLen;
	entry.m_nAllocSize = nStructSize + nMaxInstanceNameLen*sizeof(WCHAR);

	// COUNTER INFO
	entry.m_dwCounterType = 0;
	entry.m_nDefaultScale = 0;
	entry.m_nMaxCounterSize = 0;
	entry.m_nDataOffset = 0;

	entry.m_nNameId = 0;
	entry.m_nHelpId = 0;

	_ATLTRY
	{
		m_map.Add(entry);
	}
	_ATLCATCHALL()
	{
		return E_OUTOFMEMORY;
	}

	if (_GetNumMapEntries() == 1)
		m_nNumObjectTypes = 1;
	else
		m_nNumObjectTypes++;

	return S_OK;
}

inline HRESULT CPerfMon::AddCounterDefinition(
	DWORD dwCounterId,
	LPCTSTR szCounterName,
	LPCTSTR szHelpString,
	DWORD dwDetailLevel,
	DWORD dwCounterType,
	ULONG nMaxCounterSize,
	UINT nOffset,
	INT nDefaultScale) throw()
{
	for (int i=_GetNumMapEntries()-1; i>=0; i--)
	{
		CPerfMapEntry& object = _GetMapEntry(i);
		if (object.m_bIsObject)
		{
			CPerfMapEntry counter;

			counter.m_dwPerfId = dwCounterId;
			_ATLTRY
			{
				counter.m_strName = szCounterName;
				counter.m_strHelp = szHelpString;
			}
			_ATLCATCHALL()
			{
				return E_OUTOFMEMORY;
			}
			counter.m_dwDetailLevel = dwDetailLevel;
			counter.m_bIsObject = FALSE;

			// OBJECT INFO
			counter.m_nNumCounters = 0;
			counter.m_nDefaultCounter = 0;
			counter.m_nInstanceLess = 0;
			counter.m_nStructSize = 0;
			counter.m_nMaxInstanceNameLen = 0;
			counter.m_nAllocSize = 0;

			// COUNTER INFO
			counter.m_dwCounterType = dwCounterType;
			counter.m_nDefaultScale = nDefaultScale;
			counter.m_nMaxCounterSize = nMaxCounterSize;
			counter.m_nDataOffset = nOffset;

			object.m_nNumCounters++;
			if (counter.m_nMaxCounterSize > 0)
			{
				ATLASSERT(counter.m_dwCounterType & PERF_TYPE_TEXT);
				object.m_nAllocSize += counter.m_nMaxCounterSize * sizeof(WCHAR);
			}

			counter.m_nNameId = 0;
			counter.m_nHelpId = 0;

			_ATLTRY
			{
				m_map.Add(counter);
			}
			_ATLCATCHALL()
			{
				return E_OUTOFMEMORY;
			}

			return S_OK;
		}
	}

	// found no object in map! must add object BEFORE adding counter!
	ATLASSERT(FALSE);
	return E_UNEXPECTED;
}

inline void CPerfMon::ClearMap() throw()
{
	m_map.RemoveAll();
}

#ifndef _ATL_PERF_NOXML

ATL_NOINLINE inline HRESULT CPerfMon::PersistToXML(IStream *pStream, BOOL bFirst/*=TRUE*/, BOOL bLast/*=TRUE*/) throw(...)
{
	ATLASSERT(pStream != NULL);
	if (pStream == NULL)
		return E_INVALIDARG;

	CPerfLock lock(this);
	if (FAILED(lock.GetStatus()))
		return ERROR_SUCCESS;

	CStringA strXML;
	HRESULT hr = S_OK;
	ULONG nLen = 0;
	
	if (bFirst)
	{
		strXML = "<?xml version=\"1.0\" ?>\r\n<perfPersist>\r\n";
		hr = pStream->Write(strXML, strXML.GetLength(), &nLen);
		if (hr != S_OK)
			return hr;
	}

	strXML.Format("\t<perfmon name=\"%s\">\r\n", CT2CA(GetAppName()));
	hr = pStream->Write(strXML, strXML.GetLength(), &nLen);

	for (UINT i=0; i<_GetNumMapEntries(); i+= _GetMapEntry(i).m_nNumCounters+1)
	{
		CPerfMapEntry *pObjectEntry = &_GetMapEntry(i);
		CPerfMapEntry *pCounterEntries = pObjectEntry+1;

		CAtlFileMappingBase *pCurrentBlock = _GetNextBlock(NULL);
		CPerfObject *pInstance = _GetFirstObject(pCurrentBlock);

		strXML.Format("\t\t<perfObject perfid=\"%d\">\r\n", 
			pObjectEntry->m_dwPerfId, pObjectEntry->m_nNameId, pObjectEntry->m_nHelpId);

		hr = pStream->Write(strXML, strXML.GetLength(), &nLen);
		if (hr != S_OK)
			return E_FAIL;

		while (pInstance && pInstance->m_nAllocSize)
		{
			if (pInstance->m_dwObjectId == pObjectEntry->m_dwPerfId)
			{
				if (pObjectEntry->m_nInstanceLess != PERF_NO_INSTANCES)
				{
					// handle the instance name
					LPCWSTR wszInstNameSrc = LPCWSTR(LPBYTE(pInstance)+pInstance->m_nInstanceNameOffset);
					int nInstLen = (int) wcslen(wszInstNameSrc);

					// convert to UTF8
					nLen = AtlUnicodeToUTF8(wszInstNameSrc, nInstLen, NULL, 0);
					CHeapPtr<CHAR> szUTF8;
					if (!szUTF8.Allocate(nLen+1))
						return E_OUTOFMEMORY;
					nLen = AtlUnicodeToUTF8(wszInstNameSrc, nInstLen, szUTF8, nLen);
					szUTF8[nLen] = '\0';

					strXML.Format("\t\t\t<instance name=\"%s\">\r\n", szUTF8);
					hr = pStream->Write(strXML, strXML.GetLength(), &nLen);
					if (hr != S_OK)
						return hr;
				}

				for (ULONG j=0; j<pObjectEntry->m_nNumCounters; j++)
				{
					CPerfMapEntry *pCounterEntry = pCounterEntries+j;
					switch (pCounterEntry->m_dwCounterType & ATLPERF_SIZE_MASK)
					{
						case PERF_SIZE_DWORD:
						{
							strXML.Format("\t\t\t\t<counter type=\"perf_size_dword\" value=\"%d\" offset=\"%d\"/>\r\n",
								*LPDWORD(LPBYTE(pInstance)+pCounterEntry->m_nDataOffset), 
								pCounterEntry->m_nDataOffset);
							break;
						}
						case PERF_SIZE_LARGE:
						{
							strXML.Format("\t\t\t\t<counter type=\"perf_size_large\" value=\"%d\" offset=\"%d\"/>\r\n",
								*PULONGLONG(LPBYTE(pInstance)+pCounterEntry->m_nDataOffset),
								pCounterEntry->m_nDataOffset);
							break;
						}
						case PERF_SIZE_VARIABLE_LEN:
						{
							CHeapPtr<CHAR> szUTF8;
							LPBYTE pSrc = LPBYTE(pInstance)+pCounterEntry->m_nDataOffset;
							if ((pCounterEntry->m_dwCounterType & ATLPERF_TEXT_MASK) == PERF_TEXT_UNICODE)
							{
								ULONG nTextLen = (ULONG)wcslen(LPCWSTR(pSrc));
								// convert to UTF8
								nLen = AtlUnicodeToUTF8(LPCWSTR(pSrc), nTextLen, NULL, 0);
								if (!szUTF8.Allocate(nLen+1))
									return E_OUTOFMEMORY;

								nLen = AtlUnicodeToUTF8(LPCWSTR(pSrc), nTextLen, szUTF8, nLen);	
								szUTF8[nLen] = '\0';
								strXML.Format("\t\t\t\t<counter type=\"perf_size_variable_len_unicode\" value=\"%s\" offset=\"%d\"/>\r\n",
										szUTF8,
										pCounterEntry->m_nDataOffset);
							}
							else
							{
								ULONG nTextLen = (ULONG)strlen(LPCSTR(pSrc));
								if (!szUTF8.Allocate(nTextLen+1))
									return E_OUTOFMEMORY;
								strcpy(szUTF8, LPCSTR(pSrc));
								strXML.Format("\t\t\t\t<counter type=\"perf_size_variable_len_ansi\" value=\"%s\" offset=\"%d\"/>\r\n",
										szUTF8,
										pCounterEntry->m_nDataOffset);
							}
							break;
						}
						default:
							// error:
							return E_FAIL;
					}
					hr = pStream->Write(strXML, strXML.GetLength(), &nLen);
					if (hr != S_OK)
						return hr;
				}
			}

			if (pObjectEntry->m_nInstanceLess != PERF_NO_INSTANCES)
			{
				hr = pStream->Write("\t\t\t</instance>\r\n", sizeof("\t\t\t</instance>\r\n")-1, &nLen);
				if (hr != S_OK)
					return hr;
			}

			pInstance = _GetNextObject(pInstance);
			if (pInstance->m_nAllocSize == (ULONG)-1)
			{
				pCurrentBlock = _GetNextBlock(pCurrentBlock);
				if (pCurrentBlock == NULL)
					pInstance = NULL;
				else
					pInstance = _GetFirstObject(pCurrentBlock);
			}
		}

		hr = pStream->Write("\t\t</perfObject>\r\n", sizeof("\t\t</perfObject>\r\n")-1, &nLen);
		if (hr != S_OK)
			return hr;
	}

	hr = pStream->Write("\t</perfmon>\r\n", sizeof("\t</perfmon>\r\n")-1, &nLen);
	if (hr != S_OK)
		return hr;

	if (hr == S_OK && bLast)
		hr = pStream->Write("</perfPersist>", sizeof("</perfPersist>")-1, &nLen);

	return hr;
}

// This function is very lenient with inappropriate XML
ATL_NOINLINE inline HRESULT CPerfMon::LoadFromXML(IStream *pStream) throw(...)
{	
	ATLASSERT(pStream != NULL);
	if (pStream == NULL)
		return E_INVALIDARG;

	// Get a lock
	CPerfLock lock(this);
	if (FAILED(lock.GetStatus()))
		return ERROR_SUCCESS;

	CComPtr<IXMLDOMDocument> spdoc;

	// load the xml
	HRESULT hr = CoCreateInstance(__uuidof(DOMDocument), NULL, CLSCTX_INPROC, __uuidof(IXMLDOMDocument), (void **) &spdoc);
	if (FAILED(hr))
	{
		return hr;
	}

	spdoc->put_async(VARIANT_FALSE);

	CComPtr<IPersistStreamInit> spSI;
	hr = spdoc->QueryInterface(&spSI);
	if (hr != S_OK)
		return hr;
	hr = spSI->Load(pStream);
	if (hr != S_OK)
		return hr;

	// validate that it is a perfPersist stream
	CComPtr<IXMLDOMElement> spRoot;

	hr = spdoc->get_documentElement(&spRoot);
	if (hr != S_OK)
		return hr;

	CComBSTR bstrName;
	hr = spRoot->get_baseName(&bstrName);
	if (wcscmp(bstrName, L"perfPersist"))
		return S_FALSE;

	USES_CONVERSION;	
	// find the appropriate perfmon node

	CComPtr<IXMLDOMNode> spChild;
	hr = spRoot->get_firstChild(&spChild);
	while (hr == S_OK)
	{
		bstrName.Empty();
		hr = spChild->get_baseName(&bstrName);
		if (hr == S_OK)
		{
			if (!wcscmp(bstrName, L"perfmon"))
			{
				bstrName.Empty();
				hr = _GetAttribute(spChild, L"name", &bstrName);
				if (hr == S_OK)
				{
					if (!_tcscmp(W2CT(bstrName), GetAppName()))
						break;
				}
			}
		}

		CComPtr<IXMLDOMNode> spNext;
		hr = spChild->get_nextSibling(&spNext);
		spChild.Attach(spNext.Detach());
	}

	// there is no perfmon node in the XML for the current CPerfMon class
	if (hr != S_OK)
		return S_FALSE;

	CComPtr<IXMLDOMNode> spPerfRoot;
	spPerfRoot.Attach(spChild.Detach());

	// iterate over the objects in the perfmon subtree
	// this is the loop that does the real work
	hr = spPerfRoot->get_firstChild(&spChild);
	DWORD dwInstance = 1;
	while (hr == S_OK)
	{
		// see if it's a perfObject
		bstrName.Empty();
		hr = spChild->get_baseName(&bstrName);
		if (hr != S_OK || wcscmp(bstrName, L"perfObject"))
			return S_FALSE;

		// get the perfid
		bstrName.Empty();
		hr = _GetAttribute(spChild, L"perfid", &bstrName);
		DWORD dwPerfId = _wtoi(bstrName);

		// iterate over children
		CComPtr<IXMLDOMNode> spInstChild;
		hr = spChild->get_firstChild(&spInstChild);
		while (hr == S_OK)
		{
			// see if it's a instance
			bstrName.Empty();
			hr = spInstChild->get_baseName(&bstrName);
			if (hr != S_OK || wcscmp(bstrName, L"instance"))
				return S_FALSE;

			// get the instance name
			bstrName.Empty();
			hr = _GetAttribute(spInstChild, L"name", &bstrName);
			if (hr != S_OK)
				return S_FALSE;

			// create the instance
			// REVIEW : take a loook at the dwInstance stuff--is it acceptable?
			CPerfObject *pInstance = NULL;
			hr = CreateInstance(dwPerfId, dwInstance++, bstrName, &pInstance);
			if (hr != S_OK)
				return S_FALSE;

			// iterate over the counters and set the data
			CComPtr<IXMLDOMNode> spCntrChild;
			hr = spInstChild->get_firstChild(&spCntrChild);
			while (hr == S_OK)
			{
				// get the base name
				bstrName.Empty();
				hr = spCntrChild->get_baseName(&bstrName);
				if (hr != S_OK || wcscmp(bstrName, L"counter"))
					return S_FALSE;

				// get the type
				bstrName.Empty();
				hr = _GetAttribute(spCntrChild, L"type", &bstrName);
				if (hr != S_OK)
					return S_FALSE;

				DWORD dwType;
				if (!wcscmp(bstrName, L"perf_size_dword"))
					dwType = PERF_SIZE_DWORD;
				else if (!wcscmp(bstrName, L"perf_size_large"))
					dwType = PERF_SIZE_LARGE;
				else if (!wcscmp(bstrName, L"perf_size_variable_len_ansi"))
					dwType = PERF_SIZE_VARIABLE_LEN;
				else if (!wcscmp(bstrName, L"perf_size_variable_len_unicode"))
					dwType = PERF_SIZE_VARIABLE_LEN | PERF_TEXT_UNICODE;
				else
					return S_FALSE;

				// get the value
				bstrName.Empty();
				hr = _GetAttribute(spCntrChild, L"value", &bstrName);
				if (hr != S_OK)
					return S_FALSE;

				CComBSTR bstrOffset;
				hr = _GetAttribute(spCntrChild, L"offset", &bstrOffset);
				if (hr != S_OK)
					return S_FALSE;

				WCHAR *pStop = NULL;
				DWORD dwOffset = wcstoul(bstrOffset, &pStop, 10);

				if (dwType == PERF_SIZE_DWORD) // add it as a DWORD
				{
					DWORD dwVal = wcstoul(bstrName, &pStop, 10);
					*LPDWORD(LPBYTE(pInstance)+dwOffset) = dwVal;
				}
				else if (dwType == PERF_SIZE_LARGE) // add it is a ULONGLONG
				{
					ULONGLONG qwVal = _wcstoui64(bstrName, &pStop, 10);
					*PULONGLONG(LPBYTE(pInstance)+dwOffset) = qwVal;
				}
				else if (dwType == PERF_SIZE_VARIABLE_LEN) // add it as an ansi string
				{
					AtlW2AHelper(LPSTR(LPBYTE(pInstance)+dwOffset), bstrName, bstrName.Length(), ATL::_AtlGetConversionACP());
				}
				else // add it as a unicode string
				{
					memcpy(LPBYTE(pInstance)+dwOffset, bstrName, bstrName.Length()*sizeof(WCHAR));
				}

				CComPtr<IXMLDOMNode> spCntrNext;
				hr = spCntrChild->get_nextSibling(&spCntrNext);
				spCntrChild.Attach(spCntrNext.Detach());
			}

			CComPtr<IXMLDOMNode> spInstNext;
			hr = spInstChild->get_nextSibling(&spInstNext);
			spInstChild.Attach(spInstNext.Detach());
		}

		CComPtr<IXMLDOMNode> spNext;
		hr = spChild->get_nextSibling(&spNext);
		spChild.Attach(spNext.Detach());
	}

	return S_OK;
}

// a little utility function to retrieve a named attribute from a node
ATL_NOINLINE inline HRESULT CPerfMon::_GetAttribute(IXMLDOMNode *pNode, LPCWSTR szAttrName, BSTR *pbstrVal) throw()
{
	ATLASSERT(pNode != NULL);
	ATLASSERT(szAttrName != NULL);
	ATLASSERT(pbstrVal != NULL);

	*pbstrVal = NULL;
	CComPtr<IXMLDOMNamedNodeMap> spAttrs;

	HRESULT hr = pNode->get_attributes(&spAttrs);
	if (hr != S_OK)
		return hr;
	
	CComPtr<IXMLDOMNode> spAttr;
	
	hr = spAttrs->getNamedItem((BSTR) szAttrName, &spAttr);
	if (hr != S_OK)
		return hr;
	
	CComVariant varVal;
	hr = spAttr->get_nodeValue(&varVal);
	if (hr != S_OK)
		return hr;
	
	hr = varVal.ChangeType(VT_BSTR);
	if (hr != S_OK)
		return hr;

	*pbstrVal = varVal.bstrVal;
	varVal.vt = VT_EMPTY;

	return S_OK;
}

#endif // _ATL_PERF_NOXML

} // namespace ATL

#pragma warning(pop)

#endif // __ATLPERF_INL__