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!
-- v3: kernelbase: Implement HeapSummary
From: Julius Bettin julius.bettin@protonmail.com
--- dlls/kernel32/kernel32.spec | 2 +- dlls/kernel32/tests/heap.c | 52 +++++++++++++++++++++++++++++ dlls/kernelbase/kernelbase.spec | 2 +- dlls/kernelbase/memory.c | 58 +++++++++++++++++++++++++++++++++ include/minwinbase.h | 9 +++++ include/winbase.h | 1 + 6 files changed, 122 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..64327352622 100644 --- a/dlls/kernel32/tests/heap.c +++ b/dlls/kernel32/tests/heap.c @@ -3740,6 +3740,56 @@ static void test_heap_sizes(void) } }
+static void test_HeapSummary(void) +{ + HANDLE heap; + HEAP_SUMMARY heap_summary; + BOOL ret; + DWORD err; + 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 ); + err = GetLastError(); + ok( !ret, "HeapSummary() with cb != sizeof(HEAP_SUMMARY) returned TRUE\n" ); + ok( err == ERROR_INVALID_PARAMETER, + "HeapSummary() with cb != sizeof(HEAP_SUMMARY) set last error to %lu\n", err ); + + heap_summary.cb = sizeof(heap_summary); + SetLastError( ERROR_INVALID_HANDLE ); /* set to known value */ + ret = HeapSummary( heap, 0, &heap_summary ); + err = GetLastError(); + ok( ret, "HeapSummary() returned FALSE\n" ); + ok( err == ERROR_INVALID_HANDLE, "HeapSummary() set last error on success to %lu\n", err ); + + 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 +3827,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..955e1d5b7c1 100644 --- a/dlls/kernelbase/memory.c +++ b/dlls/kernelbase/memory.c @@ -777,6 +777,64 @@ BOOL WINAPI HeapSetInformation( HANDLE heap, HEAP_INFORMATION_CLASS infoclass, P }
+/*********************************************************************** + * HeapSummary (kernelbase.@) + */ +BOOL WINAPI HeapSummary( HANDLE heap, DWORD flags, LPHEAP_SUMMARY heap_summary ) +{ + DWORD last_error = GetLastError(); + SIZE_T allocated = 0; + SIZE_T committed = 0; + SIZE_T uncommitted = 0; + PROCESS_HEAP_ENTRY entry; + + if (heap_summary->cb != sizeof(*heap_summary)) + { + /* needs to be set to the exact size by the caller */ + SetLastError( ERROR_INVALID_PARAMETER ); + return FALSE; + } + + memset( &entry, 0, sizeof(entry) ); + + if (!HeapLock( heap )) + return FALSE; + + 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) + { + /* HeapWalk unsuccessful */ + (void) HeapUnlock( heap ); + return FALSE; + } + + if (!HeapUnlock( heap )) + return FALSE; + + heap_summary->cbAllocated = allocated; + heap_summary->cbCommitted = committed; + heap_summary->cbReserved = committed + uncommitted; + heap_summary->cbMaxReserve = heap_summary->cbReserved; + + /* restore previous last error on success to make it look + unchanged for the caller. */ + SetLastError(last_error); + 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);
On Sat Jun 7 23:51:20 2025 +0000, Julius Bettin wrote:
changed this line in [version 3 of the diff](/wine/wine/-/merge_requests/8237/diffs?diff_id=183690&start_sha=cf13d97d584573b97f58123ade4e4136929e7319#5ae7449abf26aa1926b9b677633a99ed950907af_791_792)
From what I could see `HeapSummary` behaves in the following way: 1. The `flag` parameter is ignored 2. On success it does not explicitly set / overwrite the last error 3. Passing an invalid `HEAP_SUMMARY` pointer (e.g. `NULL`) causes a crash 4. Passing a valid `HEAP_SUMMARY` pointer but setting the `cb` to something other than `sizeof(HEAP_SUMMARY)` yields `ERROR_INVALID_PARAMETER` 5. Passing an invalid heap handle seems to yield `998` (`ERROR_NOACCESS`)
I tried to recreate that behavior, except for 5 as I am not even sure if this is directly checked by `HeapSummary` or a follow up error.
On Sat Jun 7 18:13:49 2025 +0000, Nikolay Sivov wrote:
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.
I added the locking