您的位置:首页 > 编程语言 > C语言/C++

《VC++深入详解》学习笔记[1]——第1章 Windows程序内部运行机制

2011-12-11 13:53 686 查看
第1章 Windows程序内部运行机制
1.API与SDK:Windows操作系统提供了各种各样的函数用来方便进行Windows应用程序开发,API函数就是指系统提供的函数,所有主要的Windows函数都在Windows.h头文件中进行了声明。Windows操作系统提供了1000多种API函数。

SDK实际上就是进行程序开发所需的资源的一个集合,Win32 SDK就是Windows 32位平台下的软件开发包,包括了API函数、帮助文档、一些微软提供的辅助开发工具。

2.窗口与句柄:窗口可以分为客户区和非客户区,客户区是窗口的一部分,应用程序通常在客户区中显示文字或者绘制图形。标题栏、菜单栏、系统菜单、最小化框和最大化框、可调边框统称为窗口的非客户区。非客户区由Windows系统来管理,而应用程序则主要管理客户区的外观及操作。

桌面也是一个窗口,称为桌面窗口,它由Windows系统创建和管理。

在Windows程序中有各种各样的资源,如窗口、图标、光标等。系统在创建这些资源时会为它们分配内存,并返回标识这些资源的标识号,即为句柄(HANDLE)。窗口是通过窗口句柄(HWND)来标识的。

3.消息和消息队列:Windows程序设计是一种事件驱动方式的程序设计模式,主要是基于消息的。当用户的行为使操作系统感知到了某个事件,操作系统会将这个事件包装成一个消息投递到应用程序的消息队列中,然后应用程序从消息队列中取出消息并进行相应。

在Windows程序中,消息是由MSG结构体来表示的,该结构体定义如下:

typedef struct tagMSG {

HWND hwnd; //表示消息所属的窗口

UINT message; //消息的标识符。由于数值不便于记忆,Windows将消息对应的数值定义 //为形如WM_XXX的宏。

WPARAM wParam;

LPARAM lParam; // wParam和lParam用于指定消息的附加信息

// WPARAM实际上是unsigned int类型,LPARAM实际上是long类型

DWORD time; //消息投递到消息队列中的时间

POINT pt; //鼠标的当前位置

} MSG, *PMSG;

消息队列:每一个Windows应用程序开始执行后,系统都会为该程序创建一个消息队列,这个消息队列用来存放该应用程序创建的窗口的消息。Windows将产生的消息依次放到消息队列中,而应用程序则通过一个消息循环不断地从消息队列中取出消息,并进行响应,这种消息机制就是Windows程序运行的机制。

“进队消息”和“不进队消息”:Windows程序中的消息可以分为“进队消息”和“不进队消息”。进队的消息将由系统放入到应用程序的消息队列中,然后由应用程序取出并发送。不进队的消息在系统调用窗口过程时直接发送给窗口。不管是进队消息还是不进队消息,最终都由操作系统通过调用窗口过程函数对消息进行处理。

4.WinMain函数

WinMain函数的声明原型如下:

int WINAPI WinMain(

HINSTANCE hInstance, // 该程序当前运行的实例的句柄,一个应用程序可以运行多个实例

HINSTANCE hPrevInstance, // 当前实例的前一个实例的句柄,在Win32下总为NULL

LPSTR lpCmdLine, // 指定传递给应用程序的命令行参数

int nCmdShow // 指定程序的窗口应该如何显示,如最大化、最小化等

);

注:修饰符WINAPI其实就是_stdcall._stdcall 与_cdecl是两种不同的函数调用约定,区别在于函数参数入栈的顺序,由调用函数还是被调用函数将参数弹出栈,以及产生函数修饰名的方法。对于参数可变的函数如printf,使用的是_cdecl调用约定,Win32的API函数都遵循_stdcall调用约定。在VC++开发环境中,默认的编译选项是_cdecl,对于那些需要_stdcall调用约定的函数,必须显式的加上_stdcall

窗口的创建:创建一个完整的窗口,需要经过下面几个操作步骤:

(1)设计一个窗口类

一个完整的窗口具有许多特征,包括光标、图标、背景色等等。窗口的特征由WNDCLASS结构体来定义,该结构体定义如下:

