Geoff Chappell - Software Analyst
COMCTL32 manages each Dynamic Pointer Array through a 14h-byte control structure (20h bytes in x64 builds), whose address serves as a handle to the DPA.
typedef struct _DPA { int cpItems; PVOID *pArray; HANDLE hHeap; int cpCapacity; int cpGrow; } DPA, *HDPA;
The cpItems member is the current size of the pointer array, measured as a number of pointers.
The pArray member is the address of the pointer array, or is NULL.
The hHeap member is a handle to the heap that provides memory for this DPA.
The cpCapacity member is the current capacity of the pointer array, measured as a number of pointers.
The cpGrow member is the allocation unit for the heap block that holds the pointer array, measured as a number of pointers.
The DPA structure, but not its internal detail, was documented as part of Microsoft’s documentation of DPA functions among the Settlement Program Interfaces in 2002. The names _DPA for the structure and HDPA for the handle are Microsoft’s. The names given above for the members of this structure are invented.
This omission of internal detail is entirely in keeping with a principle that the DPA is opaque, so that code outside the implementations of the DPA functions, or at least outside COMCTL32, may use the structure’s address as a handle but not interpret the structure. Microsoft’s own practice, however, is that various modules external to COMCTL32 interpret either or both of the cpItems and pArray members. Among these modules are BROWSEUI, EXPLORER, SHDOCVW, SHELL32 and SHLWAPI.
The most frequent use that Microsoft has for the internal detail is a very obvious one whose purpose is indeed not accommodated (directly) by the functional interface: given a DPA, how many pointers are currently stored in it? The list represented by a DPA may be enumerated easily enough by calling DPA_GetPtr repeatedly, giving a successively higher index each time. If the list is known to be managed only in such a way that it cannot be sparse, then the first return of a NULL pointer indicates that all items have been seen, but otherwise, at what index should the enumeration stop? In version 4.71 and higher, DPA_EnumCallback can be called and the callbacks, one per item, even for NULL items, can be counted. Before then, no one exported function returns anything from which to know, though a limiting index can be determined in steps, e.g., by calling DPA_InsertPtr to append a dummy item, remembering the index that is returned, and calling DPA_DeletePtr to remove the dummy. It can only be good that Microsoft’s programmers avoid such clumsiness by going directly to the cpItems member of the DPA, but why not document it for everybody else?
Eventually, Microsoft did document this access that had long been enjoyed by Internet Explorer. The COMMCTRL.H from the Windows Vista SDK, dated January 2007, defines macros DPA_GetPtrCount and DPA_GetPtrPtr which give respectively the cpItems and pArray members, albeit through such obvious constructions as
#define DPA_GetPtrPtr(hdpa) (*((void * **)((BYTE *) (hdpa) + sizeof (void *))))
rather than defining members of the DPA structure. Perhaps everyone’s supposed to feel that this documentation is better late than never, however clumsy and however much the history is misrepresented: note that both macros are documented with “Windows Vista” as the minimum operating system, even though Microsoft had evidently been using something very like them for well over a decade.