c++windowswinapipsapi

What's the actual size of PSAPI_WORKING_SET_INFORMATION buffer used in QueryWorkingSet function of PSAPI.h


I'd like to use the function QueryWorkingSet available in PSAPI, but I'm having trouble to actually define the size of the buffer pv. Here is the code :

#include <Windows.h>
#include <Psapi.h>
#include <iostream>


void testQueryWorkingSet()
{
    unsigned int counter;
    HANDLE thisProcess = GetCurrentProcess();
    SYSTEM_INFO si;
    PSAPI_WORKING_SET_INFORMATION wsi, wsi2;

    GetSystemInfo(&si);
    QueryWorkingSet(thisProcess, &wsi, sizeof(wsi));

    DWORD wsi2_buffer_size = (wsi.NumberOfEntries) * sizeof(PSAPI_WORKING_SET_BLOCK);

    if (!QueryWorkingSet(thisProcess, &wsi2, wsi2_buffer_size))
    {
        std::cout << "ERROR CODE : " << GetLastError() << std::endl;
        abort();
    }
}

int main(int argc, char * argv[])
{
    testQueryWorkingSet();

    int* test = new int[1000000];

    testQueryWorkingSet();
}

I keep ending up with abort() being called and either an error code 24 or 998 during the first call to testQueryWorkingSet(). that I interpret respectively as : wsi2_buffer_size is too low and wsi2_buffer_size is too big.

Now I have no idea of the value this variable should take, I tried :

There has to be something I do not understand in the way we're supposed to use this function but I can't find what. I tried to adapt the code given there, that is :

#include <Windows.h>
#include <Psapi.h>
#include <iostream>


void testQueryWorkingSet()
{
    unsigned int counter;
    HANDLE thisProcess = GetCurrentProcess();
    SYSTEM_INFO si;
    PSAPI_WORKING_SET_INFORMATION wsi_1, * wsi;
    DWORD wsi_size;

    GetSystemInfo(&si);

    wsi_1.NumberOfEntries = 0;
    QueryWorkingSet(thisProcess, (LPVOID)&wsi_1, sizeof(wsi));

#if !defined(_WIN64)
    wsi_1.NumberOfEntries--;
#endif
    wsi_size = sizeof(PSAPI_WORKING_SET_INFORMATION)
        + sizeof(PSAPI_WORKING_SET_BLOCK) * wsi_1.NumberOfEntries;
    wsi = (PSAPI_WORKING_SET_INFORMATION*)HeapAlloc(GetProcessHeap(),
        HEAP_ZERO_MEMORY, wsi_size);

    if (!QueryWorkingSet(thisProcess, (LPVOID)wsi, wsi_size)) {
        printf("# Second QueryWorkingSet failed: %lu\n"
            , GetLastError());
        abort();
    }
}

int main(int argc, char * argv[])
{
    testQueryWorkingSet();

    int* test = new int[1000000];

    testQueryWorkingSet();
}

This code is working for only 1 call to testQueryWorkingSet(), the second one is aborting with error code 24. Here are the questions in brief :

  1. How would you use QueryWorkingSet in a function that you could call multiple times successively?
  2. What is representing the value of the parameter cb of the documentation given a PSAPI_WORKING_SET_INFORMATION?

Solution

  • Both examples are completely ignoring the return value and error code of the 1st call of QueryWorkingSet(). You are doing error handling only on the 2nd call.

    Your 1st example fails because you are not taking into account the entire size of the PSAPI_WORKING_SET_INFORMATION when calculating wsi2_buffer_size for the 2nd call of QueryWorkingSet(). Even if the 1st call were successful, you are not allocating any additional memory for the 2nd call to fill in, if the NumberOfEntries returned is > 1.

    Your 2nd example is passing in the wrong buffer size value to the cb parameter of the 1st call of QueryWorkingSet(). You are passing in just the size of a single pointer, not the size of the entire PSAPI_WORKING_SET_INFORMATION. Error 24 is ERROR_BAD_LENGTH. You need to use sizeof(wsi_1) instead of sizeof(wsi).

    I would suggest calling QueryWorkingSet() in a loop, in case the working set actually changes in between the call to query its size and the call to get its data.

    Also, be sure you free the memory you allocate when you are done using it.

    With that said, try something more life this:

    void testQueryWorkingSet()
    {
        HANDLE thisProcess = GetCurrentProcess();
    
        PSAPI_WORKING_SET_INFORMATION *wsi, *wsi_new;
        DWORD wsi_size;
        ULONG_PTR count = 1; // or whatever initial size you want...
    
        do
        {
            wsi_size = offsetof(PSAPI_WORKING_SET_INFORMATION, WorkingSetInfo[count]);
    
            wsi = (PSAPI_WORKING_SET_INFORMATION*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, wsi_size);
            if (!wsi)
            {
                printf("HeapAlloc failed: %lu\n", GetLastError());
                abort();
            }
    
            if (QueryWorkingSet(thisProcess, wsi, wsi_size))
                break;
    
            if (GetLastError() != ERROR_BAD_LENGTH)
            {
                printf("QueryWorkingSet failed: %lu\n", GetLastError());
                HeapFree(GetProcessHeap(), 0, wsi);
                abort();
            }
    
            count = wsi->NumberOfEntries;
            HeapFree(GetProcessHeap(), 0, wsi);
        }
        while (true);
    
        // use wsi as needed...
    
        HeapFree(GetProcessHeap(), 0, wsi);
    }