/* * Dynamic pointer array (DPA) implementation * * Copyright 1998 Eric Kohl * 1998 Juergen Schmied <j.schmied@metronet.de> * 2000 Eric Kohl for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * * NOTES * These functions were involuntarily documented by Microsoft in 2002 as * the outcome of an anti-trust suit brought by various U.S. governments. * As a result the specifications on MSDN are inaccurate, incomplete * and misleading. A much more complete (unofficial) documentation is * available at: * * http://members.ozemail.com.au/~geoffch/samples/win32/shell/comctl32 */ #define COBJMACROS #include <stdarg.h> #include <limits.h> #include "windef.h" #include "winbase.h" #include "winuser.h" #include "commctrl.h" #include "objbase.h" #include "comctl32.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(dpa); struct _DPA { INT nItemCount; LPVOID *ptrs; HANDLE hHeap; INT nGrow; INT nMaxCount; }; typedef struct _STREAMDATA { DWORD dwSize; DWORD dwData2; DWORD dwItems; } STREAMDATA, *PSTREAMDATA; /************************************************************************** * DPA_LoadStream [COMCTL32.9] * * Loads a dynamic pointer array from a stream * * PARAMS * phDpa [O] pointer to a handle to a dynamic pointer array * loadProc [I] pointer to a callback function * pStream [I] pointer to a stream * pData [I] pointer to callback data * * RETURNS * Success: TRUE * Failure: FALSE * * NOTES * No more information available yet! */ HRESULT WINAPI DPA_LoadStream (HDPA *phDpa, PFNDPASTREAM loadProc, IStream *pStream, LPVOID pData) { HRESULT errCode; LARGE_INTEGER position; ULARGE_INTEGER newPosition; STREAMDATA streamData; DPASTREAMINFO streamInfo; ULONG ulRead; HDPA hDpa; PVOID *ptr; FIXME ("phDpa=%p loadProc=%p pStream=%p pData=%p\n", phDpa, loadProc, pStream, pData); if (!phDpa || !loadProc || !pStream) return E_INVALIDARG; *phDpa = NULL; position.QuadPart = 0; /* * Zero out our streamData */ memset(&streamData,0,sizeof(STREAMDATA)); errCode = IStream_Seek (pStream, position, STREAM_SEEK_CUR, &newPosition); if (errCode != S_OK) return errCode; errCode = IStream_Read (pStream, &streamData, sizeof(STREAMDATA), &ulRead); if (errCode != S_OK) return errCode; FIXME ("dwSize=%u dwData2=%u dwItems=%u\n", streamData.dwSize, streamData.dwData2, streamData.dwItems); if ( ulRead < sizeof(STREAMDATA) || (DWORD)pData < sizeof(STREAMDATA) || streamData.dwSize < sizeof(STREAMDATA) || streamData.dwData2 < 1) { errCode = E_FAIL; } if (streamData.dwItems > (UINT_MAX / 2 / sizeof(VOID*))) /* 536870911 */ return E_OUTOFMEMORY; /* create the dpa */ hDpa = DPA_Create (streamData.dwItems); if (!hDpa) return E_OUTOFMEMORY; if (!DPA_Grow (hDpa, streamData.dwItems)) return E_OUTOFMEMORY; /* load data from the stream into the dpa */ ptr = hDpa->ptrs; for (streamInfo.iPos = 0; streamInfo.iPos < streamData.dwItems; streamInfo.iPos++) { errCode = (loadProc)(&streamInfo, pStream, pData); if (errCode != S_OK) { errCode = S_FALSE; break; } *ptr = streamInfo.pvItem; ptr++; } /* set the number of items */ hDpa->nItemCount = streamInfo.iPos; /* store the handle to the dpa */ *phDpa = hDpa; FIXME ("new hDpa=%p, errorcode=%x\n", hDpa, errCode); return errCode; } /************************************************************************** * DPA_SaveStream [COMCTL32.10] * * Saves a dynamic pointer array to a stream * * PARAMS * hDpa [I] handle to a dynamic pointer array * loadProc [I] pointer to a callback function * pStream [I] pointer to a stream * pData [I] pointer to callback data * * RETURNS * Success: TRUE * Failure: FALSE * * NOTES * No more information available yet! */ HRESULT WINAPI DPA_SaveStream (const HDPA hDpa, PFNDPASTREAM loadProc, IStream *pStream, LPVOID pData) { FIXME ("hDpa=%p loadProc=%p pStream=%p pData=%p\n", hDpa, loadProc, pStream, pData); return E_FAIL; } /************************************************************************** * DPA_Merge [COMCTL32.11] * * Merge two dynamic pointers arrays. * * PARAMS * hdpa1 [I] handle to a dynamic pointer array * hdpa2 [I] handle to a dynamic pointer array * dwFlags [I] flags * pfnCompare [I] pointer to sort function * pfnMerge [I] pointer to merge function * lParam [I] application specific value * * RETURNS * Success: TRUE * Failure: FALSE * * NOTES * No more information available yet! */ BOOL WINAPI DPA_Merge (HDPA hdpa1, HDPA hdpa2, DWORD dwFlags, PFNDPACOMPARE pfnCompare, PFNDPAMERGE pfnMerge, LPARAM lParam) { INT nCount; LPVOID *pWork1, *pWork2; INT nResult, i; INT nIndex; TRACE("%p %p %08x %p %p %08lx)\n", hdpa1, hdpa2, dwFlags, pfnCompare, pfnMerge, lParam); if (IsBadWritePtr (hdpa1, sizeof(*hdpa1))) return FALSE; if (IsBadWritePtr (hdpa2, sizeof(*hdpa2))) return FALSE; if (IsBadCodePtr ((FARPROC)pfnCompare)) return FALSE; if (IsBadCodePtr ((FARPROC)pfnMerge)) return FALSE; if (!(dwFlags & DPAM_SORTED)) { TRACE("sorting dpa's!\n"); if (hdpa1->nItemCount > 0) DPA_Sort (hdpa1, pfnCompare, lParam); TRACE ("dpa 1 sorted!\n"); if (hdpa2->nItemCount > 0) DPA_Sort (hdpa2, pfnCompare, lParam); TRACE ("dpa 2 sorted!\n"); } if (hdpa2->nItemCount < 1) return TRUE; TRACE("hdpa1->nItemCount=%d hdpa2->nItemCount=%d\n", hdpa1->nItemCount, hdpa2->nItemCount); /* working but untrusted implementation */ pWork1 = &(hdpa1->ptrs[hdpa1->nItemCount - 1]); pWork2 = &(hdpa2->ptrs[hdpa2->nItemCount - 1]); nIndex = hdpa1->nItemCount - 1; nCount = hdpa2->nItemCount - 1; do { if (nIndex < 0) { if ((nCount >= 0) && (dwFlags & DPAM_UNION)) { /* Now insert the remaining new items into DPA 1 */ TRACE("%d items to be inserted at start of DPA 1\n", nCount+1); for (i=nCount; i>=0; i--) { PVOID ptr; ptr = (pfnMerge)(DPAMM_INSERT, *pWork2, NULL, lParam); if (!ptr) return FALSE; DPA_InsertPtr (hdpa1, 0, ptr); pWork2--; } } break; } nResult = (pfnCompare)(*pWork1, *pWork2, lParam); TRACE("compare result=%d, dpa1.cnt=%d, dpa2.cnt=%d\n", nResult, nIndex, nCount); if (nResult == 0) { PVOID ptr; ptr = (pfnMerge)(DPAMM_MERGE, *pWork1, *pWork2, lParam); if (!ptr) return FALSE; nCount--; pWork2--; *pWork1 = ptr; nIndex--; pWork1--; } else if (nResult > 0) { /* item in DPA 1 missing from DPA 2 */ if (dwFlags & DPAM_INTERSECT) { /* Now delete the extra item in DPA1 */ PVOID ptr; ptr = DPA_DeletePtr (hdpa1, nIndex); (pfnMerge)(DPAMM_DELETE, ptr, NULL, lParam); } nIndex--; pWork1--; } else { /* new item in DPA 2 */ if (dwFlags & DPAM_UNION) { /* Now insert the new item in DPA 1 */ PVOID ptr; ptr = (pfnMerge)(DPAMM_INSERT, *pWork2, NULL, lParam); if (!ptr) return FALSE; DPA_InsertPtr (hdpa1, nIndex+1, ptr); } nCount--; pWork2--; } } while (nCount >= 0); return TRUE; } /************************************************************************** * DPA_Destroy [COMCTL32.329] * * Destroys a dynamic pointer array * * PARAMS * hdpa [I] handle (pointer) to the pointer array * * RETURNS * Success: TRUE * Failure: FALSE */ BOOL WINAPI DPA_Destroy (const HDPA hdpa) { TRACE("(%p)\n", hdpa); if (!hdpa) return FALSE; if (hdpa->ptrs && (!HeapFree (hdpa->hHeap, 0, hdpa->ptrs))) return FALSE; return HeapFree (hdpa->hHeap, 0, hdpa); } /************************************************************************** * DPA_Grow [COMCTL32.330] * * Sets the growth amount. * * PARAMS * hdpa [I] handle (pointer) to the existing (source) pointer array * nGrow [I] number of items by which the array grows when it's too small * * RETURNS * Success: TRUE * Failure: FALSE */ BOOL WINAPI DPA_Grow (HDPA hdpa, INT nGrow) { TRACE("(%p %d)\n", hdpa, nGrow); if (!hdpa) return FALSE; hdpa->nGrow = max(8, nGrow); return TRUE; } /************************************************************************** * DPA_Clone [COMCTL32.331] * * Copies a pointer array to an other one or creates a copy * * PARAMS * hdpa [I] handle (pointer) to the existing (source) pointer array * hdpaNew [O] handle (pointer) to the destination pointer array * * RETURNS * Success: pointer to the destination pointer array. * Failure: NULL * * NOTES * - If the 'hdpaNew' is a NULL-Pointer, a copy of the source pointer * array will be created and it's handle (pointer) is returned. * - If 'hdpa' is a NULL-Pointer, the original implementation crashes, * this implementation just returns NULL. */ HDPA WINAPI DPA_Clone (const HDPA hdpa, const HDPA hdpaNew) { INT nNewItems, nSize; HDPA hdpaTemp; if (!hdpa) return NULL; TRACE("(%p %p)\n", hdpa, hdpaNew); if (!hdpaNew) { /* create a new DPA */ hdpaTemp = HeapAlloc (hdpa->hHeap, HEAP_ZERO_MEMORY, sizeof(*hdpaTemp)); hdpaTemp->hHeap = hdpa->hHeap; hdpaTemp->nGrow = hdpa->nGrow; } else hdpaTemp = hdpaNew; if (hdpaTemp->ptrs) { /* remove old pointer array */ HeapFree (hdpaTemp->hHeap, 0, hdpaTemp->ptrs); hdpaTemp->ptrs = NULL; hdpaTemp->nItemCount = 0; hdpaTemp->nMaxCount = 0; } /* create a new pointer array */ nNewItems = hdpaTemp->nGrow * (((hdpa->nItemCount - 1) / hdpaTemp->nGrow) + 1); nSize = nNewItems * sizeof(LPVOID); hdpaTemp->ptrs = HeapAlloc (hdpaTemp->hHeap, HEAP_ZERO_MEMORY, nSize); hdpaTemp->nMaxCount = nNewItems; /* clone the pointer array */ hdpaTemp->nItemCount = hdpa->nItemCount; memmove (hdpaTemp->ptrs, hdpa->ptrs, hdpaTemp->nItemCount * sizeof(LPVOID)); return hdpaTemp; } /************************************************************************** * DPA_GetPtr [COMCTL32.332] * * Retrieves a pointer from a dynamic pointer array * * PARAMS * hdpa [I] handle (pointer) to the pointer array * nIndex [I] array index of the desired pointer * * RETURNS * Success: pointer * Failure: NULL */ LPVOID WINAPI DPA_GetPtr (const HDPA hdpa, INT nIndex) { TRACE("(%p %d)\n", hdpa, nIndex); if (!hdpa) return NULL; if (!hdpa->ptrs) { WARN("no pointer array.\n"); return NULL; } if ((nIndex < 0) || (nIndex >= hdpa->nItemCount)) { WARN("not enough pointers in array (%d vs %d).\n",nIndex,hdpa->nItemCount); return NULL; } TRACE("-- %p\n", hdpa->ptrs[nIndex]); return hdpa->ptrs[nIndex]; } /************************************************************************** * DPA_GetPtrIndex [COMCTL32.333] * * Retrieves the index of the specified pointer * * PARAMS * hdpa [I] handle (pointer) to the pointer array * p [I] pointer * * RETURNS * Success: index of the specified pointer * Failure: -1 */ INT WINAPI DPA_GetPtrIndex (HDPA hdpa, LPCVOID p) { INT i; if (!hdpa || !hdpa->ptrs) return -1; for (i = 0; i < hdpa->nItemCount; i++) { if (hdpa->ptrs[i] == p) return i; } return -1; } /************************************************************************** * DPA_InsertPtr [COMCTL32.334] * * Inserts a pointer into a dynamic pointer array * * PARAMS * hdpa [I] handle (pointer) to the array * i [I] array index * p [I] pointer to insert * * RETURNS * Success: index of the inserted pointer * Failure: -1 */ INT WINAPI DPA_InsertPtr (const HDPA hdpa, INT i, LPVOID p) { TRACE("(%p %d %p)\n", hdpa, i, p); if (!hdpa || i < 0) return -1; /* append item if index is out of bounds */ i = min(hdpa->nItemCount, i); /* create empty spot at the end */ if (!DPA_SetPtr(hdpa, hdpa->nItemCount, 0)) return -1; if (i != hdpa->nItemCount - 1) memmove (hdpa->ptrs + i + 1, hdpa->ptrs + i, (hdpa->nItemCount - i - 1) * sizeof(LPVOID)); hdpa->ptrs[i] = p; return i; } /************************************************************************** * DPA_SetPtr [COMCTL32.335] * * Sets a pointer in the pointer array * * PARAMS * hdpa [I] handle (pointer) to the pointer array * i [I] index of the pointer that will be set * p [I] pointer to be set * * RETURNS * Success: TRUE * Failure: FALSE */ BOOL WINAPI DPA_SetPtr (const HDPA hdpa, INT i, LPVOID p) { LPVOID *lpTemp; TRACE("(%p %d %p)\n", hdpa, i, p); if (!hdpa || i < 0) return FALSE; if (hdpa->nItemCount <= i) { /* within the old array */ if (hdpa->nMaxCount <= i) { /* resize the block of memory */ INT nNewItems = hdpa->nGrow * ((((i+1) - 1) / hdpa->nGrow) + 1); INT nSize = nNewItems * sizeof(LPVOID); if (hdpa->ptrs) lpTemp = HeapReAlloc (hdpa->hHeap, HEAP_ZERO_MEMORY, hdpa->ptrs, nSize); else lpTemp = HeapAlloc (hdpa->hHeap, HEAP_ZERO_MEMORY, nSize); if (!lpTemp) return FALSE; hdpa->nMaxCount = nNewItems; hdpa->ptrs = lpTemp; } hdpa->nItemCount = i+1; } /* put the new entry in */ hdpa->ptrs[i] = p; return TRUE; } /************************************************************************** * DPA_DeletePtr [COMCTL32.336] * * Removes a pointer from the pointer array. * * PARAMS * hdpa [I] handle (pointer) to the pointer array * i [I] index of the pointer that will be deleted * * RETURNS * Success: deleted pointer * Failure: NULL */ LPVOID WINAPI DPA_DeletePtr (const HDPA hdpa, INT i) { LPVOID *lpDest, *lpSrc, lpTemp = NULL; INT nSize; TRACE("(%p %d)\n", hdpa, i); if ((!hdpa) || i < 0 || i >= hdpa->nItemCount) return NULL; lpTemp = hdpa->ptrs[i]; /* do we need to move ?*/ if (i < hdpa->nItemCount - 1) { lpDest = hdpa->ptrs + i; lpSrc = lpDest + 1; nSize = (hdpa->nItemCount - i - 1) * sizeof(LPVOID); TRACE("-- move dest=%p src=%p size=%x\n", lpDest, lpSrc, nSize); memmove (lpDest, lpSrc, nSize); } hdpa->nItemCount --; /* free memory ?*/ if ((hdpa->nMaxCount - hdpa->nItemCount) >= hdpa->nGrow) { INT nNewItems = max(hdpa->nGrow * 2, hdpa->nItemCount); nSize = nNewItems * sizeof(LPVOID); lpDest = HeapReAlloc (hdpa->hHeap, HEAP_ZERO_MEMORY, hdpa->ptrs, nSize); if (!lpDest) return NULL; hdpa->nMaxCount = nNewItems; hdpa->ptrs = lpDest; } return lpTemp; } /************************************************************************** * DPA_DeleteAllPtrs [COMCTL32.337] * * Removes all pointers and reinitializes the array. * * PARAMS * hdpa [I] handle (pointer) to the pointer array * * RETURNS * Success: TRUE * Failure: FALSE */ BOOL WINAPI DPA_DeleteAllPtrs (const HDPA hdpa) { TRACE("(%p)\n", hdpa); if (!hdpa) return FALSE; if (hdpa->ptrs && (!HeapFree (hdpa->hHeap, 0, hdpa->ptrs))) return FALSE; hdpa->nItemCount = 0; hdpa->nMaxCount = hdpa->nGrow * 2; hdpa->ptrs = HeapAlloc (hdpa->hHeap, HEAP_ZERO_MEMORY, hdpa->nMaxCount * sizeof(LPVOID)); return TRUE; } /************************************************************************** * DPA_QuickSort [Internal] * * Ordinary quicksort (used by DPA_Sort). * * PARAMS * lpPtrs [I] pointer to the pointer array * l [I] index of the "left border" of the partition * r [I] index of the "right border" of the partition * pfnCompare [I] pointer to the compare function * lParam [I] user defined value (3rd parameter in compare function) * * RETURNS * NONE */ static VOID DPA_QuickSort (LPVOID *lpPtrs, INT l, INT r, PFNDPACOMPARE pfnCompare, LPARAM lParam) { INT m; LPVOID t; TRACE("l=%i r=%i\n", l, r); if (l==r) /* one element is always sorted */ return; if (r<l) /* oops, got it in the wrong order */ { DPA_QuickSort(lpPtrs, r, l, pfnCompare, lParam); return; } m = (l+r)/2; /* divide by two */ DPA_QuickSort(lpPtrs, l, m, pfnCompare, lParam); DPA_QuickSort(lpPtrs, m+1, r, pfnCompare, lParam); /* join the two sides */ while( (l<=m) && (m<r) ) { if(pfnCompare(lpPtrs[l],lpPtrs[m+1],lParam)>0) { t = lpPtrs[m+1]; memmove(&lpPtrs[l+1],&lpPtrs[l],(m-l+1)*sizeof(lpPtrs[l])); lpPtrs[l] = t; m++; } l++; } } /************************************************************************** * DPA_Sort [COMCTL32.338] * * Sorts a pointer array using a user defined compare function * * PARAMS * hdpa [I] handle (pointer) to the pointer array * pfnCompare [I] pointer to the compare function * lParam [I] user defined value (3rd parameter of compare function) * * RETURNS * Success: TRUE * Failure: FALSE */ BOOL WINAPI DPA_Sort (const HDPA hdpa, PFNDPACOMPARE pfnCompare, LPARAM lParam) { if (!hdpa || !pfnCompare) return FALSE; TRACE("(%p %p 0x%lx)\n", hdpa, pfnCompare, lParam); if ((hdpa->nItemCount > 1) && (hdpa->ptrs)) DPA_QuickSort (hdpa->ptrs, 0, hdpa->nItemCount - 1, pfnCompare, lParam); return TRUE; } /************************************************************************** * DPA_Search [COMCTL32.339] * * Searches a pointer array for a specified pointer * * PARAMS * hdpa [I] handle (pointer) to the pointer array * pFind [I] pointer to search for * nStart [I] start index * pfnCompare [I] pointer to the compare function * lParam [I] user defined value (3rd parameter of compare function) * uOptions [I] search options * * RETURNS * Success: index of the pointer in the array. * Failure: -1 */ INT WINAPI DPA_Search (const HDPA hdpa, LPVOID pFind, INT nStart, PFNDPACOMPARE pfnCompare, LPARAM lParam, UINT uOptions) { if (!hdpa || !pfnCompare || !pFind) return -1; TRACE("(%p %p %d %p 0x%08lx 0x%08x)\n", hdpa, pFind, nStart, pfnCompare, lParam, uOptions); if (uOptions & DPAS_SORTED) { /* array is sorted --> use binary search */ INT l, r, x, n; LPVOID *lpPtr; /* for binary search ignore start index */ l = 0; r = hdpa->nItemCount - 1; lpPtr = hdpa->ptrs; while (r >= l) { x = (l + r) / 2; n = (pfnCompare)(pFind, lpPtr[x], lParam); if (n == 0) return x; else if (n < 0) r = x - 1; else /* (n > 0) */ l = x + 1; } if (uOptions & (DPAS_INSERTBEFORE|DPAS_INSERTAFTER)) return l; } else { /* array is not sorted --> use linear search */ LPVOID *lpPtr; INT nIndex; nIndex = (nStart == -1)? 0 : nStart; lpPtr = hdpa->ptrs; for (; nIndex < hdpa->nItemCount; nIndex++) { if ((pfnCompare)(pFind, lpPtr[nIndex], lParam) == 0) return nIndex; } } return -1; } /************************************************************************** * DPA_CreateEx [COMCTL32.340] * * Creates a dynamic pointer array using the specified size and heap. * * PARAMS * nGrow [I] number of items by which the array grows when it is filled * hHeap [I] handle to the heap where the array is stored * * RETURNS * Success: handle (pointer) to the pointer array. * Failure: NULL * * NOTES * The DPA_ functions can be used to create and manipulate arrays of * pointers. */ HDPA WINAPI DPA_CreateEx (INT nGrow, HANDLE hHeap) { HDPA hdpa; TRACE("(%d %p)\n", nGrow, hHeap); if (hHeap) hdpa = HeapAlloc (hHeap, HEAP_ZERO_MEMORY, sizeof(*hdpa)); else hdpa = Alloc (sizeof(*hdpa)); if (hdpa) { hdpa->nGrow = max(8, nGrow); hdpa->hHeap = hHeap ? hHeap : GetProcessHeap(); hdpa->nMaxCount = hdpa->nGrow * 2; hdpa->ptrs = HeapAlloc (hdpa->hHeap, HEAP_ZERO_MEMORY, hdpa->nMaxCount * sizeof(LPVOID)); } TRACE("-- %p\n", hdpa); return hdpa; } /************************************************************************** * DPA_Create [COMCTL32.328] * * Creates a dynamic pointer array. * * PARAMS * nGrow [I] number of items by which the array grows when it is filled * * RETURNS * Success: handle (pointer) to the pointer array. * Failure: NULL * * NOTES * The DPA_ functions can be used to create and manipulate arrays of * pointers. */ HDPA WINAPI DPA_Create (INT nGrow) { return DPA_CreateEx( nGrow, 0 ); } /************************************************************************** * DPA_EnumCallback [COMCTL32.385] * * Enumerates all items in a dynamic pointer array. * * PARAMS * hdpa [I] handle to the dynamic pointer array * enumProc [I] * lParam [I] * * RETURNS * none */ VOID WINAPI DPA_EnumCallback (HDPA hdpa, PFNDPAENUMCALLBACK enumProc, LPVOID lParam) { INT i; TRACE("(%p %p %p)\n", hdpa, enumProc, lParam); if (!hdpa) return; if (hdpa->nItemCount <= 0) return; for (i = 0; i < hdpa->nItemCount; i++) { if ((enumProc)(hdpa->ptrs[i], lParam) == 0) return; } return; } /************************************************************************** * DPA_DestroyCallback [COMCTL32.386] * * Enumerates all items in a dynamic pointer array and destroys it. * * PARAMS * hdpa [I] handle to the dynamic pointer array * enumProc [I] * lParam [I] * * RETURNS * none */ void WINAPI DPA_DestroyCallback (HDPA hdpa, PFNDPAENUMCALLBACK enumProc, LPVOID lParam) { TRACE("(%p %p %p)\n", hdpa, enumProc, lParam); DPA_EnumCallback (hdpa, enumProc, lParam); DPA_Destroy (hdpa); }