显示对象、实例和计数器名称

如果要在用户界面中显示对象及其计数器的列表,则必须检索性能数据。 性能数据包含数量可变的性能对象及其实例和计数器。 有关性能数据格式的信息,请参阅 性能数据格式

性能数据不包含对象和计数器名称。 相反,数据包含用于检索对象和计数器的名称的索引值,这些值存储在性能计数器基础结构已知的资源中。 但是,实例名称存储在性能数据中。 有关从注册表中读取对象和计数器名称并创建索引表以便稍后访问名称的示例,请参阅 检索计数器名称和帮助文本

以下示例演示如何从性能数据中检索对象、实例和计数器块。 检索对象名称、实例名称和计数器名称后,该示例按名称对对象进行排序,并为每个对象按名称对其实例和计数器进行排序。 然后,该示例打印已排序的列表。

#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include <stdio.h>
#include <strsafe.h>

#pragma comment(lib, "advapi32.lib")

#define INIT_OBJECT_BUFFER_SIZE 48928   // Initial buffer size to use when querying specific objects.
#define INIT_GLOBAL_BUFFER_SIZE 122880  // Initial buffer size to use when using "Global" to query all objects.
#define BUFFER_INCREMENT 16384          // Increment buffer size in 16K chunks.
#define MAX_INSTANCE_NAME_LEN      255  // Max length of an instance name.
#define MAX_FULL_INSTANCE_NAME_LEN 511  // Form is parentinstancename/instancename#nnn.

//Identifies a performance object and its counters and instances. 
typedef struct _perf_object
{
  DWORD dwObjectIndex;          // Index to the name of the object.
  LPDWORD pdwCounterIndexes;    // Array of counter index values.
  DWORD dwNumberOfCounters;
  LPWSTR pInstanceNames;        // Array of instance name strings.
  DWORD dwNumberofInstances;
} PERF_OBJECT, *PPERF_OBJECT;

LPBYTE GetPerformanceData(LPWSTR pwszSource, DWORD dwInitialBufferSize);
BOOL GetCounterTextStrings(LPWSTR & pCounterTextHead, LPWSTR & pHelpTextHead, LPDWORD & pTextOffsets, DWORD* pdwNumberOfOffsets);
LPWSTR GetText(LPWSTR pwszSource);
BOOL BuildTextTable(LPWSTR pCounterHead, LPWSTR pHelpHead, LPDWORD* pOffsetsHead, LPDWORD pNumberOfOffsets);
DWORD GetNumberOfTextEntries(LPWSTR pwszSource);
PPERF_OBJECT LoadObjects(LPBYTE pObject, DWORD* dwNumberOfObjects, LPBYTE pEndObject);
BOOL LoadCounters(LPDWORD* ppdwCounterIndexes, LPBYTE pCounter, DWORD* pdwNumberOfCounters);
BOOL LoadInstances(LPWSTR* pInstanceNames, LPBYTE pInstance, DWORD dwNumberofInstances, DWORD CodePage);
BOOL GetFullInstanceName(PERF_INSTANCE_DEFINITION* pInstance, DWORD CodePage, WCHAR* pName);
BOOL ConvertNameToUnicode(UINT CodePage, LPCSTR pNameToConvert, DWORD dwNameToConvertLen, LPWSTR pConvertedName);
PERF_INSTANCE_DEFINITION* GetParentInstance(PERF_OBJECT_TYPE* pObject, DWORD dwInstancePosition);
PERF_OBJECT_TYPE* GetObject(DWORD dwObjectToFind);
int CompareObjects(const void *pObject1, const void *pObject2);
int CompareCounters(const void *pCounter1, const void *pCounter2);
int CompareInstances(const void *pInstance1, const void *pInstance2);
void PrintObjectNames(DWORD dwNumberOfObjects, BOOL fIncludeCounters, BOOL fIncludeInstances);
void FreePerfObjects(PPERF_OBJECT pObjects, DWORD dwNumberOfObjects);

// Globals
LPBYTE g_pPerfDataHead = NULL;    // Head of the performance data.
LPWSTR g_pCounterTextHead = NULL; // Head of the MULTI_SZ buffer that contains the Counter text.
LPWSTR g_pHelpTextHead = NULL;    // Head of the MULTI_SZ buffer that contains the Help text.
LPDWORD g_pTextOffsets = NULL;    // Array of DWORDS that contain the offsets to the text in
                                  // pCounterTextHead and pHelpTextHead. The text index 
                                  // values mirror the array index.
