/***************************************************************************** * * DISubCls.c * * Copyright (c) 1996 Microsoft Corporation. All Rights Reserved. * * Abstract: * * "Safe subclassing" code, stolen from comctl32. * * Originally written by francish. Stolen by raymondc. * * Contents: * * SetWindowSubclass * GetWindowSubclass * RemoveWindowSubclass * DefSubclassProc * *****************************************************************************/ #include "dinputpr.h" /***************************************************************************** * * The sqiffle for this file. * *****************************************************************************/ #define sqfl sqflSubclass /***************************************************************************** * * @doc INTERNAL * * @topic DirectInput Subclassing | * * * This module defines helper functions that make subclassing windows safe(er) * and easy(er). The code maintains a single property on the subclassed window * and dispatches various "subclass callbacks" to its clients a required. The * client is provided reference data and a simple "default processing" API. * * Semantics: * A "subclass callback" is identified by a unique pairing of a callback * function pointer and an unsigned ID value. Each callback can also store a * single DWORD of reference data, which is passed to the callback function * when it is called to filter messages. No reference counting is performed * for the callback, it may repeatedly call the SetWindowSubclass API to alter * the value of its reference data element as desired. * *****************************************************************************/ /***************************************************************************** * * @doc INTERNAL * * @struct SUBCLASS_CALL | * * Structure which tracks a single subclassing client. * * Although a linked list would have made the code slightly * simpler, this module uses a packed callback array to avoid * unneccessary fragmentation. * * @field SUBCLASSPROC | pfnSubclass | * * The subclass procedure. If this is zero, it means that * the node is dying and should be ignored. * * @field UINT | uIdSubclass | * * Unique subclass identifier. * * @field DWORD | dwRefData | * * Optional reference data for subclass procedure. * *****************************************************************************/ typedef struct SUBCLASS_CALL { SUBCLASSPROC pfnSubclass; UINT_PTR uIdSubclass; ULONG_PTR dwRefData; } SUBCLASS_CALL, *PSUBCLASS_CALL; /***************************************************************************** * * @doc INTERNAL * * @struct SUBCLASS_FRAME | * * Structure which tracks the state of an active call to the * window's window procedure. * * Each time the window procedure is entered, we create a new * , which remains active until the last * subclass procedure returns, at which point the frame is * torn down. * * The subclass frames are stored on the stack. So walking * the frame chain causes you to wander through the stack. * * @field UINT | uCallIndex | * * Index of next callback to call. * * @field UINT | uDeepestCall | * * Deepest on the stack. * * @field SUBCLASS_FRAME * | pFramePrev | * * The previous subclass frame. * * @field PSUBCLASS_HEADER | pHeader | * * The header associated with this frame. * *****************************************************************************/ typedef struct SUBCLASS_FRAME { UINT uCallIndex; UINT uDeepestCall; struct SUBCLASS_FRAME *pFramePrev; struct SUBCLASS_HEADER *pHeader; } SUBCLASS_FRAME, *PSUBCLASS_FRAME; /***************************************************************************** * * @doc INTERNAL * * @struct SUBCLASS_HEADER | * * Structure which tracks the subclass goo associated with * a window. A pointer to this structure is kept in a private * window property. * * @field UINT | uRefs | * * Subclass count. This is the number of valid entries * in the

. * * @field UINT | uAlloc | * * Number of allocated nodes in the array. * * @field UINT | uCleanup | * * Index of the call node to clean up. * * @field WORD | dwThreadId | * * Thread id of the window with which the structure is associated. * * @field PSUBCLASS_FRAME | pFrameCur | * * Pointer to the current subclass frame. * * @field SUBCLASS_CALL | CallArray[1] | * * Base of the packed call node array. * *****************************************************************************/ typedef struct SUBCLASS_HEADER { UINT uRefs; UINT uAlloc; UINT uCleanup; DWORD dwThreadId; PSUBCLASS_FRAME pFrameCur; SUBCLASS_CALL CallArray[1]; } SUBCLASS_HEADER, *PSUBCLASS_HEADER; #define CALLBACK_ALLOC_GRAIN (3) /* 1 defproc, 1 subclass, 1 spare */ LRESULT CALLBACK MasterSubclassProc(HWND hwnd, UINT wm, WPARAM wp, LPARAM lp); LRESULT INTERNAL CallNextSubclassProc(PSUBCLASS_HEADER pHeader, HWND hwnd, UINT wm, WPARAM wp, LPARAM lp); /***************************************************************************** * * @doc INTERNAL * * @func LRESULT | SubclassDeath | * * This function is called if we ever enter one of our subclassing * procedures without our reference data (and hence without the * previous ). * * Hitting this represents a catastrophic failure in the * subclass code. * * The function resets the of the window to * to avoid faulting. * * @parm HWND | hwnd | * * Window that just got hosed. * * @parm UINT | wm | * * Window message that caused us to realize that we are hosed. * * @parm WPARAM | wp | * * Meaning depends on window message. * * @parm LPARAM | lp | * * Meaning depends on window message. * *****************************************************************************/ LRESULT INTERNAL SubclassDeath(HWND hwnd, UINT wm, WPARAM wp, LPARAM lp) { /* * WE SHOULD NEVER EVER GET HERE */ // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl | sqflError, TEXT("Fatal! SubclassDeath in window %p"), hwnd); AssertF(0); /* * We call the outside world, so we'd better not have the critsec. */ AssertF(!InCrit()); /* * In theory, we could save the original WNDPROC in a separate property, * but that just wastes memory for something that should never happen. */ #ifdef WINNT SetWindowLongPtr( hwnd, GWLP_WNDPROC, (LONG_PTR)(DefWindowProc)); #else SubclassWindow(hwnd, DefWindowProc); #endif return DefWindowProc(hwnd, wm, wp, lp); } /***************************************************************************** * * @doc INTERNAL * * @func WNDPROC | GetWindowProc | * * Returns the of the specified window. * * @parm HWND | hwnd | * * Window to be inspected. * * @returns * * The of the specified window. * *****************************************************************************/ WNDPROC INLINE GetWindowProc(HWND hwnd) { #ifdef WINNT return (WNDPROC)GetWindowLongPtr(hwnd, GWLP_WNDPROC); #else return (WNDPROC)GetWindowLong(hwnd, GWL_WNDPROC); #endif } /***************************************************************************** * * @doc INTERNAL * * @global ATOM | g_atmDISubclass | * * This is the global we use to store our * property on whatever windows come our way. * * If the

