windows程序内部运行机制
阅读孙鑫老师的《VC++深入详解》第一章做的笔记。
1. 创建一个Win32应用程序的步骤
1) 编写WinMain函数,可在MSDN上复制
2) 设计窗口类WNDCLASS
3) 注册窗口类RegisterClass
4) 创建窗口CreateWindow
5) 显示窗口ShowWindow
6) 更新窗口UpdateWindow
7) 写消息循环while(GetMessage(&msg,NULL,0,0))…
8) 写窗口过程函数用于处理消息
2.WinMain函数
int WINAPI WinMain(
HINSTANCE hInstance, //该程序当前运行的实例的句柄,一个数值
HINSTANCE hPreInstance, //当前实例的前一个实例的句柄
LPSTR lpCmdLine, //以空终止的字符串,指定传递给应用程序的命令行参数
int nCmdShow //指定应用程序的窗口应该如何显示
);
3.设计窗口类(一个重要的结构体:WNDCLASS)
typedef struct {
UINT style; //窗口样式
WNDPROC lpfnWndProc; //回调函数(函数指针)
int cbClsExtra; //类附加空间
int cbWndExtra; //窗口附加空间
HINSTANCE hInstance; //包含窗口过程的程序实例句柄
HICON hIcon; //窗口类的图标句柄(必须是一个图标资源的句柄)
HCURSOR hCursor; //指定窗口类的光标句柄
HBRUSH hbrBackground; //指定窗口类的背景画刷句柄
LPCTSTR lpszMenuName; //一个以空终止的字符串,指定菜单资源名
LPCTSTR lpszClassName; //指定窗口类的名字
} WNDCLASS, *PWNDCLASS;
(1) 窗口样式 Windows Class Styles
几种常见的窗口样式取值及其含义
CS_BYTEALIGNCLIENT | 在字节边界上对齐窗体的客户区 |
CS_BYTEALIGNWINDOW | 在字节边界上对齐窗体 |
CS_DBLCLKS | 用户在窗体内双击时,发送一个double-click消息到window procedure(窗口过程) |
CS_DROPSHADOW | 窗口阴影效果? |
CS_GLOBALCLASS | 应用程序全局类 |
CS_HREDRAW | 窗口水平宽度变化时,重绘窗口 |
CS_VREDRAW | 窗口垂直高度变化时,重绘窗口 |
CS_NOCLOSE | 不显示关闭按钮 |
CS_OWNDC | 为此类中的每一个窗体分配唯一的设备环境 |
CS_PARENTDC | 在父窗体上设置经裁剪的矩形,以使子窗体可以在父窗体上绘图 |
CS_CLASSDC | 分配一个设备环境并被类中的所有窗体共享 |
CS_SAVEBITS | 将屏幕图像中被该(窗口)类窗口遮挡的部分保存为一个位图。当窗口移走时,系统用保存的位图来还原屏幕图像,包括被遮挡住的其他窗口。因此,如果位图占用的内存没被释放且其他屏幕操作宣布存储图像无效,系统不发送WM_PAINT消息至那些被遮挡的窗口。 此窗口风格对那些短暂显示并在其他屏幕活动发生前移除的小窗口(如菜单或对话框)很有用。这一风格增加了显示窗口所需的时间,因为系统必须先分配内存来存储位图。 |
注:在windows.h中,以CS_开头的类样式(Class Style)标识符被定义为16位常量,这些常量都只有某1位为1。
去掉变量中某个位标识:先对该位标识取反,再和变量进行与操作。
想去掉先前的style变量所具有的CS_VREDRAW样式,可编写代码:style = style &~ CS_VREDRAW。
PS:异或运算—不同为1,相同为0
(2) 回调函数
窗口过程函数是一个回调函数:回调函数不是由该函数的实现方直接调用,而是在特定事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。
WNDPROC定义:
typedef LRESULT (CALLBACK *WNDPROC) (HWND, UINT, WPARAM, LPARAM);
WNDPROC实际是函数指针类型
(3) 类附加空间
windows为系统中的每一个窗口类管理一个WNDCLASS结构,在应用程序注册一个窗口类时,它可以让Windows系统为WNDCLASS结构分配和追加一定字节数的附加内存空间,这部分内存空间称为类附加内存,由属于这种窗口类的所有窗口所共享,类附加内存空间用于存储类的附加信息。Windows系统把这部分内存初始化为0,一般将这个参数设置为0。
(4) 窗口附加空间
Windows系统为每一个窗口管理一个内部数据结构,在注册一个窗口类时,应用程序能够指定一定字节数的附加内存空间,称为窗口附加内存。在创建这类窗口时,Windows系统就为窗口的结构分配和追加指定数据的窗口附加内存空间,应用程序可用这部分内存存储窗口特有的数据。
(5) 包含窗口过程的程序实例句柄
(6) 窗口类的图标句柄
在为hIcon变量赋值时,可以调用LoadIcon函数来加载一个图标资源,返回系统分配给该图标的句柄:
HICON LoadIcon(HINSTANCE hInstance, LPCTSTR lpIconName);
LTCTSTR被定义为CONST CHAR*,而图标的ID是一个整数,对于这种情况需要用MAKEINTRESOURCE宏把资源ID标识符转换为需要的LPCTSTR类型。
(7) 指定窗口类的光标句柄
指定窗口类的光标句柄,必须是一个光标资源的句柄,如果设置为NULL,那么无论何时鼠标进入到应用程序窗口中,应用程序都必须明确地设置光标的形状。可以调用LoadCursor函数来加载一个光标资源,返回系统分配给该光标的句柄。声明:
HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName);
(8) 背景画刷句柄
指定窗口类的背景画刷句柄。窗口发生重绘时,系统使用这里指定的画刷来擦除窗口的背景;可以指定一个画刷句柄或一个标准系统颜色值。
可以调用GetStockObject函数来得到系统的标准画刷,原型:
HGDIOBJ GetStockObject(int fnObject);
GetStockObject函数不仅可以用于获取画刷的句柄,还可以用于获取画笔、字体和调色板的句柄。由于GetStockObject函数可以返回多种资源对象的句柄,在实际调用该函数前无法确定它返回哪一种资源对象的句柄,因此它的返回值类型定义为HGDIOBJ,在实际使用时,需要进行类型转换。
例如:要为hbrBackground成员指定一个黑色画刷的句柄,
wndclass.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
(9) 菜单资源名
如果使用菜单资源的ID号,需要用MAKEINTRESOURCE宏来进行转换。如果将其设置为NULL,则基于这个窗口类创建的窗口将没有默认的菜单。
(10) 窗口类的名字
4. 注册窗口类
设计完窗口类(WNDCLASS)后,需要调用RegisterClass函数对其进行注册,注册成功后,才可以创建该类型的窗口。
ATOM RegisterClass(CONST WNDCLASS *lpWndClass);
5.创建窗口
HWND CreateWindow(
LPCTSTR lpClassName, //窗口类的名称
LPCTSTR lpWindowName, //窗口的名字
DWORD dwStyle, //窗口样式
int x, //左上角x坐标,若设为CW_USEDEFAULT,忽略y
int y,
int nWidth, //窗口宽度,若设为CW_USEDEFAULT,忽略nHeight
int nHeight,
HWND hWndParent, //父窗口句柄
HMENU hMenu, //窗口菜单句柄
HINSTANCE hInstance, //窗口所属的应用程序实例的句柄
LPVOID lpParam //作为WM_CREATE消息的附加参数lParam传入的数据指针
);
6.显示窗口
BOOL ShowWindow(
HWND hWnd, //窗口句柄
int nCmdShow //显示状态
);
7.更新窗口
BOOL UpdateWindow(
HWND hWnd // handle to window
);
8.消息循环
(1) GetMessage()函数
从消息队列中取出消息,需要调用GetMessage()函数;此函数接收到除WM_QUIT外的消息均返回非零值,对WM_QUIT消息,返回0;如果出现了错误,返回-1
BOOL GetMessage(
LPMSG lpMsg, //指向一个MSG结构体,GetMessage从线程的消息队列中取出的消息信息将保存在该结构体对象中
HWND hWnd, //指定接收属于哪一个窗口的消息,通常设置为NULL,用于接收属于调用线程的所有窗口的窗口消息
UINT wMsgFilterMin, //指定要获取的消息的最小值,通常设为0
UINT wMsgFilterMax //指定要获取的消息的最大值,若两个参数都设为0,则接收所有消息
);
(2) 通常写的消息循环的代码
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
TranslateMessage函数用于将虚拟键消息转换为字符消息。字符消息被投递到调用线程的消息队列中,当下一次调用GetMessage函数时被取出。
9. 窗口过程函数
(1) 对窗口过程函数的说明
窗口过程函数用于处理发送给窗口的消息,一个windwos应用程序的主要代码部分就集中在窗口过程函数中。
LRESULT CALLBACK WindowProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
窗口过程的名字可以随便取,如WinSunProc,但函数定义的形式必须和上述声明形式相同。——系统通过窗口过程函数的地址来调用窗口过程函数,而不是名字。
在窗口过程函数内部使用switch/case语句来确定窗口过程接收的是什么消息,以及如何对这个消息进行处理。
(2) 一个典型的窗口过程函数分析
0) WM_CHAR
用户按下一个字符键,程序得到一条WM_CHAR消息,在其wParam参数中含有该字符的ASCII码值。
1) WM_LBUTTONDOWN
用户在窗口中按下鼠标左键时,将产生一个LBUTTONDOWN消息。
2) WM_PAINT
处理代码
HDC hDC;
PAINTSTRUCT ps;
hDC = BeginPaint(hwns, &ps); //调用BeginPaint函数得到DC的句柄
TextOut(hDC,0,0,"sunxin",strlen("sunxin"));
EndPaint(hwnd,&ps);
重绘时机
当窗口客户区的一部分或者全部变为“无效”时,系统会发送WM_PAINT消息,通知应用程序重新绘制窗口。当窗口从无到有、改变尺寸、最小化后再恢复、被其它窗口遮盖后再显示时,窗口的客户区都将变为无效,此时系统会给应用程序发送WM_PAINT消息,通知应用程序重新绘制;——窗口大小发生变化时是否发生重绘,取决于WNDCLASS结构体中style成员是否设置了CS_HREDRAW和CS_VREDRAW标志。
如何让某个图形始终显示
如果想要让某个图形始终在窗口中显示,就应该将图形的绘制操作放到响应WM_PAINT消息的代码中。
得到窗口的DC
在响应WM_PAINT消息的代码中,要得到窗口的DC,必须调用BeginPaint函数。BeginPaint函数也只能在WM_PAINT消息的响应代码中使用,在其他地方,只能用GetDC来得到DC的句柄;另外,BeginPaint函数得到的DC,必须用EndPaint函数去释放。
3) WM_CLOSE
调用DestroyWindow函数销毁窗口,DestroyWindow函数在销毁窗口后会向窗口过程发送WM_DESTROY消息。——若要控制应用程序是否退出,应该在WM_CLOSE消息的响应代码中完成。
对WM_CLOSE消息的响应并不是必须的,如果应用程序没有对该消息进行响应,系统将把这条消息传给DefWindowProc函数,而DefWindowProc函数则调用DestroyWindow函数来响应这条WM_CLOSE消息。
4) WM_DESTROY
在该消息的响应代码中调用了PostQuitMessage函数;PostQuitMessage函数向应用程序的消息队列中投递一条WM_QUIT消息并返回。
要想让应用程序正常退出,必须响应WM_DESTROY消息,并在消息响应代码中调用PostQuitMessage,向应用程序的消息队列中投递WM_QUIT消息。传递给PostQuitMessage函数的参数值将作为WM_QUIT消息的wParam参数,这个值通常用作WinMain函数的返回值
5) DefWindowProc函数
调用默认的窗口过程,对应用程序没有处理的其它消息提供默认处理。
10. 其它相关小知识点
1) __stdcall与__cdecl
两种不同的函数调用约定,定义了函数参数入栈的顺序,由调用函数还是被调用函数将参数弹出栈,以及产生函数修饰名的方法?
对于参数个数可变的函数,例如printf,使用的是__cdecl调用约定,Win32的API函数都遵循__stdcall调用约定。在VC++开发环境中,默认的编译选项是__cdecl,对于需要__stdcall调用约定的函数,在声明时必须显式地加上__stdcall。