PPERF_OBJECT g_pObjects = NULL;   // Array of PERF_OBJECTs.



void wmain(void)
{
    DWORD dwNumberOfOffsets = 0;    // Number of elements in the pTextOffsets array.
    DWORD dwNumberOfObjects = 0;    // Number of elements in the pObjects array.

    g_pPerfDataHead = (LPBYTE)GetPerformanceData(L"Global", INIT_GLOBAL_BUFFER_SIZE);
    if (NULL == g_pPerfDataHead)
    {
        wprintf(L"GetPerformanceData failed.\n");
        goto cleanup;
    }

    if (!GetCounterTextStrings(g_pCounterTextHead, g_pHelpTextHead, g_pTextOffsets, &dwNumberOfOffsets))
    {
        wprintf(L"GetCounterTextStrings failed.\n");
        goto cleanup;
    }

    dwNumberOfObjects = ((PERF_DATA_BLOCK*)g_pPerfDataHead)->NumObjectTypes;

    g_pObjects = LoadObjects(g_pPerfDataHead + ((PERF_DATA_BLOCK*)g_pPerfDataHead)->HeaderLength,
                             &dwNumberOfObjects,
                             g_pPerfDataHead + ((PERF_DATA_BLOCK*)g_pPerfDataHead)->TotalByteLength);
    
    if (NULL == g_pObjects)
    {
        wprintf(L"LoadObjects failed.\n");
        goto cleanup;
    }

    PrintObjectNames(dwNumberOfObjects, TRUE, TRUE);

cleanup:

    if (g_pPerfDataHead)
        free(g_pPerfDataHead);

    if (g_pCounterTextHead)
        free(g_pCounterTextHead);

    if (g_pHelpTextHead)
        free(g_pHelpTextHead);

    if (g_pTextOffsets)
        free(g_pTextOffsets);

    if (g_pObjects)
        FreePerfObjects(g_pObjects, dwNumberOfObjects);
}

//Retrieve a buffer that contains the specified performance data.
//The pwszSource parameter determines the data that GetRegistryBuffer returns.
//
//Typically, when calling RegQueryValueEx, you can specify zero for the size of the buffer
//and the RegQueryValueEx will set your size variable to the required buffer size. However, 
//if the source is "Global" or one or more object index values, you will need to increment
//the buffer size in a loop until RegQueryValueEx does not return ERROR_MORE_DATA. 
LPBYTE GetPerformanceData(LPWSTR pwszSource, DWORD dwInitialBufferSize)
{
    LPBYTE pBuffer = NULL;
    DWORD dwBufferSize = 0;        //Size of buffer, used to increment buffer size
    DWORD dwSize = 0;              //Size of buffer, used when calling RegQueryValueEx
    LPBYTE pTemp = NULL;           //Temp variable for realloc() in case it fails
    LONG status = ERROR_SUCCESS; 

    dwBufferSize = dwSize = dwInitialBufferSize;

    pBuffer = (LPBYTE)malloc(dwBufferSize);
    if (pBuffer)
    {
        while (ERROR_MORE_DATA == (status = RegQueryValueEx(HKEY_PERFORMANCE_DATA, pwszSource, NULL, NULL, pBuffer, &dwSize)))
        {
            //Contents of dwSize is unpredictable if RegQueryValueEx fails, which is why
            //you need to increment dwBufferSize and use it to set dwSize.
            dwBufferSize += BUFFER_INCREMENT;

            pTemp = (LPBYTE)realloc(pBuffer, dwBufferSize);
            if (pTemp)
            {
                pBuffer = pTemp;
                dwSize = dwBufferSize;
            }
            else
            {
                wprintf(L"Reallocation error.\n");
                free(pBuffer);
                pBuffer = NULL;
                goto cleanup;
            }
        }

        if (ERROR_SUCCESS != status)
        {
            wprintf(L"RegQueryValueEx failed with 0x%x.\n", status);
            free(pBuffer);
            pBuffer = NULL;
        }
    }
    else
    {
        wprintf(L"malloc failed to allocate initial memory request.\n");
    }

cleanup:

    RegCloseKey(HKEY_PERFORMANCE_DATA);

    return pBuffer;
}


