home *** CD-ROM | disk | FTP | other *** search
/ io Programmo 23 / IOPROG_23.ISO / SOFT / VFORM.ZIP / Samples / VfbEx / MsgHook.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  1998-10-10  |  6.9 KB  |  253 lines

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