I tripped over this function while trying to run a game, so I thought I would have a go at it :-)
The implementation is very similar to what is done in `Local32Info16` already.
Regarding the place to put the function: I am not sure about the exact relationship between `kernel32` and `kernelbase`, but as most of the heap related functions are implemented in `kernelbase` I opted to add it to the latter. The tests reside in the `kernel32` directory though.
The tests are currently pretty basic as I was a bit hesitant to compare against hard coded sizes for reserved / committed memory (that could break if someone made changes to the heap implementation).
Also, I am not entirely sure about the semantics of the `cbMaxReserve` field. I opted to always set it to the same value as `cbReserved`, which seems *good enough™*.
Let me know if this needs improvement!
-- v2: kernelbase: Implement HeapSummary
From: Julius Bettin julius.bettin@protonmail.com
--- dlls/kernel32/kernel32.spec | 2 +- dlls/kernel32/tests/heap.c | 45 +++++++++++++++++++++++++++++++++ dlls/kernelbase/kernelbase.spec | 2 +- dlls/kernelbase/memory.c | 39 ++++++++++++++++++++++++++++ include/minwinbase.h | 9 +++++++ include/winbase.h | 1 + 6 files changed, 96 insertions(+), 2 deletions(-)
diff --git a/dlls/kernel32/kernel32.spec b/dlls/kernel32/kernel32.spec index caa6c92b653..ff3bdc67b21 100644 --- a/dlls/kernel32/kernel32.spec +++ b/dlls/kernel32/kernel32.spec @@ -957,7 +957,7 @@ @ stub HeapSetFlags @ stdcall -import HeapSetInformation(ptr long ptr long) @ stdcall HeapSize(long long ptr) NTDLL.RtlSizeHeap -@ stub HeapSummary +@ stdcall -import HeapSummary(long long ptr) @ stdcall -import HeapUnlock(long) @ stub HeapUsage @ stdcall -import HeapValidate(long long ptr) diff --git a/dlls/kernel32/tests/heap.c b/dlls/kernel32/tests/heap.c index c62eb449192..ca938a4e16d 100644 --- a/dlls/kernel32/tests/heap.c +++ b/dlls/kernel32/tests/heap.c @@ -3740,6 +3740,49 @@ static void test_heap_sizes(void) } }
+static void test_HeapSummary(void) +{ + HANDLE heap; + HEAP_SUMMARY heap_summary; + BOOL ret; + void *p; + + /* setup */ + + heap = HeapCreate( 0, 0, 0 ); /* growable heap */ + ok( heap != NULL, "creation failed\n" ); + + HeapAlloc( heap , 0, 0x100 ); + HeapAlloc( heap , 0, 0x200 ); + p = HeapAlloc( heap , 0, 0x300 ); + HeapAlloc( heap, 0, 0x60000 ); + HeapFree( heap, 0, p ); + + memset( &heap_summary, 0, sizeof(heap_summary) ); + + /* test cases */ + + ret = HeapSummary( heap, 0, &heap_summary ); + ok( !ret, "HeapSummary() with cb != sizeof(HEAP_SUMMARY) returned TRUE\n" ); + + heap_summary.cb = sizeof(heap_summary); + ret = HeapSummary( heap, 0, &heap_summary ); + ok( ret, "HeapSummary() returned FALSE\n" ); + + ok( heap_summary.cbAllocated == 0x100 + 0x200 + 0x60000, + "HeapSummary: wrong cbAllocated value %#Ix\n", heap_summary.cbAllocated ); + ok( heap_summary.cbCommitted >= heap_summary.cbAllocated, + "HeapSummary: cbCommitted %#Ix < cbAllocated %#Ix\n", + heap_summary.cbCommitted, heap_summary.cbAllocated ); + ok( heap_summary.cbReserved >= heap_summary.cbCommitted, + "HeapSummary: cbReserved %#Ix < cbCommitted %#Ix\n", + heap_summary.cbReserved, heap_summary.cbCommitted ); + + /* cleanup */ + + HeapDestroy( heap ); +} + START_TEST(heap) { int argc; @@ -3777,4 +3820,6 @@ START_TEST(heap) } else win_skip( "RtlGetNtGlobalFlags not found, skipping heap debug tests\n" ); test_heap_sizes(); + + test_HeapSummary(); } diff --git a/dlls/kernelbase/kernelbase.spec b/dlls/kernelbase/kernelbase.spec index fd9868fb69e..545c30a1eb8 100644 --- a/dlls/kernelbase/kernelbase.spec +++ b/dlls/kernelbase/kernelbase.spec @@ -821,7 +821,7 @@ @ stdcall HeapReAlloc(long long ptr long) ntdll.RtlReAllocateHeap @ stdcall HeapSetInformation(ptr long ptr long) @ stdcall HeapSize(long long ptr) ntdll.RtlSizeHeap -@ stub HeapSummary +@ stdcall HeapSummary(long long ptr) @ stdcall HeapUnlock(long) @ stdcall HeapValidate(long long ptr) @ stdcall HeapWalk(long ptr) diff --git a/dlls/kernelbase/memory.c b/dlls/kernelbase/memory.c index 39018012199..16aa34812ef 100644 --- a/dlls/kernelbase/memory.c +++ b/dlls/kernelbase/memory.c @@ -777,6 +777,45 @@ BOOL WINAPI HeapSetInformation( HANDLE heap, HEAP_INFORMATION_CLASS infoclass, P }
+/*********************************************************************** + * HeapSummary (kernelbase.@) + */ +BOOL WINAPI HeapSummary( HANDLE heap, DWORD flags, LPHEAP_SUMMARY heap_summary ) +{ + SIZE_T allocated = 0; + SIZE_T committed = 0; + SIZE_T uncommitted = 0; + PROCESS_HEAP_ENTRY entry; + + if (heap_summary->cb != sizeof(*heap_summary)) + return FALSE; /* needs to be set to the exact size by the caller */ + + memset( &entry, 0, sizeof(entry) ); + while (HeapWalk( heap, &entry )) + { + if (entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) + { + allocated += entry.cbData; + } + else if (entry.wFlags & PROCESS_HEAP_REGION) + { + committed += entry.Region.dwCommittedSize; + uncommitted += entry.Region.dwUnCommittedSize; + } + } + + if (GetLastError() != ERROR_NO_MORE_ITEMS) + return FALSE; /* HeapWalk unsuccessful */ + + heap_summary->cbAllocated = allocated; + heap_summary->cbCommitted = committed; + heap_summary->cbReserved = committed + uncommitted; + heap_summary->cbMaxReserve = heap_summary->cbReserved; + + return TRUE; +} + + /*********************************************************************** * HeapUnlock (kernelbase.@) */ diff --git a/include/minwinbase.h b/include/minwinbase.h index 86938533374..a8d2dc4d086 100644 --- a/include/minwinbase.h +++ b/include/minwinbase.h @@ -143,6 +143,15 @@ typedef struct _PROCESS_HEAP_ENTRY #define PROCESS_HEAP_ENTRY_MOVEABLE 0x0010 #define PROCESS_HEAP_ENTRY_DDESHARE 0x0020
+typedef struct _HEAP_SUMMARY +{ + DWORD cb; + SIZE_T cbAllocated; + SIZE_T cbCommitted; + SIZE_T cbReserved; + SIZE_T cbMaxReserve; +} HEAP_SUMMARY, *PHEAP_SUMMARY, *LPHEAP_SUMMARY; + typedef enum _GET_FILEEX_INFO_LEVELS { GetFileExInfoStandard } GET_FILEEX_INFO_LEVELS; diff --git a/include/winbase.h b/include/winbase.h index f75b7503e41..1bbfd1a31a6 100644 --- a/include/winbase.h +++ b/include/winbase.h @@ -2089,6 +2089,7 @@ WINBASEAPI SIZE_T WINAPI HeapSize(HANDLE,DWORD,LPCVOID); WINBASEAPI BOOL WINAPI HeapUnlock(HANDLE); WINBASEAPI BOOL WINAPI HeapValidate(HANDLE,DWORD,LPCVOID); WINBASEAPI BOOL WINAPI HeapWalk(HANDLE,LPPROCESS_HEAP_ENTRY); +WINBASEAPI BOOL WINAPI HeapSummary(HANDLE,DWORD,LPHEAP_SUMMARY); WINBASEAPI BOOL WINAPI InitAtomTable(DWORD); WINADVAPI BOOL WINAPI InitializeAcl(PACL,DWORD,DWORD); WINBASEAPI VOID WINAPI InitializeConditionVariable(PCONDITION_VARIABLE);
Vibhav Pant (@vibhavp) commented about dlls/kernelbase/memory.c:
}
+/***********************************************************************
HeapSummary (kernelbase.@)
- */
+BOOL WINAPI HeapSummary( HANDLE heap, DWORD flags, LPHEAP_SUMMARY heap_summary ) +{
- SIZE_T allocated = 0;
- SIZE_T committed = 0;
- SIZE_T uncommitted = 0;
- PROCESS_HEAP_ENTRY entry;
- if (heap_summary->cb != sizeof(*heap_summary))
return FALSE; /* needs to be set to the exact size by the caller */
Does HeapSummary not set an error code with SetLastError? The documentation doesnt suggest any error code, but it might be helpful to make sure and test for them if it actually does so in practice.
If it's going to use HeapWalk(), it should lock the heap I think. It's also worth exploring if ntdll has a dedicated function somewhere to return this information.
On Sat Jun 7 15:21:38 2025 +0000, Vibhav Pant wrote:
Does HeapSummary not set an error code with SetLastError? The documentation doesnt suggest any error code, but it might be helpful to make sure and test for them if it actually does so in practice.
Good point, I will check later and update the code accordingly.
On Sat Jun 7 15:56:23 2025 +0000, Nikolay Sivov wrote:
If it's going to use HeapWalk(), it should lock the heap I think. It's also worth exploring if ntdll has a dedicated function somewhere to return this information.
You are right, it should lock.
Regarding the dedicated function in `ntdll`: I found `RltUsageHeap` which seems to be the corresponding function. Judging by the signature its API seems to be a bit more complex though as it also returns some extra stuff (in the form of linked lists at that) and there is not much documentation (none?). What would be a good way to go about this? Test how the original function behaves and try to recreate it?
On Sat Jun 7 15:56:23 2025 +0000, Julius Bettin wrote:
You are right, it should lock. Regarding the dedicated function in `ntdll`: I found `RltUsageHeap` which seems to be the corresponding function. Judging by the signature its API seems to be a bit more complex though as it also returns some extra stuff (in the form of linked lists at that) and there is not much documentation (none?). What would be a good way to go about this? Test how the original function behaves and try to recreate it?
RtlUsageHeap does not exist in current Windows releases unfortunately. I'm not sure which one is a replacement, or if we need this to be moved in ntdll at all.