BOOL GetCounterTextStrings(LPWSTR & pCounterTextHead, LPWSTR & pHelpTextHead, LPDWORD & pTextOffsets, DWORD* pdwNumberOfOffsets)
{
    BOOL status = TRUE;

    pCounterTextHead = GetText(L"Counter");
    if (NULL == pCounterTextHead)
    {
        wprintf(L"GetText(L\"Counter\") failed.\n");
        status = FALSE;
        goto cleanup;
    }

    pHelpTextHead = GetText(L"Help");
    if (NULL == pHelpTextHead)
    {
        wprintf(L"GetText(L\"Help\") failed.\n");
        status = FALSE;
        goto cleanup;
    }

    if (!BuildTextTable(pCounterTextHead, pHelpTextHead, &pTextOffsets, pdwNumberOfOffsets))
    {
        wprintf(L"BuildTextTable failed.\n");
        status = FALSE;
    }

cleanup:

    return status;
}


// Get the text based on the source value. This function uses the
// HKEY_PERFORMANCE_NLSTEXT key to get the strings.
LPWSTR GetText(LPWSTR pwszSource)
{
    LPWSTR pBuffer = NULL;
    DWORD dwBufferSize = 0;
    LONG status = ERROR_SUCCESS;

    // Query the size of the text data so you can allocate the buffer.
    status = RegQueryValueEx(HKEY_PERFORMANCE_NLSTEXT, pwszSource, NULL, NULL, NULL, &dwBufferSize);
    if (ERROR_SUCCESS != status)
    {
        wprintf(L"RegQueryValueEx failed getting required buffer size. Error is 0x%x.\n", status);
        goto cleanup;
    }

    // Allocate the text buffer and query the text.
    pBuffer = (LPWSTR)malloc(dwBufferSize);
    if (pBuffer)
    {
        status = RegQueryValueEx(HKEY_PERFORMANCE_NLSTEXT, pwszSource, NULL, NULL, (LPBYTE)pBuffer, &dwBufferSize);
        if (ERROR_SUCCESS != status)
        {
            wprintf(L"RegQueryValueEx failed with 0x%x.\n", status);
            free(pBuffer);
            pBuffer = NULL;
            goto cleanup;
        }
    }
    else
    {
        wprintf(L"malloc failed to allocate memory.\n");
    }

cleanup:

    return pBuffer;
}


// Build a table of offsets into the counter and help text buffers. Use the index
// values from the performance data queries to access the offsets.
BOOL BuildTextTable(LPWSTR pCounterHead, LPWSTR pHelpHead, LPDWORD* pOffsetsHead, LPDWORD pNumberOfOffsets)
{
    BOOL fSuccess = FALSE;
    LPWSTR pwszCounterText = NULL;  // Used to cycle through the Counter text
    LPWSTR pwszHelpText = NULL;     // Used to cycle through the Help text
    LPDWORD pOffsets = NULL;
    DWORD dwCounterIndex = 0;       // Index value of the Counter text
    DWORD dwHelpIndex = 0;          // Index value of the Help text
    DWORD dwSize = 0;               // Size of the block of memory that holds the offset array

    pwszCounterText = pCounterHead;
    pwszHelpText = pHelpHead;

    *pNumberOfOffsets = GetNumberOfTextEntries(L"Last Help");
    if (0 == *pNumberOfOffsets)
    {
        wprintf(L"GetNumberOfTextEntries failed; returned 0 for number of entries.\n");
        goto cleanup;
    }

    dwSize = sizeof(DWORD) * (*pNumberOfOffsets + 1);  // Add one to make the array one-based

    pOffsets = (LPDWORD)malloc(dwSize);
    if (pOffsets)
    {
        ZeroMemory(pOffsets, dwSize);
        *pOffsetsHead = pOffsets;

        // Bypass first record (pair) of the counter data - contains upper bounds of system counter index.
        pwszCounterText += (wcslen(pwszCounterText)+1);
        pwszCounterText += (wcslen(pwszCounterText)+1);

        for (; *pwszCounterText; pwszCounterText += (wcslen(pwszCounterText)+1))
        {
            dwCounterIndex = _wtoi(pwszCounterText);
            dwHelpIndex = _wtoi(pwszHelpText);

            // Use the counter's index value as an indexer into the pOffsets array.
            // Store the offset to the counter text in the array element.
            pwszCounterText += (wcslen(pwszCounterText)+1);  //Skip past index value
            pOffsets[dwCounterIndex] = (DWORD)(pwszCounterText - pCounterHead);

            // Some help indexes for system counters do not have a matching counter, so loop
            // until you find the matching help index or the index is greater than the corresponding
            // counter index. For example, if the indexes were as follows, you would loop
            // until the help index was 11.
            //
            // Counter index       Help Index
            //   2                    3
            //   4                    5
            //   6                    7
            //                        9   (skip because there is no matching Counter index)
            //   10                   11
            while (*pwszHelpText && dwHelpIndex < dwCounterIndex)
            {
                pwszHelpText += (wcslen(pwszHelpText)+1);  // Skip past index value
                pwszHelpText += (wcslen(pwszHelpText)+1);  // Skip past help text to the next index value
                dwHelpIndex = _wtoi(pwszHelpText);
            }

            // Use the Help index value as an indexer into the pOffsets array.
            // Store the offset to the help text in the array element.
            if (dwHelpIndex == (dwCounterIndex + 1))
            {
                pwszHelpText += (wcslen(pwszHelpText)+1);  //Skip past index value
                pOffsets[dwHelpIndex] = (DWORD)(pwszHelpText - pHelpHead);
                pwszHelpText += (wcslen(pwszHelpText)+1);  //Skip past help text to next index value
            }
        }

        fSuccess = TRUE;
    }

cleanup:

    return fSuccess;
}


