|
|
/*****************************************************************************
* * 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 * <t SUBCLASS_FRAME>, 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 <e SUBCLASS_FRAME.uCallIndex> 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 <p CallArray>. * * @field UINT | uAlloc | * * Number of allocated <t SUBCLASS_CALL> 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 <t WNDPROC>). * * Hitting this represents a catastrophic failure in the * subclass code. * * The function resets the <t WNDPROC> of the window to * <f DefWindowProc> 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 <t WNDPROC> of the specified window. * * @parm HWND | hwnd | * * Window to be inspected. * * @returns * * The <t WNDPROC> 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 <t ATOM> we use to store our * <t SUBCLASS_HEADER> property on whatever windows come our way. * * If the <p WIN95_HACK> symbol is defined, then we will work * around a bug in Windows 95 where Windows "helpfully" * <f GlobalDeleteAtom>'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 <t SUBCLASS_HEADER> 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 <t SUBCLASS_HEADER> associated with the window, * or <c NULL> 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 <t SUBCLASS_HEADER> 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 <t SUBCLASS_HEADER> associated with the window, * or <c NULL> 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 <t SUBCLASS_HEADER> 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 <t SUBCLASS_HEADER>. * *****************************************************************************/
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 <t SUBCLASSPROC> 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 <t WNDPROC> and * returns its result. * * @parm HWND | hwnd | * * Window in question. * * @parm UINT | wm | * * Window message that needs to go to the original <t WNDPROC>. * * @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 <t WNDPROC>). * *****************************************************************************/
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 <p uCleanup> 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 <f DefSubclassProc> * 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 <t WNDPROC>. * * @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 <f DefSubclassProc> 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 * <p pFrame> 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: <p pHeader> 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; }
|