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.

243 lines
5.9 KiB

  1. // MsgHook.cpp: implementation of the CMsgHook class.
  2. //
  3. //////////////////////////////////////////////////////////////////////
  4. #include "stdafx.h"
  5. #include "MsgHook.h"
  6. #ifdef _DEBUG
  7. #define new DEBUG_NEW
  8. #undef THIS_FILE
  9. static char THIS_FILE[] = __FILE__;
  10. #endif
  11. IMPLEMENT_DYNAMIC(CMsgHook, CObject)
  12. // This trick is used so the hook map isn't
  13. // instantiated until someone actually requests it.
  14. //
  15. #define theHookMap (CMsgHookMap::GetHookMap())
  16. //////////////////////////////////////////////////////////////////////
  17. // Construction/Destruction
  18. //////////////////////////////////////////////////////////////////////
  19. CMsgHook::CMsgHook()
  20. {
  21. m_pNext = NULL;
  22. m_pOldWndProc = NULL;
  23. m_pWndHooked = NULL;
  24. }
  25. CMsgHook::~CMsgHook()
  26. {
  27. ASSERT(m_pWndHooked == NULL); // can't destroy while still hooked!
  28. ASSERT(m_pOldWndProc == NULL);
  29. }
  30. //////////////////
  31. // Hook a window.
  32. // This installs a new window proc that directs messages to the CMsgHook.
  33. // pWnd=NULL to remove.
  34. //
  35. BOOL CMsgHook::HookWindow(CWnd* pWnd)
  36. {
  37. if (pWnd)
  38. {
  39. // Hook the window
  40. ASSERT(m_pWndHooked == NULL);
  41. HWND hwnd = pWnd->m_hWnd;
  42. ASSERT(hwnd && ::IsWindow(hwnd));
  43. theHookMap.Add(hwnd, this); // Add to map of hooks
  44. }
  45. else
  46. {
  47. // Unhook the window
  48. ASSERT(m_pWndHooked!=NULL);
  49. theHookMap.Remove(this); // Remove from map
  50. m_pOldWndProc = NULL;
  51. }
  52. m_pWndHooked = pWnd;
  53. return TRUE;
  54. }
  55. //////////////////
  56. // Window proc-like virtual function which specific CMsgHooks will
  57. // override to do stuff. Default passes the message to the next hook;
  58. // the last hook passes the message to the original window.
  59. // You MUST call this at the end of your WindowProc if you want the real
  60. // window to get the message. This is just like CWnd::WindowProc, except that
  61. // a CMsgHook is not a window.
  62. //
  63. LRESULT CMsgHook::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
  64. {
  65. ASSERT(m_pOldWndProc);
  66. return m_pNext ? m_pNext->WindowProc(msg, wp, lp) :
  67. ::CallWindowProc(m_pOldWndProc, m_pWndHooked->m_hWnd, msg, wp, lp);
  68. }
  69. //////////////////
  70. // Like calling base class WindowProc, but with no args, so individual
  71. // message handlers can do the default thing. Like CWnd::Default
  72. //
  73. LRESULT CMsgHook::Default()
  74. {
  75. // MFC stores current MSG in thread state
  76. MSG& curMsg = AfxGetThreadState()->m_lastSentMsg;
  77. // Note: must explicitly call CMsgHook::WindowProc to avoid infinte
  78. // recursion on virtual function
  79. return CMsgHook::WindowProc(curMsg.message, curMsg.wParam, curMsg.lParam);
  80. }
  81. //////////////////
  82. // Subclassed window proc for message hooks. Replaces AfxWndProc (or whatever
  83. // else was there before.)
  84. //
  85. LRESULT CALLBACK HookWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
  86. {
  87. #ifdef _USRDLL
  88. // If this is a DLL, need to set up MFC state
  89. AFX_MANAGE_STATE(AfxGetStaticModuleState());
  90. #endif
  91. // Set up MFC message state just in case anyone wants it
  92. // This is just like AfxCallWindowProc, but we can't use that because
  93. // a CMsgHook is not a CWnd.
  94. //
  95. MSG& curMsg = AfxGetThreadState()->m_lastSentMsg;
  96. MSG oldMsg = curMsg; // save for nesting
  97. curMsg.hwnd = hwnd;
  98. curMsg.message = msg;
  99. curMsg.wParam = wp;
  100. curMsg.lParam = lp;
  101. // Get hook object for this window. Get from hook map
  102. CMsgHook* pMsgHook = theHookMap.Lookup(hwnd);
  103. ASSERT(pMsgHook);
  104. LRESULT lr;
  105. if (msg==WM_NCDESTROY)
  106. {
  107. // Window is being destroyed: unhook all hooks (for this window)
  108. // and pass msg to orginal window proc
  109. //
  110. WNDPROC wndproc = pMsgHook->m_pOldWndProc;
  111. theHookMap.RemoveAll(hwnd);
  112. lr = ::CallWindowProc(wndproc, hwnd, msg, wp, lp);
  113. }
  114. else
  115. {
  116. // pass to msg hook
  117. lr = pMsgHook->WindowProc(msg, wp, lp);
  118. }
  119. curMsg = oldMsg; // pop state
  120. return lr;
  121. }
  122. ////////////////////////////////////////////////////////////////
  123. // CMsgHookMap implementation
  124. CMsgHookMap::CMsgHookMap()
  125. {
  126. }
  127. CMsgHookMap::~CMsgHookMap()
  128. {
  129. ASSERT(IsEmpty()); // all hooks should be removed!
  130. }
  131. //////////////////
  132. // Get the one and only global hook map
  133. //
  134. CMsgHookMap& CMsgHookMap::GetHookMap()
  135. {
  136. // By creating theMap here, C++ doesn't instantiate it until/unless
  137. // it's ever used! This is a good trick to use in C++, to
  138. // instantiate/initialize a static object the first time it's used.
  139. //
  140. static CMsgHookMap theMap;
  141. return theMap;
  142. }
  143. /////////////////
  144. // Add hook to map; i.e., associate hook with window
  145. //
  146. void CMsgHookMap::Add(HWND hwnd, CMsgHook* pMsgHook)
  147. {
  148. ASSERT(hwnd && ::IsWindow(hwnd));
  149. // Add to front of list
  150. pMsgHook->m_pNext = Lookup(hwnd);
  151. SetAt(hwnd, pMsgHook);
  152. if( pMsgHook->m_pNext == NULL )
  153. {
  154. // If this is the first hook added, subclass the window
  155. pMsgHook->m_pOldWndProc =
  156. (WNDPROC)SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)HookWndProc);
  157. }
  158. else
  159. {
  160. // just copy wndproc from next hook
  161. pMsgHook->m_pOldWndProc = pMsgHook->m_pNext->m_pOldWndProc;
  162. }
  163. ASSERT(pMsgHook->m_pOldWndProc);
  164. }
  165. //////////////////
  166. // Remove hook from map
  167. //
  168. void CMsgHookMap::Remove(CMsgHook* pUnHook)
  169. {
  170. HWND hwnd = pUnHook->m_pWndHooked->GetSafeHwnd();
  171. ASSERT(hwnd && ::IsWindow(hwnd));
  172. CMsgHook* pHook = Lookup(hwnd);
  173. ASSERT(pHook);
  174. if( pHook == pUnHook )
  175. {
  176. // hook to remove is the one in the hash table: replace w/next
  177. if( pHook->m_pNext )
  178. {
  179. SetAt(hwnd, pHook->m_pNext);
  180. }
  181. else
  182. {
  183. // This is the last hook for this window: restore wnd proc
  184. RemoveKey(hwnd);
  185. SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)pHook->m_pOldWndProc);
  186. }
  187. }
  188. else
  189. {
  190. // Hook to remove is in the middle: just remove from linked list
  191. while (pHook->m_pNext!=pUnHook)
  192. pHook = pHook->m_pNext;
  193. ASSERT(pHook && pHook->m_pNext==pUnHook);
  194. pHook->m_pNext = pUnHook->m_pNext;
  195. }
  196. }
  197. //////////////////
  198. // Remove all the hooks for a window
  199. //
  200. void CMsgHookMap::RemoveAll(HWND hwnd)
  201. {
  202. CMsgHook* pMsgHook;
  203. while ((pMsgHook = Lookup(hwnd))!=NULL)
  204. pMsgHook->HookWindow(NULL); // (unhook)
  205. }
  206. /////////////////
  207. // Find first hook associate with window
  208. //
  209. CMsgHook* CMsgHookMap::Lookup(HWND hwnd)
  210. {
  211. CMsgHook* pFound = NULL;
  212. if( ! CMapPtrToPtr::Lookup(hwnd,(void*&)pFound) )
  213. return NULL;
  214. ASSERT_KINDOF(CMsgHook, pFound);
  215. return pFound;
  216. }