// Retrieve the last help index used from the registry. Use this number
// to allocate an array of DWORDs. Note that index values are not contiguous.
DWORD GetNumberOfTextEntries(LPWSTR pwszSource)
{
    DWORD dwEntries = 0;
    LONG status = ERROR_SUCCESS;
    HKEY hkey = NULL;
    DWORD dwSize = sizeof(DWORD);
    LPWSTR pwszMessage = NULL;

    status = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
        L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib",
        0,
        KEY_READ,
        &hkey);
    
    if (ERROR_SUCCESS != status)
    {
        wprintf(L"RegOpenKeyEx failed with 0x%x.\n", status);
        goto cleanup;
    }

    status = RegQueryValueEx(hkey, pwszSource, NULL, 0, (LPBYTE)&dwEntries, &dwSize);

    if (ERROR_SUCCESS != status)
    {
        wprintf(L"RegQueryValueEx failed with 0x%x.\n", status);
    }

cleanup:

    if (hkey)
        RegCloseKey(hkey);

    return dwEntries;
}


// Allocate a block of memory that will contain one or more PERF_OBJECT structures.
// For each object, also load its counters and instances.

PPERF_OBJECT LoadObjects(LPBYTE pPerfDataObject, DWORD* pdwNumberOfObjects, LPBYTE pEndObject)
{
    BOOL fSuccess = FALSE;
    DWORD dwSize = sizeof(PERF_OBJECT) * *pdwNumberOfObjects;
    PPERF_OBJECT pObjects = NULL;
    DWORD i = 0;

    pObjects = (PERF_OBJECT*)malloc(dwSize);
    if (NULL == pObjects)
    {
        wprintf(L"malloc failed to allocate memory for pObjects.\n");
        goto cleanup;
    }

    ZeroMemory(pObjects, dwSize);
    for (; i < *pdwNumberOfObjects; i++)
    {
        pObjects[i].dwObjectIndex = ((PERF_OBJECT_TYPE*)pPerfDataObject)->ObjectNameTitleIndex;
        // Load Counter indexes.

        pObjects[i].dwNumberOfCounters = ((PERF_OBJECT_TYPE*)pPerfDataObject)->NumCounters;

        fSuccess = LoadCounters(&(pObjects[i].pdwCounterIndexes),
            pPerfDataObject + ((PERF_OBJECT_TYPE*)pPerfDataObject)->HeaderLength,  // Points to the first counter
            &(pObjects[i].dwNumberOfCounters));

        if (FALSE == fSuccess)
        {
            wprintf(L"LoadCounters failed.\n");
            goto cleanup;
        }

        if ( ((PERF_OBJECT_TYPE*)pPerfDataObject)->NumInstances != 0 && 
             ((PERF_OBJECT_TYPE*)pPerfDataObject)->NumInstances != PERF_NO_INSTANCES )
        {

            // Load instance indexes.

            pObjects[i].dwNumberofInstances = ((PERF_OBJECT_TYPE*)pPerfDataObject)->NumInstances;

            fSuccess = LoadInstances(&(pObjects[i].pInstanceNames),
                pPerfDataObject + ((PERF_OBJECT_TYPE*)pPerfDataObject)->DefinitionLength, // Points to the first instance
                ((PERF_OBJECT_TYPE*)pPerfDataObject)->NumInstances,
                ((PERF_OBJECT_TYPE*)pPerfDataObject)->CodePage);

            if (FALSE == fSuccess)
            {
                wprintf(L"LoadInstances failed.\n");
                goto cleanup;
            }
        }

        pPerfDataObject += ((PERF_OBJECT_TYPE*)pPerfDataObject)->TotalByteLength;
        if (pPerfDataObject >= pEndObject)
        {
            // The end of the counter objects array was reached.
            break;
        }
    }

    *pdwNumberOfObjects = i;

    //After all the data is loaded, sort the list of objects, so you can print
    //them in alphabetical order.

    qsort(pObjects, *pdwNumberOfObjects, sizeof(PERF_OBJECT), CompareObjects);

cleanup:

    if (FALSE == fSuccess)
    {
        FreePerfObjects(pObjects, i);
        pObjects = NULL;
    }

    return pObjects;
}


