On the mechanism of message mapping and command passing in MFC

This paper is mainly based on Hou Jie's MFC in simple terms, and mainly describes the MFC message mapping and transmission mechanism.

How to form a message mapping network

1 add the code of message mapping table in the source file

First, you must declare the message mapping table in the header file (. H)

class CScribbleDoc : public CDocument
{
 ...
 DECLARE_MESSAGE_MAP()
};

Then implement this table in the implementation file (. CPP):

BEGIN_MESSAGE_MAP(CScribbleDoc, CDocument)
 //{{AFX_MSG_MAP(CScribbleDoc)
 ON_COMMAND(ID_EDIT_CLEAR_ALL, OnEditClearAll)
 ON_COMMAND(ID_PEN_THICK_OR_THIN, OnPenThickOrThin)
 ...
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

Three macros appear. First macro BEGIN_MESSAGE_MAP has two parameters, such as owning this message mapping table and its parent class. The second macro is ON_COMMAND to specify the name of the message handling function. Third macro END_MESSAGE_MAP as closing token. As for the clip in BEGIN_ And END_ The strange explanatory symbols / /}} and / / {{are generated by the ClassWizard and used to show itself.

2 construction of message mapping table

The essence of message mapping is actually a huge data structure, which is used for such as WM_ Standard messages such as paint determine the flow path so that it can flow to the parent class; Also used for WM_COMMAND, a special message, determines the flow path, so that it can flow to the side branches of the class inheritance structure in various ways. Let's look at the definition of these macros in the header file and source file.
First, declare in the header file_ MESSAGE_ Map macro is defined as:

#define DECLARE_MESSAGE_MAP() \
protected: \
	static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
	virtual const AFX_MSGMAP* GetMessageMap() const; \

Then, the macro in the implementation file is defined as:

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
	const AFX_MSGMAP* theClass::GetMessageMap() const \
		{ return GetThisMessageMap(); } \
	const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
	{ \
		typedef theClass ThisClass;		\
		typedef baseClass TheBaseClass;		\
		static const AFX_MSGMAP_ENTRY _messageEntries[] =  \
		{


#define END_MESSAGE_MAP() \
			{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
		}; \
		static const AFX_MSGMAP messageMap = \
		{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] };\
		return &messageMap; \
	}\

Where AFX_ The msgmap structure represents the address information of the message mapping table of this class and the base class, which is defined as follows:

struct AFX_MSGMAP
{
 const AFX_MSGMAP* pBaseMap;	//Address of message processing mapping table of base class
 const AFX_MSGMAP_ENTRY* lpEntries;	//The address of the message mapping table of this class is an array
};

AFX_MSGMAP_ENTRY (representing each entry of the message mapping table) is in this form

struct AFX_MSGMAP_ENTRY
{
	UINT nMessage;   // windows message
	UINT nCode;      // control code or WM_NOTIFY code
	UINT nID;        // control ID (or 0 for windows messages)
	UINT nLastID;    // used for entries specifying a range of control id's
	UINT_PTR nSig;       // signature type (action) or pointer to message #
	AFX_PMSG pfn;    // routine to call (or special value)
};

/*
AFX_MSGMAP_ENTRY include
1. Windows Message number
2. Notification code (notification code, more description of the message, such as EN_CHANGED or cbn_dropdown, etc.)
3. Control ID (command message is 0)
4. A signature mark
5. CCmdTarget The member function of the derived class, that is, the response function corresponding to the message.
*/

Any one ON_ The macro initializes these six items. For example:

#define ON_COMMAND(id, memberFxn) \
 { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)memberFxn },
#define ON_WM_CREATE() \
 { WM_CREATE, 0, 0, 0, AfxSig_is, \
 (AFX_PMSG)(AFX_PMSGW)(int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT))OnCreate },
#define ON_WM_DESTROY() \
 { WM_DESTROY, 0, 0, 0, AfxSig_vv, \
 (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))OnDestroy },
#define ON_WM_MOVE() \
 { WM_MOVE, 0, 0, 0, AfxSig_vvii, \
 (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(int, int))OnMove },
#define ON_WM_SIZE() \
 { WM_SIZE, 0, 0, 0, AfxSig_vwii, \
 (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(UINT, int, int))OnSize },
