0

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 :

  • counting everything including the NumberOfEntries field, that is DWORD wsi2_buffer_size = sizeof(wsi.NumberOfEntries) + wsi.NumberOfEntries * sizeof(PSAPI_WORKING_SET_BLOCK); => error 998;
  • counting only the number of entries, that is the code given above => error 998;
  • the size of the variable wsi2, that is DWORD wsi2_buffer_size = sizeof(wsi2); => error 24;

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?
Wassim
  • 386
  • 2
  • 15
  • [Why do some structures end with an array of size 1?](https://devblogs.microsoft.com/oldnewthing/20040826-00/?p=38043) – IInspectable Aug 23 '20 at 18:31
  • 1
    Also, `wsi` is a pointer, so `sizeof(wsi)` evaluates to the size of a pointer (i.e. 4 or 8). Though [QueryWorkingSet](https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-queryworkingset) expects *"the size of the `pv` buffer, in bytes"*. – IInspectable Aug 23 '20 at 18:36

1 Answers1

2

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);
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 2
    Note, also, that the buffer size calculation is wrong. It ignores alignment requirements. Though, probably, inconsequential here, as it is over-allocating for 1 additional entry. Raymond Chen's link above explains how to properly determine the size. – IInspectable Aug 23 '20 at 18:44
  • @IInspectable thanks, I updated my example to reflect that – Remy Lebeau Aug 23 '20 at 18:58
  • *You must initialize the NumberOfEntries field to the actual number of PSAPI_WORKING_SET_BLOCK buffers you have allocated* - this is wrong. we must not do this – RbMm Aug 23 '20 at 19:24
  • use `HeapReAlloc` no sense, for what ? we not need copy previous content to new allocation. need use only `HeapAlloc`, not need zero memory (`HEAP_ZERO_MEMORY`) too – RbMm Aug 23 '20 at 19:27
  • @RbMm I have updated my answer about the use of `NumberOfEntries`. I used `HeapReAlloc()` to avoid using `HeapFree()` on every loop iteration. It is not about preserving content, it is about letting the heap manager expanding the existing memory block in-place if it is able to. – Remy Lebeau Aug 23 '20 at 19:32
  • yes, i understand for what you use `HeapReAlloc`m but anyway `HeapFree` is called internal for old block. so question what is take more time - free and acquire again heap lock, or copy memory from one block to another. i be anyway use `HeapAlloc` only (and src code become more simply with this too) – RbMm Aug 23 '20 at 19:34
  • @RbMm whatever. I updated my example. Personally, I wouldn't use the Heap functions directly to begin with, but that is the OP's choice. I'm done now. – Remy Lebeau Aug 23 '20 at 19:37