windows是基于“事件驱动,消息响应”的一个操作系统,用户使用外部设备对OS产生一个事件(例如:鼠标点击,键盘按键等),OS会把这个事件封装成一个消息(对应一个数据结构),然后由系统调用用户设置的该消息的响应函数(这个响应函数是一个回调函数)完成整个操作过程。可以看出,这个过程中用户只需要“产生事件”和“为这个事件对应的消息绑定一个回调函数,这个回调函数是给系统来调用的,这也是为什么叫“回调”的原因。
在进行windows SDK编程的过程中,我们再整个变成过程都是在按照这个过程在进行编程:为消息编写响应函数。例如创建一个菜单,为这个菜单的菜单项对应的”点击“事件对应的消息绑定回调函数,当这个菜单的某一个菜单项被点击的时候就会调用我们事先设计的回调函数了。
在我们使用windows MFC 来进行编程的时候,因为MFC的封装性,把我们在使用sdk编程过程中的API使用C++语言进行了封装,导致这个“事件驱动,消息响应”的过程不是那么明显了,但是实际上,MFC只是在sdk的基础上加上了一层而已,底层还是那些API最终完成我们的任务。
在最初使用MFC编程的时候,很多人都被vc向导生成的MFC代码吓到,一堆宏和不知代表什么意思的类。但是当我们慢慢进行编程的时候就会发现原来sdk编程的过程还是那么明显,只是更加方便了,不需要我们写更多重复的代码了。当我们需要对一个菜单增加消息响应的代码时,这个时候我们也要遵循MFC接口的规则(也可以说是接口)了,这些规则的目的就是就是完成我们sdk编程一样的目的。例如下图的代码:
在两个宏之间加了很多在MFC中称为“消息映射”的技术,这个技术把消息与响应函数结合下了一起,例如上面的
ON_BN_CLICKED(IDC_BUTTON_START_SERVER , OnButtonStartServer ) 这一句就是把一个按钮的单击事件与函数OnButtonStartServer结合在了一起,当这个按钮被敲击的时候系统就会调用这个函数。
关于消息映射这一技术当大家在深入学习MFC的时候会体会到其内在的秘密的,其实很简单,就是几个宏在作祟。
说了这么多,貌似跑题了,我们下面来说一下windows的消息响应的过程:
(1)操作系统接收到应用程序的窗口消息,将消息投递到消息队列中
(2)应用程序在
消息队列中调用GetMessage函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理,例如,放弃对某些消息的响应,或者调用
TransslateMessage产生新的消息。
(3)应用程序调用DispacheMessage,将消息传回给操作系统。消息是使用MSG结构来表示的,其中包含了接收消息的窗口的句柄。因此DisPacheMessage函数总能进行正确的投递。
(4)系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理。
以上就是windows系统对消息的处理过程。
结合代码我们来进行分析:
使用vc/vs创建一个简单的win32 application的工程。
函数的入口是WimMain:
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
在这个主函数中主要是一个消息循环,Message Loop。
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
在主函数中有一个窗机注册函数,这个函数主要是“私人定制”一个窗口的类型,包含所有特征,还包含了窗口处理过程,这是核心:
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_TEST_WINDOWS_MESSAGE);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = (LPCSTR)IDC_TEST_WINDOWS_MESSAGE;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
return RegisterClassEx(&wcex);
}
在注册这个窗口过程后就可以实例化这个窗口了:
//
// FUNCTION: InitInstance(HANDLE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
这个窗口过程函数如下:
//
// FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
TCHAR szHello[MAX_LOADSTRING];
LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
RECT rt;
GetClientRect(hWnd, &rt);
DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
初学者可能会对这个过程很费解,这里推荐给大家两本书学习:孙鑫《深入详解VC++》和侯捷《深入浅出MFC》。