借助Visual C++6.0用C来学习Windows API,模板程序;理解Windows应用程序的消息响应和处理机制
1. 使用该方法写Windows应用程序的模板代码:
/**/
/*------------------------------------------------------------
HELLOWIN.C--Displays"Hello,Windows98!"inclientarea
(c)CharlesPetzold,1998
------------------------------------------------------------*/
#include
<
windows.h
>
LRESULTCALLBACKWndProc(HWND,UINT,WPARAM,LPARAM);
//
窗口回调函数声明, LRESULT是函数返回值类型被宏定义为LONG
//
CALLBACK(被宏定义位_ _stdcall)说明的是函数参数的进栈顺序(从左到右)!
//
4个函数参数为message结构体中的前4个参数!
int
WINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,
PSTRszCmdLine,
int
iCmdShow)
//
主函数,int为函数返回值类型,WINAPI和CALLBACK一样都表示函数参数的进栈顺序!
//
HINSTANCEhInstance定义一个实例(程序自身)句柄变量,所谓句柄的概念和指针,引用差不多,但又不太一样!自己体会一下,句柄是一个数(通常为32位),它代表了一个对象。比如:实例句柄HINSTANCE, 窗口句柄HWND, 设备描述表句柄HDC等。
...
{
staticTCHARszAppName[]=TEXT("HelloWin");
//定义一个字符串数组szAppName[]并赋初值!这个“HelloWin”是程序的名字,是要进操作系统的注册表的!是告诉操作系统你这个程序的名字是什么!
HWNDhwnd;//定义窗口句柄变量!
MSGmsg;//MSG是一个结构体类型,所以msg是一个结构体变量!详见MSG类型介绍
WNDCLASSwndclass;
//WNDCLASS是一个结构体类型,所以wndclass是一个结构体变量!详见WNDCLASS类型介绍
//如下分别初始化结构体变量wndclass的10个域
wndclass.style=CS_HREDRAW|CS_VREDRAW;
//为窗口风格赋值,CS_HREDRAW为垂直重画CS_VREDRAW为水平重画!意思就是
如果你水平或竖直拖动窗口,他将重新显示窗口,即发出WM_PAINT消息!
wndclass.lpfnWndProc=WndProc;//把窗口回调函数的首地址赋给wndclass.lpfnWndProc
wndclass.cbClsExtra=0;//预留空间的附加值,此程序没用到这个域!
wndclass.cbWndExtra=0;//预留空间的附加值,此程序没用到这个域!
wndclass.hInstance=hInstance;//实例句柄,主函数WinMain的参数之一!
wndclass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
//装载图标函数,在这可以装载自己的图标!
wndclass.hCursor=LoadCursor(NULL,IDC_ARROW);
//装载光标函数,在这可以装载自己的光标!
wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
//初始化窗口的背景画刷,如果把WHITE_BRUSH改为BLACK_BRUSH背景即为黑色!
wndclass.lpszMenuName=NULL;//装载菜单,此程序没有!
wndclass.lpszClassName=szAppName;//程序名字!
if(!RegisterClass(&wndclass))//向操作系统注册窗口类,也就是向操作系统申请内存!
...{
MessageBox(NULL,TEXT("ThisprogramrequiresWindowsNT!"),
szAppName,MB_ICONERROR);//异常处理!
return0;
}
//创建窗口函数!
hwnd=CreateWindow(szAppName,
//windowclassname应用程序在操作系统注册表中的名称
TEXT("TheHelloProgram"), //windowcaption应用程序标题栏名称
WS_OVERLAPPEDWINDOW,//windowstyle窗口风格
CW_USEDEFAULT,//initialxposition窗口显示时左上角x坐标
CW_USEDEFAULT,//initialyposition窗口显示时左上角y坐标
CW_USEDEFAULT,//initialxsize窗口显示时右下角x坐标
CW_USEDEFAULT,//initialysize窗口显示时右下角y坐标
NULL,//parentwindowhandle父窗口句柄,此程序没有
NULL,//windowmenuhandle菜单句柄
hInstance,//programinstancehandle程序实例句柄
NULL);//creationparameters创建参数指针
ShowWindow(hwnd,iCmdShow);
//第一次调用窗口回调函数Wndproc,注意Wndproc函数不是ShowWindow函数来调用,而是ShowWindow函数向操作系统发送消息,是操作系统根据发送的消息来调用Wndproc函数!
//操作系统调用Wndproc函数后并响应WM_CREATE消息!
UpdateWindow(hwnd);//刷新窗口,操作系统调用Wndproc函数,并响应WM_PAINT消息!
while(GetMessage(&msg,NULL,0,0))
//这是所有WINDOWS程序的核心,消息循环处理过程!这里是接受消息和发送消息的地方!
...{
//GetMessage函数从操作系统的消息队列中获得消息,一个一个的处理,来一个处理一个,
//直到获得退出消息,也就是点击应用程序右上角的叉子!退出消息循环,并响应WM_QUIT消息
TranslateMessage(&msg);//翻译键盘消息
DispatchMessage(&msg);
//发送消息函数,先把msg发送给操作系统,然后由操作系统再调用Wndproc函数!
}
returnmsg.wParam;
}
//
主函数结束
//
窗口回调函数,此函数只有声明和定义,没有调用!这说明此函数确实是由操作系统调用的!
LRESULTCALLBACKWndProc(HWNDhwnd,UINTmessage,WPARAMwParam,LPARAMlParam)
//
函数的4个参数是消息结构体的前4个参数!
...
{
HDChdc;//设备环境描述表句柄
PAINTSTRUCTps;//绘图结构体变量
RECTrect;//矩形变量
switch(message)//message消息类型!分别处理不同的消息!
...{
caseWM_CREATE://此消息是一个应用程序发送的第一个消息,也是唯一的一次!
PlaySound(TEXT("hellowin.wav"),NULL,SND_FILENAME|SND_ASYNC);
return0;
caseWM_PAINT://绘图函数,在窗口上画画儿!
hdc=BeginPaint(hwnd,&ps);
GetClientRect(hwnd,&rect);
DrawText(hdc,TEXT("Hello,Windows98!"),-1,&rect,
DT_SINGLELINE|DT_CENTER|DT_VCENTER);
EndPaint(hwnd,&ps);
return0;
caseWM_DESTROY://处理退出消息
PostQuitMessage(0);//此消息直接进入消息队列的头部!
return0;
}
returnDefWindowProc(hwnd,message,wParam,lParam);//处理一些闲杂以及一些不可预料的消息!
}
/**/
/*注意1:所有的消息都由操作系统负责管理,所有的消息先进入总的系统消息队列,再由系统消息队列向各个小应用程序队列发消息!
2:窗口回调函数Wndproc是由操作系统调用的,所有的消息进入消息循环,由消息循环把消息发到操作系统的队列中,在由操作系统
根据消息来调用Wndproc函数,而Wndproc函数根据message的不同而调用相应的处理过程!
3:上面这个程序你可以改造一下,在添加几个消息处理过程,比如鼠标左键按下WM_LBULLONDOWN,鼠标左键抬起WM_LBULLONUP
定时器消息WM_TIMER,绘图消息WM_PAINT消息等等,然后把PlaySound(TEXT("hellowin.wav"),NULL,SND_FILENAME|SND_ASYNC)
这个函数添加到以上各个消息处理过程中,看看结果是不是进行了相应的操作!比如,点击一下鼠标左键看看是不是发出声音了!
可以自己是是!
4:其实,WINDOWS程序也可以自己去创建函数!自己去自定义消息处理过程!比如,你自己定义了一个函数,想在鼠标左键点击后
去调用这个函数,那就可以在WM_LBULLONDOWN这个消息处理过程中加上你自己定义的函数调用就可以了!
5:上面这个程序是个非常简单的程序,但是他足以说明WINDOWS程序的消息机制!几乎所有的程序都包括上面这段代码(除了PLAYSOUND函数和
DrawText函数),不管多么复杂的WINDOWS程序都必须包括上面这段代码,因为他是再简单不过的了,只是创建了一个窗口!但是,
再复杂的程序无非也就是在Wndproc函数中的switch多加几个case吗!无非就是加上什么鼠标,键盘,定时器等等,但原里都是
一样的!他的消息处理过程都是一样的!主要是把他的消息机制弄懂,其他的都非常easy!
*/
2. 开始写Windows应用程序:
使用该种方法来写Windows应用程序,完全可以采用上述模板代码。难点在于理解Windows应用程序的消息发送和消息处理机制。如果已经深刻理解,那么使用上述方法编写Windows应用程序,无非是
a) 窗口初始化时,对各个域的赋值可以加入一些个性话因素,显示你希望看到的窗口;
b) 消息处理部分,即窗口过程函数(消息处理函数)Wndproc中的switch多添加几个case,来个性化消息处理过程,满足自己程序的需要。“Windows程序所做的一切都是响应发送给窗口过程的消息”。
如果尚未理解Windows应用程序的消息发送和消息处理机制,可继续参考如下内容。
3. 需要知道的一些预备知识:
a) 程序代码中出现了许多大写标识符,这些都代表一些数值常量,在Windows的头文件中通过typedef或#define语句加以定义过:
如CS_HREDRAW, DT_VCENTER, SND_FILENAME, IDC_ARROW, WM_CREATE, MB_ICONERROR等。
代表的意义可查阅p44。
b) 有些则是为了方便起见的缩写形式:如UNIT类型其实是unsigned int的缩写, PSTR类型是指向一个字符串的指针char *等。
c) 匈牙利命名法:详见p46页
4. 上述模板代码的详细解释:
a) 理解窗口类和窗口(它是理解Windows应用程序的难点一)
可以将窗口类和窗口之间的关系理解为类和对象之间的关系,但它们关系在代码中的实现和面向对象思想有点不同。窗口是应用程序运行以后能看到的长方形的区域,窗口的创建都是基于窗口类的,窗口类定义了基于此类创建的窗口们的一般特征(通常为WNDCLASS结构体类型变量的各个域)及窗口过程(消息响应处理代码,WNDCLASS结构体类型变量的第2个域);
基于窗口类可以创建多个具体的窗口,每个窗口除了具有窗口类定义的共同的一般特征和同一个窗口过程之外,每个具体的窗口在创建时(调用CreateWindow函数)还可以定义一些自己独特的特征(CreateWindow函数的参数中进行设置,如不同大小,屏幕中的位置,风格等)。
两者之间的关系类似于类和对象间的关系,但在代码中的体现却不是这样的:
窗口类被定义位一个结构体类型WNDCLASS的结构体类型变量wndclass,WNDCLASS结构体类型的定义如下:
typedef
struct
...
{
UINTstyle;
WNDPROClpfnWndProc;
intcbClsExtra;
intcbWndExtra;
HINSTANCEhInstance;
HICONhIcon;
HCURSORhCursor;
HBRUSHhbrBackground;
LPCTSTRlpszMenuName;
LPCTSTRlpszClassName;
}
WNDCLASS,
*
PWNDCLASS
共10个域,创建窗口类的时候,即创建一个WNDCLASS该结构体类型的一个变量如wndclass,即初始化变量类型中的10个域:
较重要的WNDPROC lpfnWndProc ,它是依据这个窗口类别来建立的所有窗口所使用的窗口消息处理程序的地址。在HELLOWIN.C中,这个是WndProc函数。
最后一个字段是窗口类别的文字名称。程序写作者可以随意定义其名称。在只建立一个窗口的程序中,窗口类别名称通常设定为程序名称。
其它字段描述了该窗口类别的一些特征。详见p49
初始化了WNDCLASS结构体类型变量wndclass的各个域之后,该窗口类还未建立,需要调用RegisterClass来向OS注册这个窗口类,该函数只有一个参数,即指向WNDCLASS结构体类型的指针。
if
(
!
RegisterClass(
&
wndclass))
...
{
MessageBox(NULL,TEXT("ThisprogramrequiresWindowsNT!"),
szAppName,MB_ICONERROR);
return0;
}
要基于以上建立的窗口类来创建一个窗口,需要进行如下操作:
hwnd
=
CreateWindow(szAppName,
//
windowclassname
TEXT(
"
TheHelloProgram
"
),
//
windowcaption
WS_OVERLAPPEDWINDOW,
//
windowstyle
CW_USEDEFAULT,
//
initialxposition
CW_USEDEFAULT,
//
initialyposition
CW_USEDEFAULT,
//
initialxsize
CW_USEDEFAULT,
//
initialysize
NULL,
//
parentwindowhandle
NULL,
//
windowmenuhandle
hInstance,
//
programinstancehandle
NULL);
//
creationparameters
CreateWindow传回被建立的窗口的句柄,该句柄存放在变量hwnd中,后者被定义为HWND类型(「窗口句柄型态」)。我们可以看到CreateWindow函数的各个参数对这个建立的具体窗口进行了更个性化的设置。
Windows中的每个窗口都有一个句柄,程序用句柄来使用窗口。许多Windows函数需要使用hwnd作为参数,这样,Windows才能知道函数是针对哪个窗口的。如果一个程序建立了许多窗口,则每个窗口均有一个句柄。窗口句柄是Windows程序所处理最重要的句柄之一。
综上,看到了窗口类和窗口之间的关系如何在代码中得以体现。窗口类是WNDCLASS结构体类型的一个变量,并需要RegisterClass函数来注册;窗口则是通过CreateWindow函数来具体创建,并返回hwnd窗口句柄。
b) 其他需要解释的部分:
消息是Windows应用程序的核心,应用程序与OS之间,应用程序和应用程序之间的交互响应都是通过消息传送和响应来完成的。
消息被定义为一种结构体类型MSG:
typedef
struct
tagMSG
...
{
HWNDhwnd;
UINTmessage;
WPARAMwParam;
LPARAMlParam;
DWORDtime;
POINTpt;
}
MSG,
*
PMSG;
POINT数据型态也是一个结构:
typedef
struct
tagPOINT
...
{
LONGx;
LONGy;
}
POINT,
*
PPOINT;
结构的各个字段包括:
- hwnd 接收消息的窗口句柄。在HELLOWIN程序中,这一参数与CreateWindow传回的hwnd值相同,因为这是该程序拥有的唯一窗口。
- message 消息标识符。这是一个数值,用以标识消息。对于每个消息,均有一个对应的标识符,这些标识符定义于Windows表头文件(其中大多数在WINUSER.H中),以前缀WM(「window message」,窗口消息)开头。例如,使用者将鼠标光标放在HELLOWIN显示区域之内,并按下鼠标左按钮,Windows就在消息队列中放入一个消息,该消息的message字段等于WM_LBUTTONDOWN。这是一个常数,其值为0x0201。
- wParam 一个32位的「message parameter(消息参数)」,其含义和数值根据消息的不同而不同。
- lParam 一个32位的消息参数,其值与消息有关。
- time 消息放入消息队列中的时间。
- pt 消息放入消息队列时的鼠标坐标。
C) 重点部分介绍:
CreateWindow调用返回后,Windows内部已经创建了这个窗口,即Windows已经分配了一块内存,要在屏幕上显示窗口需要调用
ShowWindow(hwnd,iCmdShow);
函数调用
UpdateWindow(hwnd);
导致客户区域被重绘。
其他部分的消息处理参考如下:消息循环和窗口过程(消息处理函数)
while
(GetMessage(
&
msg,NULL,
0
,
0
))
...
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
和
LRESULTCALLBACKWndProc(HWNDhwnd,UINTmessage,WPARAMwParam,LPARAMlParam)
整个过程是这样的:(别调用我,我会调用您;主动激励被动响应)
OS有自己的消息队列和消息循环,至于消息处理工作OS自己可以完成;
同时OS为当前运行的每个Windows应用程序也建立了各自的消息队列,该应用程序的消息循环和消息处理函数(窗口过程函数)需要由应用程序的代码来编写,但是这些应用程序的消息循环和窗口过程函数中的实现操作,还必须依靠OS来完成。(如消息循环中的GetMessage, TranslateMessage, DispathMessage; 窗口过程中的PlaySound, BeginPaint, GetClientRect, DrawText等都是调用OS来实现的。)
Windows中的消息通常来自两种情况:
鼠标、键盘或时钟等用户操作(又可称为进队消息); 或者调用某些特定的Windows函数(如CreateWindow,ShowWindow,UpdateWindow时产生的)(又可称为不进队消息)<这里的进队、不进队中的队列指的是应用程序的消息队列>
产生的消息传输和处理过程如下:
不进队消息:首先被OS自动捕获,加入OS自己的消息队列中,当轮到该消息的时候,OS直接调用应用程序的消息处理函数(窗口过程WndProc),把该消息传给该函数,WndProc中的switch-case语句进行判断,然后调用相应条件下的系统函数如PlaySound等,做出响应,没有case的,调用系统默认的处理方法DefWindowProc。(这部分的消息通常不是来自用户的输入,而是来自于调用特定的Windows函数是产生的,如CreateWindow,ShowWindow,UpdateWindow等);
进队消息:用户操作后产生的消息,首先由OS捕获,OS将这些消息添加到应用程序自己的消息队列中,在程序中,应用程序的消息循环代码,不断地从自己的消息队列中取出消息(其实还是借助OS的GetMessage函数来去自己的消息),并将该消息键盘转换(还是借助OS,即调用OS的TranslateMessage函数完成),最后将消息分发(同样需借助OS,并由OS的DispatchMessage函数将这些消息分发,也就是由OS将这些消息传给应用程序的窗口过程WndProc函数,做switch-case处理。分发给OS的消息,OS可能仍旧需要放入自己的消息队列中。)
综上可以看到,其实绝大多数的操作必须通过OS来完成,即使是应用程序