다음을 통해 공유


개체, 인스턴스 및 카운터 이름 표시

사용자 인터페이스에 개체 및 해당 카운터 목록을 표시하려면 성능 데이터를 검색해야 합니다. 성능 데이터에는 가변적인 수의 성능 개체와 해당 인스턴스 및 카운터가 포함됩니다. 성능 데이터의 형식에 대한 자세한 내용은 성능 데이터 형식을 참조하세요.

성능 데이터에는 개체 및 카운터 이름이 포함되어 있지 않습니다. 대신 데이터에는 성능 카운터 인프라에 알려진 리소스에 저장된 개체 및 카운터의 이름을 검색하는 데 사용하는 인덱스 값이 포함됩니다. 그러나 instance 이름은 성능 데이터에 저장됩니다. 레지스트리에서 개체 및 카운터 이름을 읽고 나중에 이름에 액세스하기 위한 인덱스 테이블을 만드는 예제는 카운터 이름 및 도움말 텍스트 검색을 참조하세요.

다음 예제에서는 성능 데이터에서 개체, instance 및 카운터 블록을 검색하는 방법을 보여 줍니다. 개체 이름, instance 이름 및 카운터 이름을 검색한 후 예제에서는 개체를 이름으로 정렬하고 각 개체에 대해 해당 인스턴스와 카운터를 이름으로 정렬합니다. 그런 다음, 정렬된 목록을 인쇄합니다.

#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;
    }
}