windows程序内部运行机制

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。

posted @ 2013-03-20 12:48  知音  阅读(562)  评论(0编辑  收藏  举报