// Allocate of block of memory and load the index values for the counters that
// the object defines.
BOOL LoadCounters(LPDWORD* ppdwIndexes, LPBYTE pCounter, DWORD* pdwNumberOfCounters)
{
    BOOL fSuccess = FALSE;
    DWORD dwSize = sizeof(DWORD) * *pdwNumberOfCounters;
    LPDWORD pIndexes = NULL;
    DWORD dwSkippedBaseRecords = 0;

    pIndexes = (LPDWORD)malloc(dwSize);
    if (NULL == pIndexes)
    {
        wprintf(L"malloc failed to allocate memory for pIndexes.\n");
        goto cleanup;
    }

    ZeroMemory(pIndexes, dwSize);

    for (DWORD i = 0, j = 0; i < *pdwNumberOfCounters; i++)
    {
        // Ignore base counters.
        if ((((PERF_COUNTER_DEFINITION*)pCounter)->CounterType & PERF_COUNTER_BASE) == PERF_COUNTER_BASE)
        {
            dwSkippedBaseRecords++;
        }
        else
        {
            pIndexes[j]  = ((PERF_COUNTER_DEFINITION*)pCounter)->CounterNameTitleIndex;
            j++;  
        }

        pCounter += ((PERF_COUNTER_DEFINITION*)pCounter)->ByteLength;
    }

    // Decrement so the sort works.
    *pdwNumberOfCounters -= dwSkippedBaseRecords;

    // Sort the index values of the object's counters.
    qsort(pIndexes, *pdwNumberOfCounters, sizeof(DWORD), CompareCounters);
    *ppdwIndexes = pIndexes;
    fSuccess = TRUE;

cleanup:

    return fSuccess;
}

// Allocate a block of memory and load the full instance name for each instance
// of the object.
BOOL LoadInstances(LPWSTR* ppNames, LPBYTE pInstance, DWORD dwNumberofInstances, DWORD CodePage)
{
    BOOL fSuccess = FALSE;
    DWORD dwSize = (sizeof(WCHAR) * (MAX_FULL_INSTANCE_NAME_LEN+1)) * dwNumberofInstances;
    PERF_COUNTER_BLOCK* pCounter = NULL;
    LPWSTR pName = NULL;

    *ppNames = (LPWSTR)malloc(dwSize);
    if (NULL == *ppNames)
    {
        wprintf(L"malloc failed to allocate memory for *ppNames.\n");
        goto cleanup;
    }

    ZeroMemory(*ppNames, dwSize);
    pName = *ppNames;

    for (DWORD i = 0; i < dwNumberofInstances; i++)
    {
        fSuccess = GetFullInstanceName((PERF_INSTANCE_DEFINITION*)pInstance, CodePage, pName);
        if (FALSE == fSuccess)
        {
            wprintf(L"GetFullInstanceName failed.\n");
            goto cleanup;
        }

        pName = pName + (MAX_FULL_INSTANCE_NAME_LEN+1);

        pCounter = (PERF_COUNTER_BLOCK*)(pInstance + ((PERF_INSTANCE_DEFINITION*)pInstance)->ByteLength);
        pInstance += ((PERF_INSTANCE_DEFINITION*)pInstance)->ByteLength + pCounter->ByteLength;
    }

    // Sort the instance names of the object's instances.
    qsort(*ppNames, dwNumberofInstances, sizeof(WCHAR) * (MAX_FULL_INSTANCE_NAME_LEN+1), CompareInstances);

cleanup:

    return fSuccess;
}


