545 lines
16 KiB
C++
545 lines
16 KiB
C++
/*--------------------------------------------------------------------------*
|
||
*
|
||
* 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);
|
||
}
|
||
}
|