typedef struct _WNDCLASS {

UINT style; //指定窗口的样式。以CS_开头的可选类样式都是只有某一位为1 //的16位常量,可以用位运算符来组合使用这些位标识符

WNDPROC lpfnWndProc; //指向窗口过程函数的函数指针,窗口过程函数是一个回调函数

int cbClsExtra; //类附加内存,一般设为0

int cbWndExtra; //窗口附加内存,一般设为0

HINSTANCE hInstance; //指定包含窗口过程的程序的实例句柄

HICON hIcon; //图标句柄,可使用LoadIcon函数加载图标资源

HCURSOR hCursor; //光标句柄

HBRUSH hbrBackground; //背景画刷句柄,也可以指定为一个标准的系统颜色值;

//可以使用GetStockObject函数来获取画刷、画笔、字体等句柄;

LPCTSTR lpszMenuName; //指定菜单资源的名字

//如果使用ID号,则需使用MAKEINTRESOURCE宏进行转换

//菜单并不是一个窗口

LPCTSTR lpszClassName; //窗口类的名字

} WNDCLASS, *PWNDCLASS;

注:①可以使用位或操作来组合两种窗口的样式,如:style=CS_HREDRAW|CS_VREDRAW;假如有一个变量具有多个样式,而并不清楚该变量具有哪些样式,现在如果想去掉该变量具有的某个样式,可以通过先对该样式标识符取反,然后再与该变量进行与操作来实现,如去掉style变量所具有的CS_VREDRAW样式:style=style&~CS_VREDRAW.

②回调函数不是由该函数的实现方直接调用,而是在特定的事件或者条件发生时由另外一方调用的,用于对该事件或条件进行相应。提供函数实现的一方在初始化的时候将回调函数的函数指针注册给调用者,当特定的事件或者条件发生时,调用者使用函数指针调用回调函数对事件进行处理。

③一个Windows应用程序可以包含多个窗口过程函数,一个窗口过程总是与一个特定的窗口相关联,基于该窗口类创建的窗口使用同一个窗口过程。窗口过程函数被调用的过程如下:

<1>在设计窗口类的时候,将窗口过程函数的地址赋值给lpfnWndProc成员变量;

<2>调用RegsiterClass(&wndclass)注册窗口类,这样系统就有了窗口过程函数的地址;

<3>当应用程序接收到某一窗口的消息时,调用DispatchMessage(&msg)将消息回传给系统,系统则利用先前注册窗口类时得到的函数指针调用窗口过程函数对消息进行处理。

④WNDPROC实际上是函数指针类型,其定义为:

typedef LRESULT (CALLBACK* WNDPROC)(HWND,UNIT,WPARAM,LPARAM);

LRESULT实际为long,CALLBACK实际为_stdcall.

窗口过程函数的格式必须与WNDPROC相同。

⑤在VC++中,对于自定义的各种资源都保存在扩展名为.rc的资源脚本文件中,文件本身是文本格式。资源是通过ID来标识的,同一个ID可以标识多个不同的资源。资源的ID实质上是一个整数,在”Resource.h”中定义为一个宏。如菜单资源IDM_XXX,图标资源IDI_XXX;

(2)注册窗口类

设计完窗口类后需要调用RegisterClass函数对其进行注册,注册成功后才可以创建该类型的窗口,注册函数声明如下:

ATOM RegisterClass(CONST WNDCLASS *lpWndClass);

(3)创建窗口

HWND CreateWindow(

LPCTSTR lpClassName, //指定窗口类的名称,产生窗口的过程由操作系统完成如果没有注册 //过指定名称的窗口类,将创建失败

LPCTSTR lpWindowName, //指定窗口的名字,如果窗口样式指定了标题栏,则改名字将显示在 //标题栏上

DWORD dwStyle, //指定创建的窗口的样式

int x, //窗口左上角的x坐标

int y, //窗口左上角的y坐标

int nWidth, //窗口的宽度

int nHeight, //窗口的高度

HWND hWndParent, //指定被创建窗口的父窗口句柄,子窗口必须具有WS_CHILD样式; //对父窗口的操作同时也会影响到子窗口

HMENU hMenu, //指定窗口菜单的句柄

HINSTANCE hInstance, //指定窗口所属的应用程序实例的句柄

LPVOID lpParam //作为WM_CREAT消息的附加参数lParam传入的数据指针

//在创建多文档界面的客户窗口时,lParam必须指向CLIENTCREATESTRUCT

);

如果窗口创建成功,CreateWindow函数将返回系统为该窗口分配的句柄,否则,返回NULL。注意,在创建窗口之前应先定义一个窗口句柄变量来接收创建窗口之后返回的句柄值。

