CHook class (Base Framework class : type 1)

Home| Main Subjects| Documentation| Downloads| Misscelaneous| FAQ| Search
Email me
© Amir Israeli October 2000  CHook class (Base Framework class : type 1)


CHook class: This class is based on CSubclassWnd of "Paul DiLascia" MSJ 1997.

By using this class a person can hook (trap) messages that arrive to a trapped window and direct them into his own supplied callback fubnction. Getting the messages of another window to your own handler is welcomed whenever the original window doesn't support a feature whick can be done easily with another handler. It can be done in order to modify the behaviour of original window, The point is handling the minimum messages and returning the rest to original window.
The window procedure is the target of all manipulation , as you already know messages enter the window-proc from the message loop until a message 0 (WM_NCDESTROY) comes and the loop is over (and window terminates) . If you get the address of window proc. keep it in your purse, give the tracked window (the one that you're looking at his messages) a new address of window proc . Messages to the original window will come to your purse : the the address that you keep. Now you can handle messages , and return some/all back to their target place. When the window is about to end : you get messages such as WM_DESTROY (1) WM_NCDESTROY(0) (which is the last message that a window gets before its destroyed) you need to restore everything and -ofcource- pass the termination messages to the original window so it be destroyed correctly.

Each CHook class that you instantiate maps the window procedure of 1 window. Means that for multiple windows you need multiple classes. It is supposed to be created on stack. After creating it call HookWnd(CWnd*) This class is overloaded so you can use a generic window handle as parameter. Notice : the window should be valid .
The sintax of HookWnd is:

	BOOL HookWnd(HWND m_hWnd, BOOL bHighPriority=TRUE);
	BOOL HookWnd(CWnd *pWnd, BOOL bHighPriority=TRUE) {
		if (!pWnd)	return FALSE; //no window
		return HookWnd(pWnd->GetSafeHwnd(),bHighPriority);
	}
As you see the true working function is the one that gets generic window handle and priority signal "HookWnd(HWND m_hWnd, BOOL bHighPriority=TRUE);" so the function does work with generic handles.
When a window is hooked the function replaces the address of its window proc with a callback function located in .cpp file called : member variables in CHook class keep original Window proc address and HWND (handle).
LRESULT CALLBACK
HookWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
#ifdef _USRDLL
	// If this is a DLL, need to set up MFC state
	AFX_MANAGE_STATE(AfxGetStaticModuleState());
#endif

	// Set up MFC message state just in case anyone wants it
	// This is just like AfxCallWindowProc, but we can't use that because
	// a CSubclassWnd is not a CWnd.
	//
	MSG& curMsg = AfxGetThreadState()->m_lastSentMsg;
	MSG  oldMsg = curMsg;   // save for nesting
	curMsg.hwnd	= hwnd;
	curMsg.message = msg;

	// ... doing something with messages

	return lr;
}
The members-variables in CHook class are :
	HWND  m_hHookedWnd;
	WNDPROC  m_pOldWndProc;
	CHook *m_pNextHook;
The last member indicates the the CHook maintains a list of CHook objects : Since each window may be hooked more than once there's a need to let everybody trace the messages by some order and at last let the message reach its original target.
The structure that keeps track of all hooked windows is a SingleTone (single instance object) which is a simple template map. It maps window handles to CHook objects .

The process of hook goes like that : when a new window (that doesnt already exist in map) is hooked , a new entry is added to map , original window procedure is replaced and kept in CHook. And then the handle and CHook objects are set in map : SetAt(HWND,CHook*) . Whenever a window is hooked in several places , Entry of the same HWND is looked , then the pointer of the new CHook is inserted to the previous existing CHook object. (An extension I made define 2 priorities: if high (TRUE) the new CHook is inserted at the start of CHook list. if low : to the end. (if you insert several high priority windows) well , the first that were inseted loose their high priority.

Tracking a message
Lets assume a window that is hooked several times. This means that in "CHookWndMap" object (the map that stores the hooks and hooked HWND's) the's is a list of CHook objects . at the value entry of HWND in map. (My English ...) when finding the entry of HWND the message is sent to WindowProc2 (This is a function that has the same parameter list as the WindowProc of every CWnd derived class).
In contrast to CSubclssWnd I used a different name to keep the flow of messages separated. (If for example the hook is a base class of another CWnd derived class : I wouldn't want to mix messages , and traeat every WM_SETCURSOR differently according its destination) This happen within "HookWndProc" if arriving message is WM_NCDESTROY (which means that the window is in process of destruction) it's time to destroy the window , and clear the list of hooks (or single hook) attached to the window. We also need to send this message to original window (and let it be destroyed).
the line that does this is : lr = ::CallWindowProc( wndproc, hwnd, msg, wp, lp); In case of a regular message we pas it to the first CHook in chain of hooks. The last hook in chain will pass it to the original window procedure

LRESULT CALLBACK
HookWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
	// ....

	if (msg == WM_NCDESTROY) {
		// Window is being destroyed: unhook all hooks (for this window)
		// and pass msg to orginal window proc
		//
		WNDPROC wndproc = NULL;
		if (pHook) wndproc = pHook->m_pOldWndProc;

		// release the chain of hooks : remove all pointers in list
		// doent free objects so CHook should be on stack
		CHook::UnHookWnd(hwnd);

		// Here calling original window procedure
		lr = ::CallWindowProc( wndproc, hwnd, msg, wp, lp);
	}
	else {
		// pass to the first CHook object in chain of hooks 
		// every CHook may call the next hook or not
		lr = pHook->WindowProc2(msg, wp, lp);
	}

	return lr;
}
The function that sends the message to the next hook is "NextHook" : this function first checks whether there's a next hook in chain if it does then send the message there . If it doesn't then send to the original window procedure.
// call next hook in series of hooks to a window
LRESULT CHook::NextHook(UINT msg, WPARAM wp, LPARAM lp)
{
	ASSERT(m_pOldWndProc);
	return m_pNextHook ? m_pNextHook->WindowProc2(msg, wp, lp) :	
		::CallWindowProc(m_pOldWndProc, m_hHookedWnd, msg, wp, lp);
}

Miscellaneous operations
Some other functions exist to assist the "multiple-operation" just been described.
These 2 functions determine whether an object is making a hook (Means the the CHook object is active and participates in the "game" of tracking messages). The second simple unhookes an object: removes it from map , and if it is the only hook for a certain window : removes the window from CHookWndMap. I put the second as public to give the user and program the flexibility of unhook , due to certain event. (On destruction of CHook the second function is called automaticly)

	BOOL IsHooked()	{ return ::IsWindow(m_hHookedWnd); }
	BOOL UnHook();
These functions remove a whole chain of HWND. They remove the hook and restore situation. (static and public so you can do it to every window in app)
	static BOOL UnHookWnd(HWND m_hWnd);
	static BOOL UnHookWnd(CWnd *pWnd);

Final words
Documentation of this class is relatively complete , However I concentrated more on the Why? , I didnt look deep into the details of How? .Look into the code ... The header file includes instructions how to use the class . where to instantiate etc.
This is the base class of many implementations yet to come.

















Home  |  Links  |  About