// Retrieve the full name of the instance. The full name of the instance includes
// the name of this instance and its parent instance, if this instance is a 
// child instance. The full name is in the form, "parent name/child name".
// For example, a thread instance is a child of a process instance. 
//
// Providers are encouraged to use Unicode strings for instance names. If 
// PERF_INSTANCE_DEFINITION.CodePage is zero, the name is in Unicode; otherwise,
// use the CodePage value to convert the string to Unicode.
BOOL GetFullInstanceName(PERF_INSTANCE_DEFINITION* pInstance, DWORD CodePage, WCHAR* pName)
{
    BOOL fSuccess = TRUE;
    PERF_INSTANCE_DEFINITION *pParentInstance = NULL;
    PERF_OBJECT_TYPE *pParentObject = NULL;
    DWORD dwLength = 0;
    WCHAR wszInstanceName[MAX_INSTANCE_NAME_LEN+1];
    WCHAR wszParentInstanceName[MAX_INSTANCE_NAME_LEN+1];

    if (CodePage == 0)  // Instance name is a Unicode string
    {
        // PERF_INSTANCE_DEFINITION->NameLength is in bytes, so convert to characters.
        dwLength = (MAX_INSTANCE_NAME_LEN < (pInstance->NameLength/2)) ? MAX_INSTANCE_NAME_LEN : pInstance->NameLength/2;
        StringCchCopyN(wszInstanceName, MAX_INSTANCE_NAME_LEN+1, (LPWSTR)(((LPBYTE)pInstance)+pInstance->NameOffset), dwLength);
        wszInstanceName[dwLength] = '\0';
    }
    else  // Convert the multi-byte instance name to Unicode
    {
        fSuccess = ConvertNameToUnicode(CodePage, 
            (LPCSTR)(((LPBYTE)pInstance)+pInstance->NameOffset),  // Points to string
            pInstance->NameLength,
            wszInstanceName);

        if (FALSE == fSuccess)
        {
            wprintf(L"ConvertNameToUnicode for instance failed.\n");
            goto cleanup;
        }
    }

    if (pInstance->ParentObjectTitleIndex)
    {
        // Use the index to find the parent object. The pInstance->ParentObjectInstance
        // member tells you that the parent instance is the nth instance of the 
        // parent object.
        pParentObject = GetObject(pInstance->ParentObjectTitleIndex);
        pParentInstance = GetParentInstance(pParentObject, pInstance->ParentObjectInstance);

        if (CodePage == 0)  // Instance name is a Unicode string
        {
            dwLength = (MAX_INSTANCE_NAME_LEN < pParentInstance->NameLength/2) ? MAX_INSTANCE_NAME_LEN : pParentInstance->NameLength/2;
            StringCchCopyN(wszParentInstanceName, MAX_INSTANCE_NAME_LEN+1, (LPWSTR)(((LPBYTE)pParentInstance)+pParentInstance->NameOffset), dwLength);
            wszParentInstanceName[dwLength] = '\0';
        }
        else  // Convert the multi-byte instance name to Unicode
        {
            fSuccess = ConvertNameToUnicode(CodePage, 
                (LPCSTR)(((LPBYTE)pParentInstance)+pParentInstance->NameOffset),  //Points to string.
                pInstance->NameLength,
                wszParentInstanceName);

            if (FALSE == fSuccess)
            {
                wprintf(L"ConvertNameToUnicode for parent instance failed.\n");
                goto cleanup;
            }
        }

        StringCchPrintf(pName, MAX_FULL_INSTANCE_NAME_LEN+1, L"%s/%s", wszParentInstanceName, wszInstanceName);
    }
    else
    {
        StringCchPrintf(pName, MAX_INSTANCE_NAME_LEN+1, L"%s", wszInstanceName);
    }

cleanup:

    return fSuccess;
}