symbol is defined, then we will work * around a bug in Windows 95 where Windows "helpfully" * 's all properties that are on a window * when the window dies. See Francis's original explanation * in subclass.c. * *****************************************************************************/ #pragma BEGIN_CONST_DATA TCHAR c_tszDISubclass[] = TEXT("DirectInputSubclassInfo"); #pragma END_CONST_DATA #ifdef WIN95_HACK ATOM g_atmDISubclass; #endif /***************************************************************************** * * @doc INTERNAL * * @func PSUBCLASS_HEADER | FastGetSubclassHeader | * * Obtains the for the specified window. * * This function succeeds on any thread, although the value * is meaningless from the wrong process. * * @parm HWND | hwnd | * * Window in question. * * @returns * * Pointer to the associated with the window, * or if the window is not subclassed by us. * *****************************************************************************/ PSUBCLASS_HEADER INLINE FastGetSubclassHeader(HWND hwnd) { #ifdef WIN95_HACK /* * The right thing happens if g_atmDISubclass is 0, namely, * the property is not found. Unfortunately, NT RIPs when * you do this, so we'll be polite and not RIP. */ if (g_atmDISubclass) { return (PSUBCLASS_HEADER)GetProp(hWnd, (PV)g_atmDISubclass); } else { return 0; } #else return (PSUBCLASS_HEADER)GetProp(hwnd, c_tszDISubclass); #endif } /***************************************************************************** * * @doc INTERNAL * * @func PSUBCLASS_HEADER | GetSubclassHeader | * * Obtains the for the specified window. * It fails if the caller is in the wrong process, but will * allow the caller to get the header from a different thread. * * @parm HWND | hwnd | * * Window in question. * * @returns * * Pointer to the associated with the window, * or if the window is not subclass by us yet, or 1 * if it belongs to another process. * *****************************************************************************/ PSUBCLASS_HEADER INTERNAL GetSubclassHeader(HWND hwnd) { DWORD idProcess; /* * Make sure we're in the right process. * * Must use our helper function to catch bad scenarios like * the goofy Windows 95 console window which lies about its * owner. */ idProcess = GetWindowPid(hwnd); if (idProcess == GetCurrentProcessId()) { /* In the right process */ return FastGetSubclassHeader(hwnd); } else { if (idProcess) { // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl | sqflError, TEXT("XxxWindowSubclass: ") TEXT("wrong process for window %p"), hwnd); } return (PSUBCLASS_HEADER)1; } } /***************************************************************************** * * @doc INTERNAL * * @func BOOL | SetSubclassHeader | * * Sets the for the specified window. * * @parm HWND | hwnd | * * Window in question. * * @parm PSUBCLASS_HEADER | pHeader | * * The value to set. * * @parm PSUBCLASS_FRAME | pFrameFixup | * * The active frames, which need to be walked and fixed up * to refer to the new . * *****************************************************************************/ BOOL INTERNAL SetSubclassHeader(HWND hwnd, PSUBCLASS_HEADER pHeader, PSUBCLASS_FRAME pFrameFixup) { BOOL fRc; AssertF(InCrit()); /* We are partying on the header and frame list */ #ifdef WIN95_HACK if (g_atmDISubclass == 0) { ATOM atm; /* * HACK: we are intentionally incrementing the refcount on this atom * WE DO NOT WANT IT TO GO BACK DOWN so we will not delete it in * process detach (see comments for g_atmDISubclass in subclass.c * for more info). */ atm = GlobalAddAtom(c_tszDISubclass); if (atm) { g_atmDISubclass = atm; /* In case the old atom got nuked */ } } #endif /* * Update the frame list if required. */ while (pFrameFixup) { pFrameFixup->pHeader = pHeader; pFrameFixup = pFrameFixup->pFramePrev; } /* * If we have a window to update, then update/remove the property * as required. */ if (hwnd) { if (!pHeader) { #ifdef WIN95_HACK /* * HACK: we remove with an ATOM so the refcount won't drop * (see comments for g_atmDISubclass above) */ RemoveProp(hwnd, (PV)g_atmDISubclass); #else // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl, TEXT("SetSubclassHeader: Removing %p"), pHeader); RemoveProp(hwnd, c_tszDISubclass); #endif fRc = 1; } else { #ifdef WIN95_HACK /* * HACK: we add using a STRING so the refcount will go up * (see comments for g_atmDISubclass above) */ #endif // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl, TEXT("SetSubclassHeader: Adding %p"), pHeader); fRc = SetProp(hwnd, c_tszDISubclass, pHeader); if (!fRc) { // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl | sqflError, TEXT("SetWindowSubclass: ") TEXT("couldn't subclass window %p"), hwnd); } } } else { fRc = 1; /* Weird vacuous success */ } return fRc; } /***************************************************************************** * * @doc INTERNAL * * @func void | FreeSubclassHeader | * * Toss the subclass header for the specified window. * * @parm HWND | hwnd | * * Window in question. * * @parm PSUBCLASS_HEADER | pHeader | * * The value being tossed. * *****************************************************************************/ void INTERNAL FreeSubclassHeader(HWND hwnd, PSUBCLASS_HEADER pHeader) { AssertF(InCrit()); /* we will be removing the subclass header */ /* * Sanity checking... */ if (pHeader) { // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl, TEXT("FreeSubclassHeader: Freeing %p"), pHeader); SetSubclassHeader(hwnd, 0, pHeader->pFrameCur); /* Clean up the header */ LocalFree(pHeader); } else { AssertF(0); } } /***************************************************************************** * * @doc INTERNAL * * @func void | ReallocSubclassHeader | * * Change the size of the subclass header as indicated. * * @parm HWND | hwnd | * * Window in question. * * @parm PSUBCLASS_HEADER | pHeader | * * The current header. * * @parm UINT | uCallbacks | * * Desired size. * *****************************************************************************/ PSUBCLASS_HEADER INTERNAL ReAllocSubclassHeader(HWND hwnd, PSUBCLASS_HEADER pHeader, UINT uCallbacks) { UINT uAlloc; AssertF(InCrit()); /* we will be replacing the subclass header */ /* * Granularize the allocation. */ uAlloc = CALLBACK_ALLOC_GRAIN * ((uCallbacks + CALLBACK_ALLOC_GRAIN - 1) / CALLBACK_ALLOC_GRAIN); /* * Do we need to change the allocation? */ if (!pHeader || (uAlloc != pHeader->uAlloc)) { /* * compute bytes required */ uCallbacks = uAlloc * sizeof(SUBCLASS_CALL) + sizeof(SUBCLASS_HEADER); /* * And try to alloc / realloc. */ if (SUCCEEDED(ReallocCbPpv(uCallbacks, &pHeader))) { /* * Update info. */ pHeader->uAlloc = uAlloc; if (SetSubclassHeader(hwnd, pHeader, pHeader->pFrameCur)) { } else { FreeSubclassHeader(hwnd, pHeader); pHeader = 0; } } else { pHeader = 0; } } AssertF(pHeader); return pHeader; } /***************************************************************************** * * @doc INTERNAL * * @func LRESULT | CallOriginalWndProc | * * This procedure is the default which is always * installed when we subclass a window. The original window * procedure is installed as the reference data for this * callback. It simply calls the original and * returns its result. * * @parm HWND | hwnd | * * Window in question. * * @parm UINT | wm | * * Window message that needs to go to the original . * * @parm WPARAM | wp | * * Meaning depends on window message. * * @parm LPARAM | lp | * * Meaning depends on window message. * * @parm UINT | uIdSubclass | * * ID number (not used). * * @parm DWORD | dwRefData | * * Reference data for subclass procedure (original ). * *****************************************************************************/ LRESULT CALLBACK CallOriginalWndProc(HWND hwnd, UINT wm, WPARAM wp, LPARAM lp, UINT_PTR uIdSubclass, ULONG_PTR dwRefData) { /* * dwRefData should be the original window procedure */ AssertF(dwRefData); /* * and call it. */ return CallWindowProc((WNDPROC)dwRefData, hwnd, wm, wp, lp); } /***************************************************************************** * * @doc INTERNAL * * @func PSUBCLASS_HEADER | AttachSubclassHeader | * * This procedure makes sure that a given window is subclassed by us. * It maintains a reference count on the data structures associated * with our subclass. if the window is not yet subclassed by us * then this procedure installs our subclass procedure and * associated data structures. * * @parm HWND | hwnd | * * Window in question. * *****************************************************************************/ PSUBCLASS_HEADER INTERNAL AttachSubclassHeader(HWND hwnd) { PSUBCLASS_HEADER pHeader; /* * We party on the subclass call chain here */ AssertF(InCrit()); /* * Yes, we subclass the window out of context, but we are careful * to avoid race conditions. There is still a problem if some * other DLL tries to un-subclass a window just as we are subclassing * it. But there's nothing you can do about it, and besides, * what are the odds...? */ /* * If haven't already subclassed the window then do it now */ pHeader = GetSubclassHeader(hwnd); if( pHeader == (PSUBCLASS_HEADER)1 ) { /* * It's all gone horribly wrong. * This can happen when the application uses joyXXX functions in Winmm.dll. */ pHeader = 0; } else if (pHeader == 0) { /* * attach our header data to the window * we need space for two callbacks: * the subclass and the original proc */ pHeader = ReAllocSubclassHeader(hwnd, 0, 2); if (pHeader) { SUBCLASS_CALL *pCall; /* * Set up the first node in the array to call * the original WNDPROC. Do this before subclassing * to avoid a race if the window receives a message * after we have installed our subclass but before * we can save the original WNDPROC. */ AssertF(pHeader->uAlloc); pCall = pHeader->CallArray; pCall->pfnSubclass = CallOriginalWndProc; pCall->uIdSubclass = 0; pCall->dwRefData = (ULONG_PTR)GetWindowProc(hwnd); /* * init our subclass refcount... */ pHeader->uRefs = 1; pHeader->dwThreadId = GetWindowThreadProcessId(hwnd, NULL); /* * Super-paranoid. We must must not race with another * instance of ourselves trying to un-subclass. */ AssertF(InCrit()); /* * Save the new "old" wndproc in case we raced with * somebody else trying to subclass. */ #ifdef WINNT pCall->dwRefData = (ULONG_PTR)SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)MasterSubclassProc); #else pCall->dwRefData = (DWORD)SubclassWindow(hwnd, MasterSubclassProc); #endif if (pCall->dwRefData) { DllLoadLibrary(); /* Make sure we don't get unloaded */ } else { /* clean up and get out */ FreeSubclassHeader(hwnd, pHeader); pHeader = 0; } } } return pHeader; } /***************************************************************************** * * @doc INTERNAL * * @func void | DetachSubclassHeader | * * This procedure attempts to detach the subclass header from * the specified window. * * @parm HWND | hwnd | * * Window in question. * * @parm PSUBCLASS_HEADER | pHeader | * * Header to detach. * * @parm BOOL | fForce | * * Nonzero if we should detach even if we are not the top-level * subclass. * *****************************************************************************/ void INTERNAL DetachSubclassHeader(HWND hwnd, PSUBCLASS_HEADER pHeader, BOOL fForce) { WNDPROC wndprocOld; AssertF(InCrit()); /* we party on the subclass call chain here */ AssertF(pHeader); /* fear */ /* * If we are not being forced to remove and the window is still * valid then sniff around a little and decide if it's a good * idea to detach now. */ if (!fForce && hwnd) { AssertF(pHeader == FastGetSubclassHeader(hwnd)); /* paranoia */ /* should always have the "call original" node */ AssertF(pHeader->uRefs); /* * We can't have active clients. * We can't have people still on our stack. */ if (pHeader->uRefs <= 1 && !pHeader->pFrameCur) { /* * We must be in the correct context. */ if (pHeader->dwThreadId == GetCurrentThreadId()) { /* * We kept the original window procedure as refdata for our * CallOriginalWndProc subclass callback. */ wndprocOld = (WNDPROC)pHeader->CallArray[0].dwRefData; AssertF(wndprocOld); /* * Make sure we are the top of the subclass chain. */ if (GetWindowProc(hwnd) == MasterSubclassProc) { /* * go ahead and try to detach */ #ifdef WINNT if (SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)wndprocOld)) { #else if (SubclassWindow(hwnd, wndprocOld)) { #endif SquirtSqflPtszV(sqfl, TEXT("DetachSubclassHeader: ") TEXT("Unhooked")); } else { AssertF(0); /* just plain shouldn't happen */ goto failed; } } else { /* Not at top of chain; can't do it */ SquirtSqflPtszV(sqfl, TEXT("DetachSubclassHeader: ") TEXT("Somebody else subclassed")); goto failed; } } else { /* Out of context. Try again later. */ SendNotifyMessage(hwnd, WM_NULL, 0, 0L); goto failed; } } else { // 7/18/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl, TEXT("DetachSubclassHeader: ") TEXT("Still %d users, %p frame"), pHeader->uRefs, pHeader->pFrameCur); goto failed; } } #if 0 #ifdef DEBUG { /* * warn about anybody who hasn't unhooked yet */ UINT uCur; SUBCLASS_CALL *pCall; uCur = pHeader->uRefs; pCall = pHeader->CallArray + uCur; /* don't complain about our 'call original' node */ while (--uCur) { pCall--; if (pCall->pfnSubclass) { /* * always warn about these they could be leaks */ // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl | sqflError, TEXT("warning: orphan subclass: ") TEXT("fn %p, id %08x, dw %08x"), pCall->pfnSubclass, pCall->uIdSubclass, pCall->dwRefData); } } } #endif #endif /* * free the header now */ FreeSubclassHeader(hwnd, pHeader); DllFreeLibrary(); /* Undo LoadLibrary when we hooked */ failed:; } /***************************************************************************** * * @doc INTERNAL * * @func void | PurgeSingleCallNode | * * Purges a single dead node in the call array. * * @parm HWND | hwnd | * * Window in question. * * @parm PSUBCLASS_HEADER | pHeader | * * The header associated with the window. * The

