2039 lines
58 KiB
C
2039 lines
58 KiB
C
/*****************************************************************************
|
|
*
|
|
* 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;
|
|
}
|
|
|