// Converts a multi-byte string to a Unicode string. If the input string is longer than 
// MAX_INSTANCE_NAME_LEN, the input string is truncated.
BOOL ConvertNameToUnicode(UINT CodePage, LPCSTR pNameToConvert, DWORD dwNameToConvertLen, LPWSTR pConvertedName)
{
    BOOL fSuccess = FALSE;
    int CharsConverted = 0;
    DWORD dwLength = 0;

    // dwNameToConvertLen is in bytes, so convert MAX_INSTANCE_NAME_LEN to bytes.
    dwLength = (MAX_INSTANCE_NAME_LEN*sizeof(WCHAR) < (dwNameToConvertLen)) ? MAX_INSTANCE_NAME_LEN*sizeof(WCHAR) : dwNameToConvertLen;

    CharsConverted = MultiByteToWideChar((UINT)CodePage, 0, pNameToConvert, dwLength, pConvertedName, MAX_INSTANCE_NAME_LEN);
    if (CharsConverted)
    {
        pConvertedName[dwLength] = '\0';
        fSuccess = TRUE;
    } else 
    {
        // If the specified code page didn't work, try one more time, assuming that the input string is Unicode.
        dwLength = (MAX_INSTANCE_NAME_LEN < (dwNameToConvertLen/2)) ? MAX_INSTANCE_NAME_LEN : dwNameToConvertLen/2;
        if (SUCCEEDED(StringCchCopyN(pConvertedName, MAX_INSTANCE_NAME_LEN+1, (LPWSTR)pNameToConvert, dwLength))) 
        {
            pConvertedName[dwLength] = '\0';
            fSuccess = TRUE;
        }
    }

    return fSuccess;
}


// Find the object using the specified index value.
PERF_OBJECT_TYPE* GetObject(DWORD dwObjectToFind)
{
    LPBYTE pObject = g_pPerfDataHead + ((PERF_DATA_BLOCK*)g_pPerfDataHead)->HeaderLength;
    DWORD dwNumberOfObjects = ((PERF_DATA_BLOCK*)g_pPerfDataHead)->NumObjectTypes;
    BOOL fFoundObject = FALSE;

    for (DWORD i = 0; i < dwNumberOfObjects; i++)
    {
        if (dwObjectToFind == ((PERF_OBJECT_TYPE*)pObject)->ObjectNameTitleIndex)
        {
            fFoundObject = TRUE;
            break;
        }

        pObject += ((PERF_OBJECT_TYPE*)pObject)->TotalByteLength;
    }

    return (fFoundObject) ? (PERF_OBJECT_TYPE*)pObject : NULL;  
}


// Find the nth instance of an object.
PERF_INSTANCE_DEFINITION* GetParentInstance(PERF_OBJECT_TYPE* pObject, DWORD dwInstancePosition)
{
    LPBYTE pInstance = (LPBYTE)pObject + pObject->DefinitionLength;
    PERF_COUNTER_BLOCK* pCounter = NULL;

    for (DWORD i = 0; i < dwInstancePosition; i++)
    {
        pCounter = (PERF_COUNTER_BLOCK*)(pInstance + ((PERF_INSTANCE_DEFINITION*)pInstance)->ByteLength);
        pInstance += ((PERF_INSTANCE_DEFINITION*)pInstance)->ByteLength + pCounter->ByteLength;
    }

    return (PERF_INSTANCE_DEFINITION*)pInstance;
}


// Used by qsort to put the object names in alphabetical order.
int CompareObjects(const void *pObject1, const void *pObject2)
{
    DWORD index1 = ((PPERF_OBJECT)pObject1)->dwObjectIndex;
    DWORD index2 = ((PPERF_OBJECT)pObject2)->dwObjectIndex;
    LPWSTR pName1 = g_pCounterTextHead+g_pTextOffsets[index1];
    LPWSTR pName2 = g_pCounterTextHead+g_pTextOffsets[index2];
    DWORD dwName1Len = (DWORD)wcslen(pName1);
    DWORD dwName2Len = (DWORD)wcslen(pName2);

  return _wcsnicmp(pName1, pName2, (dwName1Len < dwName2Len) ? dwName1Len : dwName2Len);
}