field is the index of the node being * cleaned up. * *****************************************************************************/ void INTERNAL PurgeSingleCallNode(HWND hwnd, PSUBCLASS_HEADER pHeader) { AssertF(InCrit()); /* we will try to re-arrange the call array */ if (pHeader->uCleanup) {/* Sanity check */ UINT uRemain; SquirtSqflPtszV(sqfl, TEXT("PurgeSingleCallNode: Purging number %d"), pHeader->uCleanup); /* * and a little paranoia */ AssertF(pHeader->CallArray[pHeader->uCleanup].pfnSubclass == 0); AssertF(fLimpFF(pHeader->pFrameCur, pHeader->uCleanup < pHeader->pFrameCur->uDeepestCall)); /* * are there any call nodes above the one we're about to remove? */ uRemain = pHeader->uRefs - pHeader->uCleanup; if (uRemain > 0) { /* * yup, need to fix up the array the hard way */ SUBCLASS_CALL *pCall; SUBCLASS_FRAME *pFrame; UINT uCur, uMax; /* * move the remaining nodes down into the empty space */ pCall = pHeader->CallArray + pHeader->uCleanup; /* * Since the souce and destination overlap (unless there's only * one node remaining) the behavior of memcpy is undefined. * memmove (aka MoveMemory) would guarantee the correct * behavior but requires the runtime library. * Since this is the only function we require in retail from the * RTL, it is not worth the 22% bloat we gain from using the * static version and using the dynamic version is a load time * and redist test hit. So copy the array one element at a time. */ for( uCur = 0; uCur < uRemain; uCur++ ) { memcpy( &pCall[uCur], &pCall[uCur+1], sizeof(*pCall) ); } /* * update the call indices of any active frames */ uCur = pHeader->uCleanup; pFrame = pHeader->pFrameCur; while (pFrame) { if (pFrame->uCallIndex >= uCur) { pFrame->uCallIndex--; if (pFrame->uDeepestCall >= uCur) { pFrame->uDeepestCall--; } } pFrame = pFrame->pFramePrev; } /* * now search for any other dead call nodes in the remaining area */ uMax = pHeader->uRefs - 1; /* we haven't decremented uRefs yet */ while (uCur < uMax && pCall->pfnSubclass) { pCall++; uCur++; } pHeader->uCleanup = (uCur < uMax) ? uCur : 0; } else { /* * No call nodes above. This case is easy. */ pHeader->uCleanup = 0; } /* * finally, decrement the client count */ pHeader->uRefs--; SquirtSqflPtszV(sqfl, TEXT("warning: PurgeSingleCallNode: ") TEXT("Still %d refs"), pHeader->uRefs); } else { AssertF(0); /* Nothing to do! */ } } /***************************************************************************** * * @doc INTERNAL * * @func void | CompactSubclassHeader | * * Attempts to compact the subclass array, freeing the * subclass header if the array is empty. * * @parm HWND | hwnd | * * Window in question. * * @parm PSUBCLASS_HEADER | pHeader | * * The header associated with the window. * *****************************************************************************/ void INTERNAL CompactSubclassHeader(HWND hwnd, PSUBCLASS_HEADER pHeader) { AssertF(InCrit()); /* we will try to re-arrange the call array */ /* * we must handle the "window destroyed unexpectedly during callback" case */ if (hwnd) { /* * Clean out as many dead callbacks as possible. * * The "DeepestCall" test is an optimization so we don't go * purging call nodes when no active frame cares. * * (I'm not entirely conviced of this. I mean, we have to * purge it eventually anyway, right?) */ while (pHeader->uCleanup && fLimpFF(pHeader->pFrameCur, pHeader->uCleanup < pHeader->pFrameCur->uDeepestCall)) { PurgeSingleCallNode(hwnd, pHeader); } /* * do we still have clients? */ if (pHeader->uRefs > 1) { SquirtSqflPtszV(sqfl, TEXT("CompactSubclassHeader: ") TEXT("Still %d users"), pHeader->uRefs); /* * yes, shrink our allocation, leaving room for at least one client */ ReAllocSubclassHeader(hwnd, pHeader, pHeader->uRefs + 1); goto done; } } /* * There are no clients left, or the window is gone. * Try to detach and free */ DetachSubclassHeader(hwnd, pHeader, FALSE); done:; } /***************************************************************************** * * @doc INTERNAL * * @func PSUBCLASS_CALL | FindCallRecord | * * Searches for a call record with the specified subclass proc * and id, and returns its address. If no such call record is * found then NULL is returned. * * This is a helper function used when we need to track down * a callback because the client is changing its refdata or * removing it. * * @parm PSUBCLASS_HEADER | pHeader | * * The header in which to search. * * @parm SUBCLASSPROC | pfnSubclass | * * Subclass callback procedure to locate. * * @parm UINT | uIdSubclass | * * Instance identifier associated with the callback. * *****************************************************************************/ SUBCLASS_CALL * INTERNAL FindCallRecord(PSUBCLASS_HEADER pHeader, SUBCLASSPROC pfnSubclass, UINT_PTR uIdSubclass) { SUBCLASS_CALL *pCall; UINT uCallIndex; AssertF(InCrit()); /* we'll be scanning the call array */ /* * scan the call array. note that we assume there is always at least * one member in the table (our CallOriginalWndProc record) */ uCallIndex = pHeader->uRefs; pCall = &pHeader->CallArray[uCallIndex]; do { uCallIndex--; pCall--; if ((pCall->pfnSubclass == pfnSubclass) && (pCall->uIdSubclass == uIdSubclass)) { return pCall; } } while (uCallIndex != (UINT)-1); return NULL; } /***************************************************************************** * * @doc INTERNAL * * @func BOOL | GetWindowSubclass | * * Retrieves the reference data for the specified window * subclass callback. * * @parm HWND | hwnd | * * Window in question. * * @parm SUBCLASSPROC | pfnSubclass | * * Subclass callback procedure to locate. * * @parm UINT | uIdSubclass | * * Instance identifier associated with the callback. * * @parm LPDWORD | pdwRefData | * * Output pointer. * *****************************************************************************/ BOOL EXTERNAL GetWindowSubclass(HWND hwnd, SUBCLASSPROC pfnSubclass, UINT_PTR uIdSubclass, PULONG_PTR pdwRefData) { BOOL fRc; ULONG_PTR dwRefData; DllEnterCrit(); /* * sanity */ if (IsWindow(hwnd) && pfnSubclass) { PSUBCLASS_HEADER pHeader; SUBCLASS_CALL *pCall; /* * if we've subclassed it and they are a client then get the refdata */ pHeader = GetSubclassHeader(hwnd); if (pHeader && (pHeader != (PSUBCLASS_HEADER)1) && (pCall = FindCallRecord(pHeader, pfnSubclass, uIdSubclass)) != 0) { /* * fetch the refdata and note success */ fRc = 1; dwRefData = pCall->dwRefData; } else { fRc = 0; dwRefData = 0; } } else { /* Invalid window handle */ // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl | sqflError, TEXT("GetWindowSubclass: ") TEXT("Bad window %p or callback %p"), hwnd, pfnSubclass); fRc = 0; dwRefData = 0; } /* * we always fill in/zero pdwRefData regradless of result */ if (pdwRefData) { *pdwRefData = dwRefData; } DllLeaveCrit(); return fRc; } /***************************************************************************** * * @doc INTERNAL * * @func BOOL | SetWindowSubclass | * * Installs/updates a window subclass callback. Subclass * callbacks are identified by their callback address and id pair. * If the specified callback/id pair is not yet installed then * the procedure installs the pair. If the callback/id pair is * already installed, then this procedure changes the reference * data for the pair. * * @parm HWND | hwnd | * * Window in question. * * @parm SUBCLASSPROC | pfnSubclass | * * Subclass callback procedure to install or modify. * * @parm UINT | uIdSubclass | * * Instance identifier associated with the callback. * * @parm DWORD | dwRefData | * * Reference data to associate with the callback/id. * *****************************************************************************/ BOOL EXTERNAL SetWindowSubclass(HWND hwnd, SUBCLASSPROC pfnSubclass, UINT_PTR uIdSubclass, ULONG_PTR dwRefData) { BOOL fRc; /* * sanity */ if (IsWindow(hwnd) && pfnSubclass) { SUBCLASS_HEADER *pHeader; /* * we party on the subclass call chain here */ DllEnterCrit(); /* * actually subclass the window */ /* * Prefix gets confused (mb:34501) by this. I assume this is because * AttachSubclassHeader returns a pointer to allocated memory but we * allow the pointer to go out of context without saving it. This is * OK because AttachSubclassHeader already saved it for us. */ pHeader = AttachSubclassHeader(hwnd); if (pHeader) { SUBCLASS_CALL *pCall; /* * find a call node for this caller */ pCall = FindCallRecord(pHeader, pfnSubclass, uIdSubclass); if (pCall == NULL) { /* * not found, alloc a new one */ SUBCLASS_HEADER *pHeaderT = ReAllocSubclassHeader(hwnd, pHeader, pHeader->uRefs + 1); if (pHeaderT) { pHeader = pHeaderT; pCall = &pHeader->CallArray[pHeader->uRefs++]; } else { /* * re-query in case it is already gone */ pHeader = FastGetSubclassHeader(hwnd); if (pHeader) { CompactSubclassHeader(hwnd, pHeader); } goto bail; } } /* * fill in the subclass call data */ pCall->pfnSubclass = pfnSubclass; pCall->uIdSubclass = uIdSubclass; pCall->dwRefData = dwRefData; // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl, TEXT("SetWindowSubclass: Added %p/%d as %d"), pfnSubclass, uIdSubclass, pHeader->uRefs - 1); fRc = 1; } else { /* Unable to subclass */ bail:; fRc = 0; } DllLeaveCrit(); } else { fRc = 0; /* Invalid parameter */ } return fRc; } /***************************************************************************** * * @doc INTERNAL * * @func BOOL | RemoveWindowSubclass | * * Removes a subclass callback from a window. * Subclass callbacks are identified by their * callback address and id pair. * * @parm HWND | hwnd | * * Window in question. * * @parm SUBCLASSPROC | pfnSubclass | * * Subclass callback procedure to remove. * * @parm UINT | uIdSubclass | * * Instance identifier associated with the callback. * *****************************************************************************/ BOOL EXTERNAL RemoveWindowSubclass(HWND hwnd, SUBCLASSPROC pfnSubclass, UINT_PTR uIdSubclass) { BOOL fRc; /* * sanity */ if (IsWindow(hwnd) && pfnSubclass) { SUBCLASS_HEADER *pHeader; /* * we party on the subclass call chain here */ DllEnterCrit(); /* * obtain our subclass data and find the callback to remove. */ pHeader = GetSubclassHeader(hwnd); if (pHeader && (pHeader != (PSUBCLASS_HEADER)1) ) { SUBCLASS_CALL *pCall; /* * find the callback to remove */ pCall = FindCallRecord(pHeader, pfnSubclass, uIdSubclass); if (pCall) { UINT uCall; /* * disable this node. */ pCall->pfnSubclass = 0; /* * Remember that we have something to clean up. * * Set uCleanup to the index of the shallowest node that * needs to be cleaned up. CompactSubclassHeader will * clean up everything from uCleanup onward. */ uCall = (UINT)(pCall - pHeader->CallArray); if (fLimpFF(pHeader->uCleanup, uCall < pHeader->uCleanup)) { pHeader->uCleanup = uCall; } // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl, TEXT("RemoveWindowSubclass: Removing %p/%d as %d"), pfnSubclass, uIdSubclass, uCall); /* * now try to clean up any unused nodes */ CompactSubclassHeader(hwnd, pHeader); /* * the call above can realloc or free the subclass * header for this window, so make sure we don't use it. */ D(pHeader = 0); fRc = 1; } else { /* Not found */ fRc = 0; } } else { /* Never subclassed (ergo not found) */ fRc = 0; } /* * release the critical section and return the result */ DllLeaveCrit(); } else { fRc = 0; /* Validation failed */ } return fRc; } /***************************************************************************** * * @doc INTERNAL * * @func LRESULT | DefSubclassProc | * * Calls the next handler in the window's subclass chain. * The last handler in the subclass chain is installed by us, * and calls the original window procedure for the window. * * Every subclass procedure should call * in order to allow the message to be processed by other handlers. * * @parm HWND | hwnd | * * Window in question. * * @parm UINT | wm | * * Window message that needs to go to the original . * * @parm WPARAM | wp | * * Meaning depends on window message. * * @parm LPARAM | lp | * * Meaning depends on window message. * *****************************************************************************/ LRESULT EXTERNAL DefSubclassProc(HWND hwnd, UINT wm, WPARAM wp, LPARAM lp) { LRESULT lResult; /* * make sure the window is still valid */ if (IsWindow(hwnd)) { PSUBCLASS_HEADER pHeader; /* * take the critical section while we figure out who to call next */ AssertF(!InCrit()); DllEnterCrit(); /* * complain if we are being called improperly */ pHeader = FastGetSubclassHeader(hwnd); if (pHeader && pHeader->pFrameCur && GetCurrentThreadId() == pHeader->dwThreadId) { /* * call the next proc in the subclass chain * * WARNING: this call temporarily releases the critical section * WARNING: pHeader is invalid when this call returns */ lResult = CallNextSubclassProc(pHeader, hwnd, wm, wp, lp); D(pHeader = 0); } else { SquirtSqflPtszV(sqfl | sqflError, TEXT("DefSubclassProc: Called improperly")); lResult = 0; } DllLeaveCrit(); } else { // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl | sqflError, TEXT("DefSubclassProc: %P not a window"), hwnd); lResult = 0; } return lResult; } /***************************************************************************** * * @doc INTERNAL * * @func void | UpdateDeepestCall | * * Updates the deepest call index for the specified frame. * * @parm PSUBCLASS_FRAME | pFrame | * * Frame in question. * *****************************************************************************/ void INTERNAL UpdateDeepestCall(SUBCLASS_FRAME *pFrame) { AssertF(InCrit()); /* we are partying on the frame list */ /* * My deepest call equals my current call or my parent's * deepest call, whichever is deeper. */ if (pFrame->pFramePrev && (pFrame->pFramePrev->uDeepestCall < pFrame->uCallIndex)) { pFrame->uDeepestCall = pFrame->pFramePrev->uDeepestCall; } else { pFrame->uDeepestCall = pFrame->uCallIndex; } } /***************************************************************************** * * @doc INTERNAL * * @func void | EnterSubclassFrame | * * Sets up a new subclass frame for the specified header, * saving away the previous one. * * @parm PSUBCLASS_HEADER | pHeader | * * Header in question. * * @parm PSUBCLASS_FRAME | pFrame | * * Brand new frame to link in. * *****************************************************************************/ void INLINE EnterSubclassFrame(PSUBCLASS_HEADER pHeader, SUBCLASS_FRAME *pFrame) { AssertF(InCrit()); /* we are partying on the header and frame list */ /* * fill in the frame and link it into the header */ pFrame->uCallIndex = pHeader->uRefs + 1; pFrame->pFramePrev = pHeader->pFrameCur; pFrame->pHeader = pHeader; pHeader->pFrameCur = pFrame; /* * initialize the deepest call index for this frame */ UpdateDeepestCall(pFrame); } /***************************************************************************** * * @doc INTERNAL * * @func void | LeaveSubclassFrame | * * Tear down the current subclass frame, restoring the previous one. * * @parm PSUBCLASS_FRAME | pFrame | * * Frame going away. * *****************************************************************************/ PSUBCLASS_HEADER INLINE LeaveSubclassFrame(SUBCLASS_FRAME *pFrame) { PSUBCLASS_HEADER pHeader; AssertF(InCrit()); /* we are partying on the header */ /* * unlink the frame from its header (if it still exists) */ pHeader = pFrame->pHeader; if (pHeader) { pHeader->pFrameCur = pFrame->pFramePrev; } return pHeader; } #ifdef SUBCLASS_HANDLEEXCEPTIONS /***************************************************************************** * * @doc INTERNAL * * @func void | SubclassFrameException | * * Clean up when a exception is thrown from a subclass frame. * * @parm PSUBCLASS_FRAME | pFrame | * * Frame to clean up. * *****************************************************************************/ void INTERNAL SubclassFrameException(SUBCLASS_FRAME *pFrame) { /* * clean up the current subclass frame */ AssertF(!InCrit()); DllEnterCrit(); SquirtSqflPtszV(sqfl | sqflError, TEXT("SubclassFrameException: ") TEXT("cleaning up subclass frame after exception")); LeaveSubclassFrame(pFrame); DllLeaveCrit(); } #endif /***************************************************************************** * * @doc INTERNAL * * @func LRESULT | MasterSubclassProc | * * The window procedure we install to dispatch subclass * callbacks. * * It maintains a linked list of "frames" through the stack * which allow to call the right subclass * procedure in multiple-message scenarios. * * @parm HWND | hwnd | * * Window under attack. * * @parm UINT | wm | * * Window message. * * @parm WPARAM | wp | * * Meaning depends on window message. * * @parm LPARAM | lp | * * Meaning depends on window message. * *****************************************************************************/ LRESULT CALLBACK MasterSubclassProc(HWND hwnd, UINT wm, WPARAM wp, LPARAM lp) { SUBCLASS_HEADER *pHeader; LRESULT lResult; /* * prevent people from partying on the callback chain while we look at it */ AssertF(!InCrit()); DllEnterCrit(); /* * We'd better have our data. */ pHeader = FastGetSubclassHeader(hwnd); if (pHeader) { SUBCLASS_FRAME Frame; /* * set up a new subclass frame and save away the previous one */ EnterSubclassFrame(pHeader, &Frame); #ifdef SUBCLASS_HANDLEEXCEPTIONS __try /* protect our state information from exceptions */ #endif { /* * go ahead and call the subclass chain on this frame * * WARNING: this call temporarily releases the critical section * WARNING: pHeader is invalid when this call returns */ lResult = CallNextSubclassProc(pHeader, hwnd, wm, wp, lp); D(pHeader = 0); } #ifdef SUBCLASS_HANDLEEXCEPTIONS __except (SubclassFrameException(&Frame), EXCEPTION_CONTINUE_SEARCH) { AssertF(0); } #endif AssertF(InCrit()); /* * restore the previous subclass frame */ pHeader = LeaveSubclassFrame(&Frame); /* * Do postprocessing if the header is still here. */ if (pHeader) { /* * was the window nuked (somehow) * without us seeing the WM_NCDESTROY? */ if (!IsWindow(hwnd)) { /* * EVIL! somebody subclassed after us and didn't pass on WM_NCDESTROY */ AssertF(!TEXT("unknown subclass proc swallowed a WM_NCDESTROY")); /* go ahead and clean up now */ hwnd = 0; wm = WM_NCDESTROY; } /* * if we are returning from a WM_NCDESTROY then we need to clean up */ if (wm == WM_NCDESTROY) { DetachSubclassHeader(hwnd, pHeader, TRUE); } else { /* * is there any pending cleanup, or are all our clients gone? */ if (pHeader->uCleanup || (!pHeader->pFrameCur && (pHeader->uRefs <= 1))) { CompactSubclassHeader(hwnd, pHeader); D(pHeader = 0); } } /* * all done */ } else { /* * Header is gone. We already cleaned up in a nested frame. */ } DllLeaveCrit(); AssertF(!InCrit()); } else { /* Our property vanished! */ DllLeaveCrit(); lResult = SubclassDeath(hwnd, wm, wp, lp); } return lResult; } /***************************************************************************** * * @doc INTERNAL * * @func UINT | EnterSubclassCallback | * * Finds the next callback in the subclass chain and updates *

to indicate that we are calling it. * * @parm PSUBCLASS_HEADER | pHeader | * * Controlling header. * * @parm PSUBCLASS_FRAME | pFrame | * * Frame to update. * * @parm SUBCLASS_CALL * | pCallChosen | * * The call selected for calling. * *****************************************************************************/ UINT INTERNAL EnterSubclassCallback(PSUBCLASS_HEADER pHeader, SUBCLASS_FRAME *pFrame, SUBCLASS_CALL *pCallChosen) { SUBCLASS_CALL *pCall; UINT uDepth; /* * we will be scanning the subclass chain and updating frame data */ AssertF(InCrit()); /* * scan the subclass chain for the next callable subclass callback * Assert that the loop will terminate. */ AssertF(pHeader->CallArray[0].pfnSubclass); pCall = pHeader->CallArray + pFrame->uCallIndex; uDepth = 0; do { uDepth++; pCall--; } while (!pCall->pfnSubclass); /* * copy the callback information for the caller */ pCallChosen->pfnSubclass = pCall->pfnSubclass; pCallChosen->uIdSubclass = pCall->uIdSubclass; pCallChosen->dwRefData = pCall->dwRefData; /* * adjust the frame's call index by the depth we entered */ pFrame->uCallIndex -= uDepth; /* * keep the deepest call index up to date */ UpdateDeepestCall(pFrame); return uDepth; } /***************************************************************************** * * @doc INTERNAL * * @func void | LeaveSubclassCallback | * * Get out one level. * * @parm PSUBCLASS_FRAME | pFrame | * * Frame to update. * * @parm UINT | uDepth | * * Who just finished. * *****************************************************************************/ void INLINE LeaveSubclassCallback(SUBCLASS_FRAME *pFrame, UINT uDepth) { /* * we will be updating subclass frame data */ AssertF(InCrit()); /* * adjust the frame's call index by the depth we entered and return */ pFrame->uCallIndex += uDepth; /* * keep the deepest call index up to date */ UpdateDeepestCall(pFrame); } #ifdef SUBCLASS_HANDLEEXCEPTIONS /***************************************************************************** * * @doc INTERNAL * * @func void | SubclassCallbackException | * * Clean up when a subclass callback throws an exception. * * @parm PSUBCLASS_FRAME | pFrame | * * Frame to clean up. * * @parm UINT | uDepth | * * Where we were. * *****************************************************************************/ void INTERNAL SubclassCallbackException(SUBCLASS_FRAME *pFrame, UINT uDepth) { /* * clean up the current subclass callback */ AssertF(!InCrit()); DllEnterCrit(); SquirtSqflPtszV(sqfl | sqflError, TEXT("SubclassCallbackException: ") TEXT("cleaning up subclass callback after exception")); LeaveSubclassCallback(pFrame, uDepth); DllLeaveCrit(); } #endif /***************************************************************************** * * @doc INTERNAL * * @func LRESULT | CallNextSubclassProc | * * Calls the next subclass callback in the subclass chain. * * WARNING: this call temporarily releases the critical section. * * WARNING:

is invalid when this call returns. * * @parm PSUBCLASS_HEADER | pHeader | * * The header that is tracking the state. * * @parm HWND | hwnd | * * Window under attack. * * @parm UINT | wm | * * Window message. * * @parm WPARAM | wp | * * Meaning depends on window message. * * @parm LPARAM | lp | * * Meaning depends on window message. * *****************************************************************************/ LRESULT INTERNAL CallNextSubclassProc(PSUBCLASS_HEADER pHeader, HWND hwnd, UINT wm, WPARAM wp, LPARAM lp) { SUBCLASS_CALL Call; SUBCLASS_FRAME *pFrame; LRESULT lResult; UINT uDepth; AssertF(InCrit()); /* sanity */ AssertF(pHeader); /* paranoia */ /* * get the current subclass frame */ pFrame = pHeader->pFrameCur; AssertF(pFrame); /* * get the next subclass call we need to make */ uDepth = EnterSubclassCallback(pHeader, pFrame, &Call); /* * leave the critical section so we don't deadlock in our callback * * WARNING: pHeader is invalid when this call returns */ DllLeaveCrit(); D(pHeader = 0); /* * we call the outside world so prepare to deadlock if we have the critsec */ AssertF(!InCrit()); #ifdef SUBCLASS_HANDLEEXCEPTIONS __try /* protect our state information from exceptions */ #endif { /* * call the chosen subclass proc */ AssertF(Call.pfnSubclass); lResult = Call.pfnSubclass(hwnd, wm, wp, lp, Call.uIdSubclass, Call.dwRefData); } #ifdef SUBCLASS_HANDLEEXCEPTIONS __except (SubclassCallbackException(pFrame, uDepth), EXCEPTION_CONTINUE_SEARCH) { AssertF(0); } #endif /* * we left the critical section before calling out so re-enter it */ AssertF(!InCrit()); DllEnterCrit(); /* * finally, clean up and return */ LeaveSubclassCallback(pFrame, uDepth); return lResult; }