#include "ctlspriv.h" /////////////////////////////////////////////////////////////////////////////// // SUBCLASS.C -- subclassing helper functions // // SetWindowSubclass // GetWindowSubclass // RemoveWindowSubclass // DefSubclassProc // // 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. // // Warning: You cannot use these to subclass a window across threads since // the critical sections have been removed. 05-May-97 // // History: // 26-April-96 francish Created. // 05-May -97 davidds Stopped serializing the world. /////////////////////////////////////////////////////////////////////////////// // // NOTE: Although a linked list would have made the code slightly simpler, this // module uses a packed callback array to avoid unneccessary fragmentation. fh // struct _SUBCLASS_HEADER; typedef struct { SUBCLASSPROC pfnSubclass; // subclass procedure WPARAM uIdSubclass; // unique subclass identifier DWORD_PTR dwRefData; // optional ref data } SUBCLASS_CALL; typedef struct _SUBCLASS_FRAME { UINT uCallIndex; // index of next callback to call UINT uDeepestCall; // deepest uCallIndex on stack struct _SUBCLASS_FRAME *pFramePrev; // previous subclass frame pointer struct _SUBCLASS_HEADER *pHeader; // header associated with this frame } SUBCLASS_FRAME; typedef struct _SUBCLASS_HEADER { UINT uRefs; // subclass count UINT uAlloc; // allocated subclass call nodes UINT uCleanup; // index of call node to clean up DWORD dwThreadId; // thread id of window we are hooking SUBCLASS_FRAME *pFrameCur; // current subclass frame pointer SUBCLASS_CALL CallArray[1]; // base of packed call node array } SUBCLASS_HEADER; #define CALLBACK_ALLOC_GRAIN (3) // 1 defproc, 1 subclass, 1 spare #ifdef DEBUG BOOL IsValidPSUBCLASS_CALL(SUBCLASS_CALL * pcall) { return (IS_VALID_WRITE_PTR(pcall, SUBCLASS_CALL) && (NULL == pcall->pfnSubclass || IS_VALID_CODE_PTR(pcall->pfnSubclass, SUBCLASSPROC))); } // The LITE version does not validate the pHeader. // Use this if you expect the pHeader to be bad. BOOL IsValidPSUBCLASS_FRAME_LITE(SUBCLASS_FRAME * pframe) { return (IS_VALID_WRITE_PTR(pframe, SUBCLASS_FRAME) && (NULL == pframe->pFramePrev || IS_VALID_WRITE_PTR(pframe->pFramePrev, SUBCLASS_FRAME))); } // The regular version does all the LITE validation plus validates // the pHeader. Most people will use this version. BOOL IsValidPSUBCLASS_FRAME(SUBCLASS_FRAME * pframe) { return (IS_VALID_STRUCT_PTR(pframe, SUBCLASS_FRAME_LITE) && IS_VALID_WRITE_PTR(pframe->pHeader, SUBCLASS_HEADER)); } // // The LITE version validates the SUBCLASS_FRAME the LITE way rather // than the regular way. // BOOL IsValidPSUBCLASS_HEADER_LITE(SUBCLASS_HEADER * phdr) { BOOL bRet = (IS_VALID_WRITE_PTR(phdr, SUBCLASS_HEADER) && (NULL == phdr->pFrameCur || IS_VALID_STRUCT_PTR(phdr->pFrameCur, SUBCLASS_FRAME_LITE)) && IS_VALID_WRITE_BUFFER(phdr->CallArray, SUBCLASS_CALL, phdr->uAlloc)); if (bRet) { UINT i; SUBCLASS_CALL * pcall = phdr->CallArray; for (i = 0; i < phdr->uRefs; i++, pcall++) { if (!IS_VALID_STRUCT_PTR(pcall, SUBCLASS_CALL)) return FALSE; } } return bRet; } // The regular version does regular validation of the SUBCLASS_FRAME. BOOL IsValidPSUBCLASS_HEADER(SUBCLASS_HEADER * phdr) { return (IS_VALID_STRUCT_PTR(phdr, SUBCLASS_HEADER_LITE) && (NULL == phdr->pFrameCur || IS_VALID_STRUCT_PTR(phdr->pFrameCur, SUBCLASS_FRAME))); } #endif /////////////////////////////////////////////////////////////////////////////// // DEBUG CODE TO CHECK IF WINDOW IS ON SAME THREAD AS CALLER // Since we don't do any serialization, we need this to make sure of this. /////////////////////////////////////////////////////////////////////////////// #ifdef DEBUG BOOL IsWindowOnCurrentThread(HWND hWnd) { DWORD foo; if (!IsWindow(hWnd)) // bail if the window is dead so we dont bogusly rip return(TRUE); if (GetCurrentThreadId() != GetWindowThreadProcessId(hWnd, &foo)) { DebugMsg(TF_ALWAYS, TEXT("wn: WindowSubclass - Called from wrong thread %08X"), hWnd); return(FALSE); } else return(TRUE); } #endif /////////////////////////////////////////////////////////////////////////////// LRESULT CALLBACK MasterSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT CallNextSubclassProc(SUBCLASS_HEADER *pHeader, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); //----------------------------------------------------------------------------- // RETAIL_ZOMBIE_MESSAGE_WNDPROC // // this macro controls the generation of diagnostic code for an error condition // in the subclass code (see the SubclassDeath function below). // // commenting out this macro will zombie windows using DefWindowProc instead. // //----------------------------------------------------------------------------- //#define RETAIL_ZOMBIE_MESSAGE_WNDPROC #if defined(RETAIL_ZOMBIE_MESSAGE_WNDPROC) || defined(DEBUG) #ifndef DEBUG #pragma message("\r\nWARNING: disable retail ZombieWndProc before final release\r\n") #endif LRESULT ZombieWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); #else #define ZombieWndProc DefWindowProc #endif //----------------------------------------------------------------------------- // SubclassDeath // // this function is called if we ever enter one of our subclassing procedures // without our reference data (and hence without the previous wndproc). // // hitting this represents a catastrophic failure in the subclass code. // // the function resets the wndproc of the window to a 'zombie' window // procedure to avoid faulting. the RETAIL_ZOMBIE_MESSAGE_WNDPROC macro above // controls the generation of diagnostic code for this wndproc. // //----------------------------------------------------------------------------- LRESULT SubclassDeath(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { // // WE SHOULD NEVER EVER GET HERE // if we do please find francish to debug it immediately // DebugMsg(TF_ALWAYS, TEXT("fatal: SubclassDeath in window %08X"), hWnd); #ifdef DEBUG // // if we are in a debugger, stop now regardless of break flags // __try { DebugBreak(); } __except(EXCEPTION_EXECUTE_HANDLER) {;} __endexcept #endif // // we call the outside world so prepare to deadlock if we have the critsec // #ifdef FREETHREADEDSUBCLASSGOOP ASSERTNONCRITICAL #endif // // in theory we could save the original wndproc in a separate property // but that just wastes memory for something that should never happen // // convert this window to a zombie in hopes that it will get debugged // InvalidateRect(hWnd, NULL, TRUE); SubclassWindow(hWnd, ZombieWndProc); return ZombieWndProc(hWnd, uMsg, wParam, lParam); } //----------------------------------------------------------------------------- // GetWindowProc // // this inline function returns the current wndproc for the specified window. // //----------------------------------------------------------------------------- __inline WNDPROC GetWindowProc(HWND hWnd) { return (WNDPROC)GetWindowLongPtr(hWnd, GWLP_WNDPROC); } //----------------------------------------------------------------------------- // g_aCC32Subclass // // This is the global ATOM we use to store our SUBCLASS_HEADER property on // random windows that come our way. // // HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK // // Win95's property code is BROKEN. If you SetProp using a text string, USER // adds and removes atoms for the property symmetrically, including when the // window is destroyed with properties lying around (good). Unfortunately, if // you SetProp using a global atom, USER doesn't do things quite right in the // window cleanup case. It uses the atom without adding references in SetProp // calls and without deleting them in RemoveProp calls (good so far). However, // when a window with one of these properties lying around is cleaned up, USER // will delete the atom on you. This tends to break apps that do the // following: // // - MyAtom = GlobalAddAtom("foo"); // at app startup // - SetProp(SomeWindow, MyAtom, MyData); // - // -