#define ON_WM_ACTIVATE() \
 { WM_ACTIVATE, 0, 0, 0, AfxSig_vwWb, \
 (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(UINT, CWnd*,
BOOL))OnActivate },
#define ON_WM_SETFOCUS() \
 { WM_SETFOCUS, 0, 0, 0, AfxSig_vW, \
 (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(CWnd*))OnSetFocus },
#define ON_WM_PAINT() \
 { WM_PAINT, 0, 0, 0, AfxSig_vv, \
 (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))OnPaint },
#define ON_WM_CLOSE() \
 { WM_CLOSE, 0, 0, 0, AfxSig_vv, \
 (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))OnClose },
...

/*
You can see that the last function pointer has three type conversions, the first static_ The cast operator can check whether the function type is
 No, it meets the requirements. The second (AFX_PMSGW) and third (AFX_PMSG) are for type safety. Specifically, if an inheritance
 If the system contains multiple inheritance, ambiguity may occur when the subclass is strongly converted to the grandfather class (for example, subclass D inherits from two parent classes B and C)
In this case, when subclass D is converted to A, the compiler will prompt A ambiguous error). total
 Therefore, the corresponding message processing function needs to be transformed into a unified AFX_PMSG form.
*/

It can be seen from the above macro definition that when defining the message mapping table, a static array is actually defined in the class, and each item in this array is AFX_ MSGMAP_ Data of type entry, while AFX_MSGMAP_ENTRY contains the message number, ID value, notification message code, signature mark and message processing function of different messages;
In addition, AFX can be obtained through GetMessageMap method_ Msgmap structure, which can obtain the message mapping table addresses of this class and the base class.
So far, our message mapping network has initially taken shape. It can be imagined that each new window class we create can have its own message mapping table, and we can also find the message mapping table of the base class. In this way, we can go back and find the default message processing function.

II. Message transmission and response

Our message mapping network has been built, so how can the window select the corresponding processing function according to the message?
First of all, after the window receives the message, the operating system calls the CWnd::OnWndMsg function of the window through the callback function. The function is implemented as follows:

BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
	LRESULT lResult = 0;
	union MessageMapFunctions mmf;
	mmf.pfn = 0;
	CInternalGlobalLock winMsgLock;
	
	// Windows messages are handled separately
	if (message == WM_COMMAND)
	{
		if (OnCommand(wParam, lParam))
		{
			lResult = 1;
			goto LReturnTrue;
		}
		return FALSE;
	}
	
	........

	// Notification messages are processed separately
	if (message == WM_NOTIFY)
	{
		NMHDR* pNMHDR = (NMHDR*)lParam;
		if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
			goto LReturnTrue;
		return FALSE;
	}
	........

	//These are the standard Windows messages
	const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();	//Find the message mapping table of the lowest subclass
	UINT iHash; iHash = (LOWORD((DWORD_PTR)pMessageMap) ^ message) & (iHashMax-1);
	winMsgLock.Lock(CRIT_WINMSGCACHE);
	AFX_MSG_CACHE* pMsgCache; pMsgCache = &_afxMsgCache[iHash];
	const AFX_MSGMAP_ENTRY* lpEntry;
	if (........)  	//Check whether it is in the cache
	{
	........
	}
	else
	{
		pMsgCache->nMsg = message;
		pMsgCache->pMessageMap = pMessageMap;

		//Please check the following loop carefully. Its code logic is straight-line backtracking, and find the detailed mapping table layer by layer,
		//Until you find it.
		for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap != NULL;
			pMessageMap = (*pMessageMap->pfnGetBaseMap)())
		{
			if (message < 0xC000)
			{
				// Start looking for qualified messages
				if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,
					message, 0, 0)) != NULL)
				{
					pMsgCache->lpEntry = lpEntry;
					winMsgLock.Unlock();
					goto LDispatch;
				}
			}
			else
			{
				// registered windows message
				lpEntry = pMessageMap->lpEntries;
				while ((lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, 0, 0)) != NULL)
				{
					UINT* pnID = (UINT*)(lpEntry->nSig);
					ASSERT(*pnID >= 0xC000 || *pnID == 0);
						// must be successfully registered
					if (*pnID == message)
					{
						pMsgCache->lpEntry = lpEntry;
						winMsgLock.Unlock();
						goto LDispatchRegistered;
					}
					lpEntry++;      // keep looking past this one
				}
			}
		}

		pMsgCache->lpEntry = NULL;
		winMsgLock.Unlock();
		return FALSE;
	}

