Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

544 lines
16 KiB

  1. /*--------------------------------------------------------------------------*
  2. *
  3. * Microsoft Windows
  4. * Copyright (C) Microsoft Corporation, 1992 - 1999
  5. *
  6. * File: subclass.cpp
  7. *
  8. * Contents: Implementation file for the dynamic subclass manager
  9. *
  10. * History: 06-May-98 JeffRo Created
  11. *
  12. *--------------------------------------------------------------------------*/
  13. #include "stdafx.h"
  14. #include "subclass.h"
  15. /*
  16. * Add 0x00080000 to
  17. * HKLM\Software\Microsoft\Windows\CurrentVersion\AdminDebug\AMCConUI
  18. * to enable debug output for this module
  19. */
  20. #define DEB_SUBCLASS DEB_USER4
  21. /*--------------------------------------------------------------------------*
  22. * SetWindowProc
  23. *
  24. * Changes the window procedure for a window and returns the previous
  25. * window procedure.
  26. *--------------------------------------------------------------------------*/
  27. static WNDPROC SetWindowProc (HWND hwnd, WNDPROC pfnNewWndProc)
  28. {
  29. return ((WNDPROC) SetWindowLongPtr (hwnd, GWLP_WNDPROC,
  30. (LONG_PTR) pfnNewWndProc));
  31. }
  32. /*--------------------------------------------------------------------------*
  33. * GetWindowProc
  34. *
  35. * Returns the window procedure for a window.
  36. *--------------------------------------------------------------------------*/
  37. static WNDPROC GetWindowProc (HWND hwnd)
  38. {
  39. return ((WNDPROC) GetWindowLongPtr (hwnd, GWLP_WNDPROC));
  40. }
  41. /*--------------------------------------------------------------------------*
  42. * GetSubclassManager
  43. *
  44. * Returns the one-and-only subclass manager for the app.
  45. *--------------------------------------------------------------------------*/
  46. CSubclassManager& GetSubclassManager()
  47. {
  48. static CSubclassManager mgr;
  49. return (mgr);
  50. }
  51. /*--------------------------------------------------------------------------*
  52. * CSubclassManager::SubclassWindow
  53. *
  54. * Subclasses a window.
  55. *--------------------------------------------------------------------------*/
  56. bool CSubclassManager::SubclassWindow (
  57. HWND hwnd,
  58. CSubclasser* pSubclasser)
  59. {
  60. /*
  61. * Set up the data structure that represents this subclass.
  62. */
  63. SubclasserData subclasser (pSubclasser, hwnd);
  64. /*
  65. * Get the subclass context for this window. If this is the
  66. * first time this window is being subclassed, std::map will
  67. * create a map entry for it.
  68. */
  69. WindowContext& ctxt = m_ContextMap[hwnd];
  70. /*
  71. * If the subclass context's wndproc pointer is NULL, then this
  72. * is the first time we've subclassed this window. We need to
  73. * physically subclass the window with CSubclassManager's subclass proc.
  74. */
  75. if (ctxt.pfnOriginalWndProc == NULL)
  76. {
  77. ctxt.pfnOriginalWndProc = SetWindowProc (hwnd, SubclassProc);
  78. ASSERT (ctxt.Subclassers.empty());
  79. Dbg (DEB_SUBCLASS, _T("CSubclassManager subclassed window 0x%08x\n"), hwnd);
  80. }
  81. /*
  82. * Otherwise, make sure this isn't a redundant subclass.
  83. */
  84. else
  85. {
  86. SubclasserList::iterator itEnd = ctxt.Subclassers.end();
  87. SubclasserList::iterator itFound = std::find (ctxt.Subclassers.begin(),
  88. itEnd, subclasser);
  89. /*
  90. * Trying to subclass a window with a given subclasser twice?
  91. */
  92. if (itFound != itEnd)
  93. {
  94. ASSERT (false);
  95. return (false);
  96. }
  97. }
  98. /*
  99. * Add this subclasser to this windows subclasser list.
  100. */
  101. ctxt.Insert (subclasser);
  102. Dbg (DEB_SUBCLASS, _T("CSubclassManager added subclass proc for window 0x%08x\n"), hwnd);
  103. return (true);
  104. }
  105. /*--------------------------------------------------------------------------*
  106. * CSubclassManager::UnsubclassWindow
  107. *
  108. * Unsubclasses a window.
  109. *--------------------------------------------------------------------------*/
  110. bool CSubclassManager::UnsubclassWindow (
  111. HWND hwnd,
  112. CSubclasser* pSubclasser)
  113. {
  114. /*
  115. * Get the subclass context for this window. Use map::find
  116. * instead of map::operator[] to avoid creating a map entry if
  117. * one doesn't exist already
  118. */
  119. ContextMap::iterator itContext = m_ContextMap.find (hwnd);
  120. /*
  121. * Trying to unsubclass a window that's not subclassed at all?
  122. */
  123. if (itContext == m_ContextMap.end())
  124. return (false);
  125. WindowContext& ctxt = itContext->second;
  126. /*
  127. * Set up the data structure that represents this subclass.
  128. */
  129. SubclasserData subclasser (pSubclasser, hwnd);
  130. /*
  131. * Trying to unsubclass a window that's not subclassed
  132. * by this subclasser?
  133. */
  134. SubclasserList::iterator itEnd = ctxt.Subclassers.end();
  135. SubclasserList::iterator itSubclasser = std::find (ctxt.Subclassers.begin(), itEnd, subclasser);
  136. if (itSubclasser == itEnd)
  137. {
  138. ASSERT (false);
  139. return (false);
  140. }
  141. /*
  142. * Remove this subclasser
  143. */
  144. UINT cRefs = ctxt.Remove (*itSubclasser);
  145. if (cRefs == 0)
  146. {
  147. Dbg (DEB_SUBCLASS, _T("CSubclassManager removed subclass proc for window 0x%08x\n"), hwnd);
  148. }
  149. else
  150. {
  151. Dbg (DEB_SUBCLASS, _T("CSubclassManager zombied subclass proc for window 0x%08x, (cRefs=%d)\n"),
  152. hwnd, cRefs);
  153. }
  154. /*
  155. * If we just removed the last subclasser, unsubclass the window
  156. * and remove the window's WindowContext from the map.
  157. */
  158. if (ctxt.Subclassers.empty() && !PhysicallyUnsubclassWindow (hwnd))
  159. {
  160. Dbg (DEB_SUBCLASS, _T("CSubclassManager zombied window 0x%08x\n"), hwnd);
  161. }
  162. return (true);
  163. }
  164. /*--------------------------------------------------------------------------*
  165. * CSubclassManager::PhysicallyUnsubclassWindow
  166. *
  167. * Physically removes CSubclassManager's subclass proc from the given
  168. * window if it is safe (or forced) to do so.
  169. *
  170. * It is safe to remove a subclass procedure A from a window W if no one
  171. * has subclassed W after A. In other words, subclasses have to be removed
  172. * in a strictly LIFO order, or there's big trouble.
  173. *
  174. * To illustrate, let's say the A subclasses W. Messages that A doesn't
  175. * handle will be passed on to W's original window procedure that was in
  176. * place when A subclassed W. Call this original procedure O. So
  177. * messages flow from A to O:
  178. *
  179. * A -> O
  180. *
  181. * Now let's say that B subclasses the W. B will pass messages on to A,
  182. * so the messages now flow like so:
  183. *
  184. * B -> A -> O
  185. *
  186. * Now say that A no longer needs to subclass W. The typical way to
  187. * unsubclass a window is to put back the original window procedure that
  188. * was in place at the time of subclassing. In A's case that was O, so
  189. * messages destined for W now flow directly to O:
  190. *
  191. * O
  192. *
  193. * This is the first problem: B has been shorted out of the window's
  194. * message stream.
  195. *
  196. * The problem gets worse when B no longer needs to subclass W. It will
  197. * put back the window procedure it found when it subclassed, namely A.
  198. * A's work no longer needs to be done, and there's no telling whether
  199. * A's conduit to O is still alive. We don't want to get into this
  200. * situation.
  201. *--------------------------------------------------------------------------*/
  202. bool CSubclassManager::PhysicallyUnsubclassWindow (
  203. HWND hwnd, /* I:window to unsubclass */
  204. bool fForce /* =false */) /* I:force the unsubclass? */
  205. {
  206. ContextMap::iterator itRemove = m_ContextMap.find(hwnd);
  207. /*
  208. * If we get here, this window had better be in the map.
  209. */
  210. ASSERT (itRemove != m_ContextMap.end());
  211. /*
  212. * If no one subclassed after CSubclassManager, it's safe to unsubclass.
  213. */
  214. if (GetWindowProc (hwnd) == SubclassProc)
  215. {
  216. const WindowContext& ctxt = itRemove->second;
  217. SetWindowProc (hwnd, ctxt.pfnOriginalWndProc);
  218. fForce = true;
  219. Dbg (DEB_SUBCLASS, _T("CSubclassManager unsubclassed window 0x%08x\n"), hwnd);
  220. }
  221. /*
  222. * Remove this window's entry from the context map if appropriate.
  223. */
  224. if (fForce)
  225. m_ContextMap.erase (itRemove);
  226. return (fForce);
  227. }
  228. /*--------------------------------------------------------------------------*
  229. * CSubclassManager::SubclassProc
  230. *
  231. *
  232. *--------------------------------------------------------------------------*/
  233. LRESULT CALLBACK CSubclassManager::SubclassProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
  234. {
  235. AFX_MANAGE_STATE (AfxGetAppModuleState());
  236. return (GetSubclassManager().SubclassProcWorker (hwnd, msg, wParam, lParam));
  237. }
  238. /*--------------------------------------------------------------------------*
  239. * CSubclassManager::SubclassProcWorker
  240. *
  241. *
  242. *--------------------------------------------------------------------------*/
  243. LRESULT CSubclassManager::SubclassProcWorker (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
  244. {
  245. /*
  246. * Get the subclass context for this window. Use map::find
  247. * instead of map::operator[] to avoid excessive overhead in
  248. * map::operator[]
  249. */
  250. ContextMap::iterator itContext = m_ContextMap.find (hwnd);
  251. /*
  252. * If we get here, this window had better be in the map.
  253. */
  254. ASSERT (itContext != m_ContextMap.end());
  255. WindowContext& ctxt = itContext->second;
  256. WNDPROC pfnOriginalWndProc = ctxt.pfnOriginalWndProc;
  257. bool fPassMessageOn = true;
  258. LRESULT rc;
  259. /*
  260. * If there are subclassers, give each one a crack at this message.
  261. * If a subclasser indicates it wants to eat the message, bail.
  262. */
  263. if (!ctxt.Subclassers.empty())
  264. {
  265. SubclasserList::iterator it;
  266. for (it = ctxt.Subclassers.begin();
  267. it != ctxt.Subclassers.end() && fPassMessageOn;
  268. ++it)
  269. {
  270. SubclasserData& subclasser = *it;
  271. subclasser.AddRef();
  272. ctxt.RemoveZombies ();
  273. /*
  274. * If this isn't a zombied subclasser, call the callback
  275. */
  276. if (!ctxt.IsZombie(subclasser))
  277. {
  278. rc = subclasser.pSubclasser->Callback (hwnd, msg,
  279. wParam, lParam,
  280. fPassMessageOn);
  281. }
  282. subclasser.Release();
  283. }
  284. ctxt.RemoveZombies ();
  285. }
  286. /*
  287. * Otherwise, we have a zombie window (see
  288. * PhysicallyUnsubclassWindow). Try to remove the zombie now.
  289. */
  290. else if (PhysicallyUnsubclassWindow (hwnd))
  291. {
  292. Dbg (DEB_SUBCLASS, _T("CSubclassManager removed zombied window 0x%08x\n"), hwnd);
  293. }
  294. /*
  295. * remove this window's WindowContext on WM_NCDESTROY
  296. */
  297. if ((msg == WM_NCDESTROY) &&
  298. (m_ContextMap.find(hwnd) != m_ContextMap.end()))
  299. {
  300. Dbg (DEB_SUBCLASS, _T("CSubclassManager forced removal of zombied window 0x%08x on WM_NCDESTROY\n"), hwnd);
  301. PhysicallyUnsubclassWindow (hwnd, true);
  302. }
  303. /*
  304. * If the last subclasser didn't eat the message,
  305. * give it to the original window procedure.
  306. */
  307. if (fPassMessageOn)
  308. rc = CallWindowProc (pfnOriginalWndProc, hwnd, msg, wParam, lParam);
  309. return (rc);
  310. }
  311. /*--------------------------------------------------------------------------*
  312. * WindowContext::IsZombie
  313. *
  314. *
  315. *--------------------------------------------------------------------------*/
  316. bool WindowContext::IsZombie (const SubclasserData& subclasser) const
  317. {
  318. /*
  319. * If this is a zombie, make sure it's in the zombie list;
  320. * if it's not, make sure it's not.
  321. */
  322. ASSERT (subclasser.fZombie == (Zombies.find(subclasser) != Zombies.end()));
  323. return (subclasser.fZombie);
  324. }
  325. /*--------------------------------------------------------------------------*
  326. * WindowContext::Zombie
  327. *
  328. * Changes the state fo a subclasser to or from a zombie.
  329. *--------------------------------------------------------------------------*/
  330. void WindowContext::Zombie (SubclasserData& subclasser, bool fZombie)
  331. {
  332. // zombie-ing a zombied subclasser?
  333. ASSERT (IsZombie (subclasser) != fZombie);
  334. subclasser.fZombie = fZombie;
  335. if (fZombie)
  336. Zombies.insert (subclasser);
  337. else
  338. Zombies.erase (subclasser);
  339. ASSERT (IsZombie (subclasser) == fZombie);
  340. }
  341. /*--------------------------------------------------------------------------*
  342. * WindowContext::Insert
  343. *
  344. *
  345. *--------------------------------------------------------------------------*/
  346. void WindowContext::Insert (SubclasserData& subclasser)
  347. {
  348. /*
  349. * This code can't handle re-subclassing by a subclasser
  350. * that's currently a zombie. If this ever becomes a requirement,
  351. * we'll need to identify the subclass instance by something other
  352. * than the CSubclasser pointer, like a unique handle.
  353. */
  354. ASSERT (Zombies.find(subclasser) == Zombies.end());
  355. /*
  356. * Subclassers get called in LIFO order, put the new
  357. * subclasser at the head of the list.
  358. */
  359. Subclassers.push_front (subclasser);
  360. }
  361. /*--------------------------------------------------------------------------*
  362. * WindowContext::Remove
  363. *
  364. * Logically removes a subclasser from the subclass chain. "Logically"
  365. * because it's not safe to totally remove a subclasser from the chain if
  366. * it's currently in use. If the subclass is in use when we want to remove
  367. * it, we'll mark it as "zombied" so it won't be used any more, to be
  368. * physically removed later.
  369. *
  370. * Returns the reference count for the subclasser.
  371. *--------------------------------------------------------------------------*/
  372. UINT WindowContext::Remove (SubclasserData& subclasser)
  373. {
  374. // we shouldn't be removing zombies this way
  375. ASSERT (!IsZombie (subclasser));
  376. /*
  377. * If this subclasser has outstanding references, zombie it instead
  378. * of removing it.
  379. */
  380. UINT cRefs = subclasser.cRefs;
  381. if (cRefs == 0)
  382. {
  383. SubclasserList::iterator itRemove = std::find (Subclassers.begin(),
  384. Subclassers.end(),
  385. subclasser);
  386. ASSERT (itRemove != Subclassers.end());
  387. Subclassers.erase (itRemove);
  388. }
  389. else
  390. {
  391. Zombie (subclasser, true);
  392. }
  393. return (cRefs);
  394. }
  395. /*--------------------------------------------------------------------------*
  396. * WindowContext::RemoveZombies
  397. *
  398. *
  399. *--------------------------------------------------------------------------*/
  400. void WindowContext::RemoveZombies ()
  401. {
  402. if (Zombies.empty())
  403. return;
  404. /*
  405. * Build up a list of zombies that we can remove. We have to build
  406. * the list ahead of time, instead of removing them as we find them,
  407. * because removing an element from a set invalidates all iterators
  408. * on the set.
  409. */
  410. SubclasserSet ZombiesToRemove;
  411. SubclasserSet::iterator itEnd = Zombies.end();
  412. SubclasserSet::iterator it;
  413. for (it = Zombies.begin(); it != itEnd; ++it)
  414. {
  415. const SubclasserData& ShadowSubclasser = *it;
  416. /*
  417. * Find the real subclasser in the Subclassers list. That's
  418. * the live one whose ref count will be correct.
  419. */
  420. SubclasserList::iterator itReal = std::find (Subclassers.begin(),
  421. Subclassers.end(),
  422. ShadowSubclasser);
  423. const SubclasserData& RealSubclasser = *itReal;
  424. if (RealSubclasser.cRefs == 0)
  425. {
  426. Dbg (DEB_SUBCLASS, _T("CSubclassManager removed zombied subclass proc for window 0x%08x\n"),
  427. RealSubclasser.hwnd);
  428. ZombiesToRemove.insert (ShadowSubclasser);
  429. Subclassers.erase (itReal);
  430. }
  431. }
  432. /*
  433. * Now remove the truly dead zombies.
  434. */
  435. itEnd = ZombiesToRemove.end();
  436. for (it = ZombiesToRemove.begin(); it != itEnd; ++it)
  437. {
  438. Zombies.erase (*it);
  439. }
  440. }