注:①参数dwStyle指定某个具体的窗口的样式,区别于WNDCLASS中的style成员,style指定窗口类的样式,基于该窗口类创建的窗口都具有这些样式;style的位标志以CS_开头,而dwStyle的位标志以WS_开头。

②如果参数x被设为CW_USERDEFAULT那么系统为窗口选择默认的左上角坐标并忽略y参数;如果nWidth被设为CW_USEDEFAULT,那么系统为窗口选择默认的宽度和高度,参数nHeight被忽略。

③对父窗口的操作对子窗口的影响:

<1>销毁:在父窗口被销毁之前销毁;

<2>隐藏:在父窗口被隐藏之前隐藏,子窗口只有在父窗口可见时可见

<3>移动:跟随父窗口客户区一起移动

<4>在父窗口显示之后显示

(4)显示及更新窗口

显示窗口调用函数:

BOOL ShowWindow(

HWND hWnd, //要显示的窗口的句柄

int nCmdShow //窗口显示的状态,SW_开头的位标志

);

更新窗口调用函数:

BOOL UpdateWindow(

HWND hWnd // handle to window

);

UpdateWindow函数通过发送一个WM_PAINT消息来刷新窗口,它将WM_PAINT消息直接发送给了窗口过程函数进行处理,而没有放到消息队列当中。

消息循环:要从消息队列中取出消息,需要调用GetMessage函数:

BOOL GetMessage(

LPMSG lpMsg, //指向一个消息结构体;GetMessage从线程的消息队列中取出的消息将保 //存在该结构体对象中

HWND hWnd, //指定接收属于哪一个窗口的消息;设置为NULL则接收属于调用线程的 //所有窗口的窗口消息

UINT wMsgFilterMin, //指定要获取的消息的最小值,通常设为0

UINT wMsgFilterMax //指定要获取的消息的最大值

);

GetMessage函数接收到除WM_QUIT外的消息均返回非零值。对于WM_QUIT消息,该函数返回零。如果出现了错误,该函数返回-1.要特别注意返回-1时退出消息循环的情况。

注:如果wMsgFilterMin和wMsgFilterMax参数同时设为0,则接收所有消息。

通常编写的消息循环代码如下:

MSG msg;