LDispatch:
	mmf.pfn = lpEntry->pfn;	//This mysterious Union will be explained in detail later. You only need to know that this variable represents a function pointer
	switch (lpEntry->nSig)	//Via AFX_ MSGMAP_ The nSig variable in the entry determines the true function form
	{
	default:
		ASSERT(FALSE);
		break;
	case AfxSig_l_p:
		{
			CPoint point(lParam);
			lResult = (this->*mmf.pfn_l_p)(point);
			break;
		}
	case AfxSig_b_D_v:
		lResult = (this->*mmf.pfn_b_D)(CDC::FromHandle(reinterpret_cast<HDC>(wParam)));
		break;

	case AfxSig_l_D_u:
		lResult = (this->*mmf.pfn_l_D_u)(CDC::FromHandle(reinterpret_cast<HDC>(wParam)), (UINT)lParam);
		break;

	case AfxSig_b_b_v:
		lResult = (this->*mmf.pfn_b_b)(static_cast<BOOL>(wParam));
		break;

	case AfxSig_b_u_v:
		lResult = (this->*mmf.pfn_b_u)(static_cast<UINT>(wParam));
		break;

	case AfxSig_b_h_v:
		lResult = (this->*mmf.pfn_b_h)(reinterpret_cast<HANDLE>(wParam));
		break;
	... ... 
	}
	goto LReturnTrue;

LDispatchRegistered:    // for registered windows messages
	ASSERT(message >= 0xC000);
	ASSERT(sizeof(mmf) == sizeof(mmf.pfn));
	mmf.pfn = lpEntry->pfn;
	lResult = (this->*mmf.pfn_l_w_l)(wParam, lParam);

LReturnTrue:
	if (pResult != NULL)
		*pResult = lResult;
	return TRUE;
}

const AFX_MSGMAP_ENTRY* AFXAPI
AfxFindMessageEntry(const AFX_MSGMAP_ENTRY* lpEntry,
	UINT nMsg, UINT nCode, UINT nID)
{
	... ... 	//It is processed in assembly language to speed up the speed

	// C version of search routine
	while (lpEntry->nSig != AfxSig_end)
	{
		if (lpEntry->nMessage == nMsg && lpEntry->nCode == nCode &&
			nID >= lpEntry->nID && nID <= lpEntry->nLastID)
		{
			return lpEntry;
		}
		lpEntry++;
	}
	return NULL;    // not found
}

Through this function, we can see that if it is a Windows message, we adopt the method of straight-line backtracking to compare whether the message is consistent with the entries in the message mapping table one by one. The implementation function of the lookup function is AfxFindMessageEntry. In fact, this function iterates the message mapping table in this class and tries to find entries with the same nMessage, nCode and nID. If no matching entry is found in this subclass, go to the upper parent class until it is found.
So, what will you do if you find it? How to call the relevant response function? The key here is AFX_ MSGMAP_ nSig variable in entry.
The possible values of nSig variable are as follows:

enum AfxSig
{
 AfxSig_end = 0, // [marks end of message map]
 AfxSig_bD, // BOOL (CDC*)
 AfxSig_bb, // BOOL (BOOL)
 AfxSig_bWww, // BOOL (CWnd*, UINT, UINT)
 AfxSig_hDWw, // HBRUSH (CDC*, CWnd*, UINT)
 AfxSig_hDw, // HBRUSH (CDC*, UINT)
 AfxSig_iwWw, // int (UINT, CWnd*, UINT)
 AfxSig_iww, // int (UINT, UINT)
 AfxSig_iWww, // int (CWnd*, UINT, UINT)
 AfxSig_is, // int (LPTSTR)
 AfxSig_lwl, // LRESULT (WPARAM, LPARAM)
 AfxSig_lwwM, // LRESULT (UINT, UINT, CMenu*)
 AfxSig_vv, // void (void)
 AfxSig_vw, // void (UINT)
 AfxSig_vww, // void (UINT, UINT)
 AfxSig_vvii, // void (int, int) // wParam is ignored
 AfxSig_vwww, // void (UINT, UINT, UINT)
 AfxSig_vwii, // void (UINT, int, int)
 AfxSig_vwl, // void (UINT, LPARAM)
 AfxSig_vbWW, // void (BOOL, CWnd*, CWnd*)
... ...
};

The last digits of the AfxSig enumeration type represent the function type, for example, AfxSig_bWww represents that the return value of the function is bool type, and the function parameters are CWnd*, UINT and UINT in turn.
Program by viewing AFX_ MSGMAP_ The function type of the message response function in the entry can be known by the nSig variable value in the entry. As we just said, AFX is stored in the message response function table_ How can function pointers of PMSG type be converted into correct function pointers?
MFC defines all possible response function forms as a consortium variable.

union MessageMapFunctions
{
 AFX_PMSG pfn; // generic member function pointer
 // specific type safe variants
 BOOL (AFX_MSG_CALL CWnd::*pfn_bD)(CDC*);
 BOOL (AFX_MSG_CALL CWnd::*pfn_bb)(BOOL);
 BOOL (AFX_MSG_CALL CWnd::*pfn_bWww)(CWnd*, UINT, UINT);
 BOOL (AFX_MSG_CALL CWnd::*pfn_bHELPINFO)(HELPINFO*);
 HBRUSH (AFX_MSG_CALL CWnd::*pfn_hDWw)(CDC*, CWnd*, UINT);
 HBRUSH (AFX_MSG_CALL CWnd::*pfn_hDw)(CDC*, UINT);
 int (AFX_MSG_CALL CWnd::*pfn_iwWw)(UINT, CWnd*, UINT);
 int (AFX_MSG_CALL CWnd::*pfn_iww)(UINT, UINT);
 int (AFX_MSG_CALL CWnd::*pfn_iWww)(CWnd*, UINT, UINT);
 int (AFX_MSG_CALL CWnd::*pfn_is)(LPTSTR);
 LRESULT (AFX_MSG_CALL CWnd::*pfn_lwl)(WPARAM, LPARAM);
 LRESULT (AFX_MSG_CALL CWnd::*pfn_lwwM)(UINT, UINT, CMenu*);
 void (AFX_MSG_CALL CWnd::*pfn_vv)(void);
 void (AFX_MSG_CALL CWnd::*pfn_vw)(UINT);
 void (AFX_MSG_CALL CWnd::*pfn_vww)(UINT, UINT);
 void (AFX_MSG_CALL CWnd::*pfn_vvii)(int, int);
 void (AFX_MSG_CALL CWnd::*pfn_vwww)(UINT, UINT, UINT);
 void (AFX_MSG_CALL CWnd::*pfn_vwii)(UINT, int, int);
 ... ...
};

Then it is very easy. If you know the matching entries in the message mapping table, you can know the address of the message response function corresponding to the message (that is, the name of the function). Then, according to AFX_ MSGMAP_ The nSig variable value in the entry. Just select the appropriate member in the consortium. Here is an example.