// Used by qsort to put the counter names in alphabetical order.
int CompareCounters(const void *pCounter1, const void *pCounter2)
{
    DWORD index1 = *(DWORD*)pCounter1;
    DWORD index2 = *(DWORD*)pCounter2;
    LPWSTR pName1 = g_pCounterTextHead+g_pTextOffsets[index1];
    LPWSTR pName2 = g_pCounterTextHead+g_pTextOffsets[index2];
    DWORD dwName1Len = (DWORD)wcslen(pName1);
    DWORD dwName2Len = (DWORD)wcslen(pName2);

    return _wcsnicmp(pName1, pName2, (dwName1Len < dwName2Len) ? dwName1Len : dwName2Len);
}


// Used by qsort to put the instance names in alphabetical order.
int CompareInstances(const void *pName1, const void *pName2)
{
    return _wcsicmp((LPWSTR)pName1, (LPWSTR)pName2);
}


// Print the object names and optionally print their counter and instance names.
void PrintObjectNames(DWORD dwNumberOfObjects, BOOL fIncludeCounters, BOOL fIncludeInstances)
{
    PERF_INSTANCE_DEFINITION* pInstance = NULL;
    DWORD dwIncrementSize = sizeof(WCHAR) * (MAX_FULL_INSTANCE_NAME_LEN+1);
    DWORD index = 0;
    DWORD dwSerialNo = 0;

    for (DWORD i = 0; i < dwNumberOfObjects; i++)
    {
        index = g_pObjects[i].dwObjectIndex;
        wprintf(L"%d %s\n", index, g_pCounterTextHead+g_pTextOffsets[index]);

        // There can be multiple instances with duplicate names. For example, the
        // Process object can have multiple instance of svchost. To differentiate
        // the instances, append a serial number to the name of duplicate instances
        // If the object contained three svchost names, the function prints them
        // as svchost, svchost#1, and svchost#2.
        // If running on Windows 11 or later, you can avoid this issue by
        // using the "Process V2" counterset.
        if (fIncludeInstances && g_pObjects[i].dwNumberofInstances > 0)
        {
            dwSerialNo = 0;

            for (DWORD j = 0; j < g_pObjects[i].dwNumberofInstances; j++)
            {
                if (j > 0)
                {
                    if ( !CompareInstances( (void*)(g_pObjects[i].pInstanceNames + ((j-1) * (MAX_FULL_INSTANCE_NAME_LEN+1))), 
                        (void*)(g_pObjects[i].pInstanceNames + (j * (MAX_FULL_INSTANCE_NAME_LEN+1))) ) )
                    {
                        dwSerialNo++;
                    }
                    else
                    {
                        dwSerialNo = 0;
                    }
                }

                if (dwSerialNo)
                    wprintf(L"\t%s#%d\n", g_pObjects[i].pInstanceNames + (j * (MAX_FULL_INSTANCE_NAME_LEN+1)), dwSerialNo);
                else
                    wprintf(L"\t%s\n", g_pObjects[i].pInstanceNames + (j * (MAX_FULL_INSTANCE_NAME_LEN+1)));
            }

            wprintf(L"\n");
        }

        if (fIncludeCounters)
        {
            for (DWORD j = 0; j < g_pObjects[i].dwNumberOfCounters; j++)
            {
                index = g_pObjects[i].pdwCounterIndexes[j];
                wprintf(L"\t%d %s\n", index, g_pCounterTextHead+g_pTextOffsets[index]);
            }

            wprintf(L"\n\n");
        }
    }

    wprintf(L"\n\n");
}


void FreePerfObjects(PPERF_OBJECT pObjects, DWORD dwNumberOfObjects)
{
    if (pObjects)
    {
        for (DWORD i=0; i < dwNumberOfObjects; i++)
        {
            if (pObjects[i].pInstanceNames)
            {
                free(pObjects[i].pInstanceNames);
            }

            if (pObjects[i].pdwCounterIndexes)
            {
                free(pObjects[i].pdwCounterIndexes);
            }
        }

        free(pObjects);
        pObjects = NULL;
    }
}