while(GetMessage(&msg,NULL,0,0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

TranslateMessage函数用于将虚拟键消息转换为字符消息,即将WM_KEYDOWN和WM_KEYUP消息的组合转换为一条WM_CHAR消息,并将转换后的新消息投递到调用线程的消息队列中,当下一次调用GetMessage函数时被取出。TranslateMessage函数并不会修改原有的消息,它只是产生新的消息并投递到消息队列中。

DispatchMessage函数分派一个消息到窗口过程,由窗口过程函数对消息进行处理。DispatchMessage实际上是将消息回传给操作系统,由操作系统调用窗口过程函数对消息进行处理。

Windows应用程序的消息处理过程

(1)操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中;

(2)应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理,例如放弃对某些消息的响应,或者调用TranslateMessage产生新的消息;

(3)应用程序调用DispatchMessage将消息回传给操作系统。消息是由MSG结构体对象表示的,其中就包含了接收消息的窗口的句柄,因此DispatchMessage函数总能进行正确的投递;

(4)系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程对消息进行处理;

注:①从消息队列中获取消息还可以使用PeekMessage函数,该函数可以指定在获取消息后是否将消息从消息队列中移除。

②发送消息可以使用SendMessage和PostMessage函数。SendMessage将消息直接发送给窗口,并调用该窗口的窗口过程进行处理,在窗口过程对消息处理完毕后该函数才返回,即发送不进队消息。PostMessage函数将消息放入与创建窗口的线程相关联的消息队列后立即返回。

此外,PostThreadMessage函数用于向线程发送消息。

编写窗口过程函数

窗口过程函数的声明形式:

LRESULT CALLBACK WindowProc(

HWND hwnd, // handle to window

UINT uMsg, // message identifier

WPARAM wParam, // first message parameter

LPARAM lParam // second message parameter

);

系统通过窗口过程函数的地址来调用窗口过程函数,而不是名字。因此窗口过程函数的名字可以随便取,但是函数定义的形式是确定的。

在窗口过程函数内部通常对uMsg参数使用switch/case语句来确定窗口过程接收的是什么消息,以及如何对这个消息进行处理。

源码:WinMain.cpp

#include <windows.h>

#include <stdio.h>

LRESULT CALLBACK WinSunProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

{

WNDCLASS wndcls; //设计一个窗口

wndcls.cbClsExtra = 0;

wndcls.cbWndExtra = 0;

wndcls.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);

wndcls.hIcon = LoadIcon (NULL,IDI_ERROR);

wndcls.hCursor = LoadCursor (NULL,IDC_CROSS);

wndcls.hInstance = hInstance;

wndcls.lpfnWndProc = WinSunProc; //指定窗口过程函数

wndcls.lpszClassName = "myWnd"; //自定义窗口类的名称

wndcls.style = CS_HREDRAW | CS_VREDRAW;

wndcls.lpszMenuName = NULL;

RegisterClass(&wndcls); //注册窗口类

HWND hwnd;

hwnd = CreateWindow ("myWnd","标题栏文字", WS_OVERLAPPEDWINDOW, 0, 0, 600, 400, NULL, NULL, hInstance, NULL );

ShowWindow (hwnd, SW_SHOWNORMAL);

UpdateWindow (hwnd);

MSG msg;

while (GetMessage (&msg, NULL, 0, 0 ))

{

TranslateMessage (&msg);

DispatchMessage (&msg);

}

return 0;

}

LRESULT CALLBACK WinSunProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

switch(uMsg)

{

case WM_CHAR: //

char szChar[20];

sprintf (szChar , "char is %d",wParam);

MessageBox(hwnd,szChar,"对话框标题",0);

break;

case WM_LBUTTONDOWN:

MessageBox(hwnd,"mouse clicked","对话框标题",0);

HDC hdc;

hdc = GetDC(hwnd);

TextOut (hdc,0,50,"点击了鼠标左键",strlen("点击了鼠标左键"));

ReleaseDC (hwnd,hdc);

break;

case WM_PAINT:

HDC hDC;

PAINTSTRUCT ps;

hDC = BeginPaint(hwnd,&ps);

TextOut(hDC,0, 0, "默认绘制文字",strlen("默认绘制文字"));

EndPaint(hwnd,&ps);

break;

case WM_CLOSE:

if(IDYES == MessageBox(hwnd,"是否真的结束?","要退出第一个写的程序吗?",MB_YESNO))

{

DestroyWindow(hwnd);

}

break;

case WM_DESTROY:

PostQuitMessage(0);

break;

default:

return DefWindowProc(hwnd,uMsg, wParam, lParam);

}

return 0;

}//end of WinSunProc()

注:①在响应WM_PAINT消息的代码中,要得到窗口的DC,必须调用BeginPaint函数。BeginPaint函数也只能在WM_PAINT消息的响应代码中使用。在其他地方只能使用GetDC来得到DC的句柄。

②DestroyWindow函数在销毁窗口后会向窗口过程发送WM_DESTROY消息。此时窗口虽然销毁了,但是应用程序并没有退出。所以不应在WM_DESTROY消息的响应代码中提示用户是否退出,因为此时窗口已经销毁了,即使用户选择不退出也没有什么意义。如果要控制程序是否退出,应该在WM_CLOSE消息的响应代码中完成。对WM_CLOSE消息的响应并不是必须的,如果应用程序没有对该消息进行响应,系统将把这条消息传给DefWindowProc函数,而DefWindowProc函数则调用DestroyWindow函数来响应这条WM_CLOSE消息。

GetMessage函数只有在收到WM_QUIT消息时才返回0,此时消息循环才结束,程序退出。要想让程序正常退出,必须响应WM_DESTROY消息,并在消息响应代码中调用PostQuitMessage向应用程序的消息队列中投递WM_QUIT消息。

③DefWindowProc函数调用默认是窗口过程,对应用程序没有处理的其他消息提供默认处理。对于大多数的消息,应用程序都可以直接调用DefWindowProc函数进行处理。

在编写窗口过程函数时,应该将DefWindowProc函数的调用放到default语句中,并将该函数的返回值作为窗口过程函数的返回值。

④在用GetMessage函数接收指定窗口的消息时,要注意返回值为-1导致死循环的情况。改进代码如下:

BOOL bRet;

while( (bRet = GetMessage(&msg,hwnd,0,0))!=0)

{

if(bRet == -1) return -1;

else{

TranslateMessage (&msg);

DispatchMessage (&msg);

}

}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: