|
|
/*--------------------------------------------------------------------------*
* * Microsoft Windows * Copyright (C) Microsoft Corporation, 1992 - 1999 * * File: subclass.cpp * * Contents: Implementation file for the dynamic subclass manager * * History: 06-May-98 JeffRo Created * *--------------------------------------------------------------------------*/
#include "stdafx.h"
#include "subclass.h"
/*
* Add 0x00080000 to * HKLM\Software\Microsoft\Windows\CurrentVersion\AdminDebug\AMCConUI * to enable debug output for this module */ #define DEB_SUBCLASS DEB_USER4
/*--------------------------------------------------------------------------*
* SetWindowProc * * Changes the window procedure for a window and returns the previous * window procedure. *--------------------------------------------------------------------------*/
static WNDPROC SetWindowProc (HWND hwnd, WNDPROC pfnNewWndProc) { return ((WNDPROC) SetWindowLongPtr (hwnd, GWLP_WNDPROC, (LONG_PTR) pfnNewWndProc)); }
/*--------------------------------------------------------------------------*
* GetWindowProc * * Returns the window procedure for a window. *--------------------------------------------------------------------------*/
static WNDPROC GetWindowProc (HWND hwnd) { return ((WNDPROC) GetWindowLongPtr (hwnd, GWLP_WNDPROC)); }
/*--------------------------------------------------------------------------*
* GetSubclassManager * * Returns the one-and-only subclass manager for the app. *--------------------------------------------------------------------------*/
CSubclassManager& GetSubclassManager() { static CSubclassManager mgr; return (mgr); }
/*--------------------------------------------------------------------------*
* CSubclassManager::SubclassWindow * * Subclasses a window. *--------------------------------------------------------------------------*/
bool CSubclassManager::SubclassWindow ( HWND hwnd, CSubclasser* pSubclasser) { /*
* Set up the data structure that represents this subclass. */ SubclasserData subclasser (pSubclasser, hwnd);
/*
* Get the subclass context for this window. If this is the * first time this window is being subclassed, std::map will * create a map entry for it. */ WindowContext& ctxt = m_ContextMap[hwnd];
/*
* If the subclass context's wndproc pointer is NULL, then this * is the first time we've subclassed this window. We need to * physically subclass the window with CSubclassManager's subclass proc. */ if (ctxt.pfnOriginalWndProc == NULL) { ctxt.pfnOriginalWndProc = SetWindowProc (hwnd, SubclassProc); ASSERT (ctxt.Subclassers.empty()); Dbg (DEB_SUBCLASS, _T("CSubclassManager subclassed window 0x%08x\n"), hwnd); }
/*
* Otherwise, make sure this isn't a redundant subclass. */ else { SubclasserList::iterator itEnd = ctxt.Subclassers.end(); SubclasserList::iterator itFound = std::find (ctxt.Subclassers.begin(), itEnd, subclasser);
/*
* Trying to subclass a window with a given subclasser twice? */ if (itFound != itEnd) { ASSERT (false); return (false); } }
/*
* Add this subclasser to this windows subclasser list. */ ctxt.Insert (subclasser); Dbg (DEB_SUBCLASS, _T("CSubclassManager added subclass proc for window 0x%08x\n"), hwnd);
return (true); }
/*--------------------------------------------------------------------------*
* CSubclassManager::UnsubclassWindow * * Unsubclasses a window. *--------------------------------------------------------------------------*/
bool CSubclassManager::UnsubclassWindow ( HWND hwnd, CSubclasser* pSubclasser) { /*
* Get the subclass context for this window. Use map::find * instead of map::operator[] to avoid creating a map entry if * one doesn't exist already */ ContextMap::iterator itContext = m_ContextMap.find (hwnd);
/*
* Trying to unsubclass a window that's not subclassed at all? */ if (itContext == m_ContextMap.end()) return (false);
WindowContext& ctxt = itContext->second;
/*
* Set up the data structure that represents this subclass. */ SubclasserData subclasser (pSubclasser, hwnd);
/*
* Trying to unsubclass a window that's not subclassed * by this subclasser? */ SubclasserList::iterator itEnd = ctxt.Subclassers.end(); SubclasserList::iterator itSubclasser = std::find (ctxt.Subclassers.begin(), itEnd, subclasser);
if (itSubclasser == itEnd) { ASSERT (false); return (false); }
/*
* Remove this subclasser */ UINT cRefs = ctxt.Remove (*itSubclasser);
if (cRefs == 0) { Dbg (DEB_SUBCLASS, _T("CSubclassManager removed subclass proc for window 0x%08x\n"), hwnd); } else { Dbg (DEB_SUBCLASS, _T("CSubclassManager zombied subclass proc for window 0x%08x, (cRefs=%d)\n"), hwnd, cRefs); }
/*
* If we just removed the last subclasser, unsubclass the window * and remove the window's WindowContext from the map. */ if (ctxt.Subclassers.empty() && !PhysicallyUnsubclassWindow (hwnd)) { Dbg (DEB_SUBCLASS, _T("CSubclassManager zombied window 0x%08x\n"), hwnd); }
return (true); }
/*--------------------------------------------------------------------------*
* CSubclassManager::PhysicallyUnsubclassWindow * * Physically removes CSubclassManager's subclass proc from the given * window if it is safe (or forced) to do so. * * It is safe to remove a subclass procedure A from a window W if no one * has subclassed W after A. In other words, subclasses have to be removed * in a strictly LIFO order, or there's big trouble. * * To illustrate, let's say the A subclasses W. Messages that A doesn't * handle will be passed on to W's original window procedure that was in * place when A subclassed W. Call this original procedure O. So * messages flow from A to O: * * A -> O * * Now let's say that B subclasses the W. B will pass messages on to A, * so the messages now flow like so: * * B -> A -> O * * Now say that A no longer needs to subclass W. The typical way to * unsubclass a window is to put back the original window procedure that * was in place at the time of subclassing. In A's case that was O, so * messages destined for W now flow directly to O: * * O * * This is the first problem: B has been shorted out of the window's * message stream. * * The problem gets worse when B no longer needs to subclass W. It will * put back the window procedure it found when it subclassed, namely A. * A's work no longer needs to be done, and there's no telling whether * A's conduit to O is still alive. We don't want to get into this * situation. *--------------------------------------------------------------------------*/
bool CSubclassManager::PhysicallyUnsubclassWindow ( HWND hwnd, /* I:window to unsubclass */ bool fForce /* =false */) /* I:force the unsubclass? */ { ContextMap::iterator itRemove = m_ContextMap.find(hwnd);
/*
* If we get here, this window had better be in the map. */ ASSERT (itRemove != m_ContextMap.end());
/*
* If no one subclassed after CSubclassManager, it's safe to unsubclass. */ if (GetWindowProc (hwnd) == SubclassProc) { const WindowContext& ctxt = itRemove->second; SetWindowProc (hwnd, ctxt.pfnOriginalWndProc); fForce = true; Dbg (DEB_SUBCLASS, _T("CSubclassManager unsubclassed window 0x%08x\n"), hwnd); }
/*
* Remove this window's entry from the context map if appropriate. */ if (fForce) m_ContextMap.erase (itRemove);
return (fForce); }
/*--------------------------------------------------------------------------*
* CSubclassManager::SubclassProc * * *--------------------------------------------------------------------------*/
LRESULT CALLBACK CSubclassManager::SubclassProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { AFX_MANAGE_STATE (AfxGetAppModuleState());
return (GetSubclassManager().SubclassProcWorker (hwnd, msg, wParam, lParam)); }
/*--------------------------------------------------------------------------*
* CSubclassManager::SubclassProcWorker * * *--------------------------------------------------------------------------*/
LRESULT CSubclassManager::SubclassProcWorker (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { /*
* Get the subclass context for this window. Use map::find * instead of map::operator[] to avoid excessive overhead in * map::operator[] */ ContextMap::iterator itContext = m_ContextMap.find (hwnd);
/*
* If we get here, this window had better be in the map. */ ASSERT (itContext != m_ContextMap.end());
WindowContext& ctxt = itContext->second; WNDPROC pfnOriginalWndProc = ctxt.pfnOriginalWndProc;
bool fPassMessageOn = true; LRESULT rc;
/*
* If there are subclassers, give each one a crack at this message. * If a subclasser indicates it wants to eat the message, bail. */ if (!ctxt.Subclassers.empty()) { SubclasserList::iterator it;
for (it = ctxt.Subclassers.begin(); it != ctxt.Subclassers.end() && fPassMessageOn; ++it) { SubclasserData& subclasser = *it; subclasser.AddRef();
ctxt.RemoveZombies (); /*
* If this isn't a zombied subclasser, call the callback */ if (!ctxt.IsZombie(subclasser)) { rc = subclasser.pSubclasser->Callback (hwnd, msg, wParam, lParam, fPassMessageOn); }
subclasser.Release(); }
ctxt.RemoveZombies (); }
/*
* Otherwise, we have a zombie window (see * PhysicallyUnsubclassWindow). Try to remove the zombie now. */ else if (PhysicallyUnsubclassWindow (hwnd)) { Dbg (DEB_SUBCLASS, _T("CSubclassManager removed zombied window 0x%08x\n"), hwnd); }
/*
* remove this window's WindowContext on WM_NCDESTROY */ if ((msg == WM_NCDESTROY) && (m_ContextMap.find(hwnd) != m_ContextMap.end())) { Dbg (DEB_SUBCLASS, _T("CSubclassManager forced removal of zombied window 0x%08x on WM_NCDESTROY\n"), hwnd); PhysicallyUnsubclassWindow (hwnd, true); }
/*
* If the last subclasser didn't eat the message, * give it to the original window procedure. */ if (fPassMessageOn) rc = CallWindowProc (pfnOriginalWndProc, hwnd, msg, wParam, lParam);
return (rc); }
/*--------------------------------------------------------------------------*
* WindowContext::IsZombie * * *--------------------------------------------------------------------------*/
bool WindowContext::IsZombie (const SubclasserData& subclasser) const { /*
* If this is a zombie, make sure it's in the zombie list; * if it's not, make sure it's not. */ ASSERT (subclasser.fZombie == (Zombies.find(subclasser) != Zombies.end()));
return (subclasser.fZombie); }
/*--------------------------------------------------------------------------*
* WindowContext::Zombie * * Changes the state fo a subclasser to or from a zombie. *--------------------------------------------------------------------------*/
void WindowContext::Zombie (SubclasserData& subclasser, bool fZombie) { // zombie-ing a zombied subclasser?
ASSERT (IsZombie (subclasser) != fZombie);
subclasser.fZombie = fZombie;
if (fZombie) Zombies.insert (subclasser); else Zombies.erase (subclasser);
ASSERT (IsZombie (subclasser) == fZombie); }
/*--------------------------------------------------------------------------*
* WindowContext::Insert * * *--------------------------------------------------------------------------*/
void WindowContext::Insert (SubclasserData& subclasser) { /*
* This code can't handle re-subclassing by a subclasser * that's currently a zombie. If this ever becomes a requirement, * we'll need to identify the subclass instance by something other * than the CSubclasser pointer, like a unique handle. */ ASSERT (Zombies.find(subclasser) == Zombies.end());
/*
* Subclassers get called in LIFO order, put the new * subclasser at the head of the list. */ Subclassers.push_front (subclasser); }
/*--------------------------------------------------------------------------*
* WindowContext::Remove * * Logically removes a subclasser from the subclass chain. "Logically" * because it's not safe to totally remove a subclasser from the chain if * it's currently in use. If the subclass is in use when we want to remove * it, we'll mark it as "zombied" so it won't be used any more, to be * physically removed later. * * Returns the reference count for the subclasser. *--------------------------------------------------------------------------*/
UINT WindowContext::Remove (SubclasserData& subclasser) { // we shouldn't be removing zombies this way
ASSERT (!IsZombie (subclasser));
/*
* If this subclasser has outstanding references, zombie it instead * of removing it. */ UINT cRefs = subclasser.cRefs;
if (cRefs == 0) { SubclasserList::iterator itRemove = std::find (Subclassers.begin(), Subclassers.end(), subclasser); ASSERT (itRemove != Subclassers.end()); Subclassers.erase (itRemove); } else { Zombie (subclasser, true); }
return (cRefs); }
/*--------------------------------------------------------------------------*
* WindowContext::RemoveZombies * * *--------------------------------------------------------------------------*/
void WindowContext::RemoveZombies () { if (Zombies.empty()) return;
/*
* Build up a list of zombies that we can remove. We have to build * the list ahead of time, instead of removing them as we find them, * because removing an element from a set invalidates all iterators * on the set. */ SubclasserSet ZombiesToRemove;
SubclasserSet::iterator itEnd = Zombies.end(); SubclasserSet::iterator it;
for (it = Zombies.begin(); it != itEnd; ++it) { const SubclasserData& ShadowSubclasser = *it;
/*
* Find the real subclasser in the Subclassers list. That's * the live one whose ref count will be correct. */ SubclasserList::iterator itReal = std::find (Subclassers.begin(), Subclassers.end(), ShadowSubclasser);
const SubclasserData& RealSubclasser = *itReal;
if (RealSubclasser.cRefs == 0) { Dbg (DEB_SUBCLASS, _T("CSubclassManager removed zombied subclass proc for window 0x%08x\n"), RealSubclasser.hwnd); ZombiesToRemove.insert (ShadowSubclasser); Subclassers.erase (itReal); } }
/*
* Now remove the truly dead zombies. */ itEnd = ZombiesToRemove.end();
for (it = ZombiesToRemove.begin(); it != itEnd; ++it) { Zombies.erase (*it); } }
|