static BOOL DispatchCmdMsg(CCmdTarget* pTarget, UINT nID, int nCode,
 AFX_PMSG pfn, void* pExtra, UINT nSig, AFX_CMDHANDLERINFO* pHandlerInfo)
 // return TRUE to stop routing
{
	union MessageMapFunctions mmf;	//Define a union variable, which is actually a function pointer
	mmf.pfn = pfn;
	BOOL bResult = TRUE; // default is ok
	... ...
	
 	//The following is based on AFX_ MSGMAP_ The nSig variable value in the entry of entry, and the correct form and parameters of the calling function.
	//Note: the parameters of the function are actually originally passed in by wParam and lParam in the message body
	switch (nSig)
	{
	default:    // illegal
		ASSERT(FALSE);
		return 0;
		break;

	case AfxSigCmd_v:
		// normal command or control notification
		ASSERT(CN_COMMAND == 0);        // CN_COMMAND same as BN_CLICKED
		ASSERT(pExtra == NULL);
		(pTarget->*mmf.pfnCmd_v_v)();
		break;

	case AfxSigCmd_b:
		// normal command or control notification
		ASSERT(CN_COMMAND == 0);        // CN_COMMAND same as BN_CLICKED
		ASSERT(pExtra == NULL);
		bResult = (pTarget->*mmf.pfnCmd_b_v)();
		break;

	case AfxSigCmd_RANGE:
		// normal command or control notification in a range
		ASSERT(CN_COMMAND == 0);        // CN_COMMAND same as BN_CLICKED
		ASSERT(pExtra == NULL);
		(pTarget->*mmf.pfnCmd_v_u)(nID);
		break;

	case AfxSigCmd_EX:
		// extended command (passed ID, returns bContinue)
		ASSERT(pExtra == NULL);
		bResult = (pTarget->*mmf.pfnCmd_b_u)(nID);
		break;
	... ...
}

This is a moving scene of MFC. Different messages, parameters and return values are different, and they are only a pointer at the time of definition, but there are various ways of calling. With a union variable, all different morphological functions are unified into one, which is great.
But why? In order to reduce the spatial complexity of the program. If we use virtual functions commonly used in C + + to realize polymorphism, we need to maintain a virtual function table in the class, so as to realize the effect of calling subclass methods by base class pointers. Due to the large number of Windows messages, maintaining a virtual function table in each class will greatly increase the space complexity of the program.

Third, the last question is the usage of member dereference (- > *) operator

There is one last question left, such as the statement in the above function

	case AfxSigCmd_v:
		(pTarget->*mmf.pfnCmd_v_v)();
		break;

pTarget is a CCmdTarge pointer, MMF pfnCmd_ v_ V pointer types are as follows:

void (AFX_MSG_CALL CCmdTarget::*pfnCmd_v_v)();

It can be seen that this is a base class pointer, which calls a base class method. How can we finally call the method of a subclass? The key here is to understand the member dereference (- > *) operator. When the base class pointer uses the member dereference operator, the base class pointer (actually pointing to the subclass object) presses the this pointer on the stack, and then the program goes directly to the address of the subclass member function. Therefore, the corresponding class method of the subclass is found.
The following code pattern describes this process.

#include<iostream>
using namespace std;

class CBase
{
public:
	void BasePrintMsg() {
		cout << "In Base class" << endl;
	}
};

class CDerive :public CBase
{
public:
	CDerive() :m_iDerive(3) {}
	int GetInt(int m) {
		return m_iDerive * m;
	}

	double GetDouble(int m, double d) {
		return m * d;
	}
private:
	int m_iDerive;
};

typedef void (CBase::* pBaseVoidFun)();

union UMapFuns
{
	//Note that the following three members are function pointers to member functions of the base class
	pBaseVoidFun pfn;
	double (CBase::*pfn_double)(int m, double d);
	int (CBase::*pfn_int)(int m);
};

int main()
{
	UMapFuns uMapFun;	//Learn from MFC and define a union variable to represent the function pointer
	uMapFun.pfn = (pBaseVoidFun)&CDerive::GetInt;	//The subclass method is transferred to the base class method and assigned to the consortium

	CDerive cDeriveObj;
	CBase* pBase = &cDeriveObj;
	
	//The key to successful call is to understand umapfun pfn_ The address of int is actually the address of subclass member function, subclass object
	//When calling the dereference operator, you just stack the address of the subclass object so that the subclass member function can
	//Address found another member of the class.
	int i = (cDeriveObj.*(uMapFun.pfn_int))(3);	
	
	//The call is successful, and the subclass function is called with the base class pointer. The key is to understand umapfun pfn_ The address of int is actually a subclass
	//The address of the member function. The base class pointer points to the address of the subclass object. After the address is pressed on the stack,
	//The data members of the class found by the subclass member function according to this address are correct.
	int i2 = (pBase->*(uMapFun.pfn_int))(4);	

	uMapFun.pfn = (pBaseVoidFun)&CDerive::GetDouble;
	double d = (pBase->*(uMapFun.pfn_double))(3, 4.0);	//The call is successful, and the subclass function is called with the base class pointer

	return 0;
}

That is all!

Keywords: C++ MFC microsoft

Added by EvilWalrus on Sun, 13 Feb 2022 15:07:59 +0200