上节对 WndProc 已经讲解了,不过后面的部分应该有点难懂,因为 句柄 还是没谈到。
下面将来了解一下什么是 句柄 以及 句柄的作用。
一个句柄是指使用的一个唯一的整数值,即一个4字节(64位程序中为8字节)长的数值,来标识应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。应用程序能够通过句柄访问相应的对象的信息,但是句柄不是指针,程序不能利用句柄来直接阅读文件中的信息。如果句柄不在I/O文件中,它是毫无用处的。句柄是Windows用来标志应用程序中建立的或是使用的唯一整数,Windows大量使用了句柄来标识对象。
句柄与普通指针的区别在于,指针包含的是引用对象的内存地址,而句柄则是由系统所管理的引用标识,该标识可以被系统重新定位到一个内存地址上。这种间接访问对象的模式增强了系统对引用对象的控制。
WINDOWS程序中并不是用物理地址来标识一个内存块,文件,任务或动态装入模块的。相反,WINDOWS API给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。
一个句柄,只有当唯一地确定了一个项目的时候,它才开始有意义。句柄对应着项目表中的一项,而只有WINDOWS本身才能直接存取这个表,应用程序只能通过API函数来处理不同的句柄。
总而言之:句柄就是对窗口及窗口的控件进行标识的,如果要改变窗口或它的控件的话,就需要指定窗口或控件对它进行改变。
这样说应该明白了吧。
让我们再来看一看之前的代码吧!
#include<windows.h> #include<mmsystem.h> #pragma comment(lib,"WINMM.LIB") LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("HelloWin"); HWND hwnd; MSG msg; WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; if (!RegisterClass(&wndclass)) { MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR); return 0; } hwnd = CreateWindow(szAppName, TEXT("The Hello Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_U SEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } system("pause"); return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rect; switch (message) { case WM_CREATE: PlaySound(TEXT("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC); // 播放声音文件 return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); // 标明窗口绘制开始 GetClientRect(hwnd, &rect); // 获取窗口客户区尺寸 DrawText(hdc, TEXT("Hello,Windows 98"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); // 显示一个文本字符串 EndPaint(hwnd, &ps); // 结束窗口绘制 return 0; case WM_DESTROY: PostQuitMessage(0); // 将 “退出” 消息插入消息队列 return 0; } return DefWindowProc(hwnd, message, wParam, lParam); // 返回 默认的消息处理的结果 }
或许你早已运行该程序了,发现没有声音!其实是声音文件的位置错了。
你可以把TEXT("hellowin.wav") 改成 TEXT("C:\\Windows\\media\\Ring03.wav")
因为在 C:\\Windows\\media 这个目录下有许多的 wav 型的文件。这样就会有声音了。
上一节的 DrawText() 函数也没讲到,这个地方读者可以自己查找一下资料。
现在我们再来对这个WndProc 进行一个整体的认识:
这个函数有一个句柄,一个绘制客户区信息的结构体,还有两个附加信息的结构体变量。
在 switch 语句中总共也就处理了 3 种消息,分别是 窗口创建消息,窗口重绘消息,窗口摧毁消息。
这三种消息时所用 Windows 程序所不可缺少的。而在处理窗口重绘消息时,几乎总是必不可少的伴随着
BeginPaint() 和 EndPaint() ,而且这两个函数总是成对存在的。
窗口如果要摧毁的话,必须调用 PostQuitMessage() 来终止线程。
最后窗口过程把一些不关心的消息都交给默认的窗口过程函数处理,也就是 DefWindProc() 。
那再来看一下主体部分的代码吧!
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("HelloWin"); HWND hwnd; MSG msg; WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // LoadIcon() 加载图标 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); // LoadCursor() 加载鼠标 wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // GetStockObject() 获取一个图形对象 wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; if (!RegisterClass(&wndclass)) // RegisterClass() 为应用程序的窗口注册一个窗口类 { MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR); //显示消息框 return 0; } hwnd = CreateWindow(szAppName, TEXT("The Hello Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_U SEDEFAULT, NULL, NULL, hInstance, NULL); // 基于窗口类创建一个窗口 ShowWindow(hwnd, iCmdShow); // 显示窗口 UpdateWindow(hwnd); // 指示窗口对其自身进行重绘 while (GetMessage(&msg, NULL, 0, 0)) // GetMessage() 从消息队列获取消息 { TranslateMessage(&msg); // 翻译键盘消息 DispatchMessage(&msg); // 将消息发送给窗口过程 } system("pause"); return msg.wParam;
这下我猜你可能看不懂 MSG msg;
MSG是Windows程序中的结构体。在Windows程序中,消息是由MSG结构体来表示的。成员变量含义:
typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG;
从名字上看它感觉是个类,但是C语言里是没有类的,其实他也是个结构体!
WNDCLASS是一个由系统支持的结构,用来储存某一类窗口的信息,如ClassStyle,消息处理函数,Icon,Cursor,背景Brush等。也就是说,CreateWindow只是将某个WNDCLASS定义的窗体变成实例。
构WNDCLASS包含一个窗口类的全部信息,也是Windows编程中使用的基本数据结构之一,应用程序通过定义一个窗口类确定窗口的属性
我们再来看一下它的结构体成分:
typedef struct _WNDCLASS { UINT style;// 窗口类型 WNDPROC lpfnWndProc;//窗口处理函数 int cbClsExtra;//窗口扩展 int cbWndExtra;//窗口实例扩展 HINSTANCE hInstance;//实例句柄 HICON hIcon;//窗口的最小化图标 HCURSOR hCursor;//窗口鼠标光标 HBRUSH hbrBackground;//窗口背景色 LPCTSTR lpszMenuName;//窗口菜单 LPCTSTR lpszClassName;// 窗口类名 } WNDCLASS, *LPWNDCLASS;
看到这个结构体,那么它下面的一部分内容是不是也能理解了呢!
后面的是它的一些值,由于本节侧重点不在这里,这里就不介绍了,请大家自己动手了解一下!!!
下面请大家再看到 RegisterClass()
RegisterClass注册后再调用CreateWindow函数中使用的窗口类。
该函数的定义如下:
ATOM RegisterClass(__inCONST WNDCLASS *lpWndClass);
lpWndClass:指向一个WNDCLASS结构的指针。在将它传递给函数之前,必须在该结构中填充适当的类属性。
ATOM类型为Windows中定义的新数据类型,其即unsigned short类型。
如果函数成功,返回值是唯一标识已注册的类的一个原子;如果函数失败,返回值为0。若想获得更多错误信息,请调用GetLastError函数。
可以看到如果没有注册成功的话,MessageBox() 函数就会给出提示!
至于为啥会注册失败,那是因为很久以前Windous98 不支持宽字符啦!
再来看函数余下部分,现在发现就只剩下几个函数不清楚的了!
hwnd = CreateWindow(szAppName, TEXT("The Hello Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_U SEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } system("pause"); return msg.wParam;
来看一下CreatWindow() 函数:
windowsAPI函数,在WinUser.h中根据是否已定义Unicode被分别定义为CreateWindowW和CreateWindowA,然后两者又被分别定义为对CreateWindowExW和CreateWindowExA函数的调用
来看一下函数原型:
HWND CreateWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HANDLE hlnstance, LPVOID lpParam);
主要参数:
lpClassName
备注:
在返回前,CreateWindow给窗口过程发送一个WM_CREATE消息。对于层叠,弹出式和子窗口,CreateWindow给窗口发送WM_CREATE,WM_GETMINMAXINFO和WM_NCCREATE消息。消息WM_CREATE的IParam参数包含一个指向CREATESTRUCT结构的指针。如果指定了WS_VISIBLE风格,CreateWindow向窗口发送所有需要激活和显示窗口的消息。
接下来就看ShowWindow() 函数了。
函数功能:该函数设置指定窗口的显示状态。
函数原型:BOOL ShowWindow(HWND hWnd, int nCmdShow);
hWnd:指窗口句柄。
返回值:
如果窗口之前可见,则返回值为非零。如果窗口之前被隐藏,则返回值为零。
备注:
应用程序第一次调用ShowWindow时,应该使用WinMain函数的nCmdshow参数作为它的nCmdShow参数。在随后调用ShowWindow函数时,必须使用列表中的一个给定值,而不是由WinMain函数的nCmdSHow参数指定的值。
同样这两个函数关于值的讲解请自行百度。
大家再看 UpdateWindow() 函数。从名字上就知道是个更新窗口的函数!
如果窗口更新的区域不为空,UpdateWindow函数就发送一个WM_PAINT消息来更新指定窗口的客户区。函数绕过应用程序的消息队列,直接发送WM_PAINT消息给指定窗口的窗口过程,如果更新区域为空,则不发送消息。
函数原型:
);
如果函数调用成功,返回值为非零值。
如果函数调用不成功,返回值为零。
现在就只剩下这几个函数了:
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } system("pause"); return msg.wParam;
GetMessage() :
GetMessage是从调用线程的消息队列里取得一个消息并将其放于指定的结构。此函数可取得与指定窗口联系的消息和由PostThreadMessage寄送的线程消息。此函数接收一定范围的消息值。GetMessage不接收属于其他线程或应用程序的消息。获取消息成功后,线程将从消息队列中删除该消息。函数会一直等待直到有消息到来才有返回值。
函数声明:
GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax)
备注:
循环里的函数:
TranslateMessage函数用于将虚拟键消息转换为字符消息。字符消息被寄送到调用线程的消息队列里,当下一次线程调用函数GetMessage或PeekMessage时被读出
函数原型:
DispatchMessage():
函数功能:该函数分发一个消息给窗口程序。通常消息从GetMessage函数获得或者TranslateMessage函数传递的。消息被分发到回调函数(过程函数),作用是消息传递给操作系统,然后操作系统去调用我们的回调函数,也就是说我们在窗体的过程函数中处理消息。
函数原型:LONG DispatchMessage(CONST MSG*lpmsg);
下节内容将对整个程序剖析,下次再见啦!
编辑器还不太会用,又出现了这个。。。