Windows应用程序的基本结构.ppt
《Windows应用程序的基本结构.ppt》由会员分享,可在线阅读,更多相关《Windows应用程序的基本结构.ppt(135页珍藏版)》请在冰点文库上搜索。
![Windows应用程序的基本结构.ppt](https://file1.bingdoc.com/fileroot1/2023-4/30/734dd74d-1cd3-49db-ab89-a2b02bca53d9/734dd74d-1cd3-49db-ab89-a2b02bca53d91.gif)
Windows应用程序框架结构,哈尔滨工程大学,概述,理解Window编程所使用的事件驱动模型Window编程的基本框架,Windows平台下可视化开发工具,可视化开发系统集成了一系列系统可用资源和开发工具1、程序调试工具包括源程序语法检查、可执行程序修改和运行监视等2、源程序编辑器和编译器3、资源管理器,包括图形化窗口及组成元素的多种对象的编辑器4、系统函数库和系统函数开发工具5、可选择并构成具体语句或源程序结构的例程库及Help,Windows程序的特点,大致说来windows编程有两种方法:
a.windowsc方式(SDK),SDK编程就是直接调用windows的API进行编程;b.c+方式:
即对SDK函数进行包装,如VC的MFC,BCB的OWL等。
MFC把这些API封闭起来,共有一百多个类组成.API,全称applicationprograminterface,意思是应用程序编程接口(说起API并不仅仅指windows而言,windows支持的API叫winapi)。
winapi就是应用程序和windows之间通讯的一个编程界面。
windows提供了上千个API函数,以方便程序员来编写应用程序。
Windows程序的特点,WinSDK程序设计就是API方式的windows程序设计。
SDK,全称SoftwareDevelopersKit,意思是软件开发工具箱。
MFC,全称MicrosoftFoundationClasses,伪软把WinAPI进行封装的类库。
它是一个类的集合,通过覆盖WinAPI,为编程提供了一个面向对象的界面。
它使windows程序员能够利用C+面象对象的特性进行编程,类似BCB的OWL,Delphi的VCL组件。
它把那些进行SDK编程时最繁琐的部分提供给程序员,使之专注于功能的实现。
你不妨把它想象成类似TC提供的函数库吧。
SDK编程,利用WindowsAPI函数编写Windows应用程序必须首先了解以下内容:
(1)窗口的概念
(2)事件驱动的概念(3)句柄(4)消息,Windows的事件驱动机制,Dos的过程驱动与Windows的事件驱动在讲Window消息循环之前,我想先谈一下Dos与Windows驱动机制的区别:
DOS程序主要使用顺序的,过程驱动的程序设计方法。
顺序的,过程驱动的程序有一个明显的开始,明显的过程及一个明显的结束,因此程序能直接控制程序事件或过程的顺序。
而Windows的驱动方式是事件驱动,就是不由事件的顺序来控制,而是由事件的发生来控制,所有的事件是无序的,作为一个windows程序员,在你编写程序时,你并不知道用户先按哪个按纽,也不知道程序先触发哪个消息。
你的任务就是对正在开发的应用程序要发出或要接收的消息进行排序和管理。
过程驱动方法和事件驱动方法,Dos编程和Windows编程不同,dos下的C编程的main()一样,windows下的入口是WinMain()函数。
WinMain()所起的作用:
初始化,展示窗口,销毁应用程序等。
第一个参数:
应用程序的当前实例句柄。
第二个参数:
应用程序的前一个实例句柄,别管它,对于Win32位而言,它一般是NULL.第三个参数:
指向任何传给程序的命令行参数。
PSTR代表指向字符串的指针。
第四个参数:
它告诉应用程序如何初始化窗口,如最大化,最小化等状态。
WinMain函数的功能,三个基本的组成部分:
函数说明、初始化和消息循环,WinMain函数,注意!
Win是多任务管理的,同一应用程序的多个窗口可能会同时存,Win系统对每个窗口的执行称为一个实例,并用一个实例句柄来唯一标识,Windows常见的数据类型,在Windows.h中定义了Windows应用程序中包含种类繁多的数据类型,重要的数据结构,两者句柄定义的不同,句柄(handle):
在标准C库中句柄用来对文件输入输出。
在Windows环境中,句柄是用来标识项目的,这些项目包括:
*.模块(module)*.任务(task)*.实例(instance)*.文件(file)*.内存块(blockofmemory)*.菜单(menu)*.控制(control)*.字体(font)*.资源(resource),包括图标(icon),光标(cursor),字符串(string)*.GDI对象(GDIobject),包括位图(bitmap),画刷(brush),元文件(metafile),调色板(palette),画笔(pen),区域(region),以及设备描述表(devicecontext)。
WINDOWS程序中并不是用物理地址来标识一个内存块,文件,任务或动态装入模块的,相反的,WINDOWSAPI给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。
句柄是什么?
在Win32里,句柄是指向一个无值型对象(void*)的指针,是一个4字节长的数据”。
句柄并不是一个真正意义上的指针。
从结构上看,句柄的确是一个指针,尽管它没有指向用于存储某个对象的内存位置,而实际上句柄指向的是一个包含了对该对象进行的引用的位置。
我们天气热摇扇子的时候只要抓住扇柄便可以控制整个扇子的运动了,在程序中也差不多是这个意思。
通常一个句柄就可以传递我们所要做的事情。
有经验的开发者肯定清楚,编写程序总是要和各种句柄打交道的,句柄是系统用来标识不同对象类型的工具,如窗口、菜单等,这些东西在系统中被视为不同类型的对象,用不同的句柄将他们区分开来。
句柄,常用句柄类型及其说明,应用程序通过句柄访问相应的对象信息,HWND窗口句柄HDC设备环境句柄HBITMAP位图句柄HCURSOR光标句柄HICON图标句柄HFONT字体句柄HMENU菜单句柄HPEN画笔句柄HFILE文件句柄HBRUSH画刷句柄HINSTANCE当前实例句柄,Windows程序的特点,窗口句柄:
系统通过窗口句柄来在整个系统中唯一标识一个窗口,发送一个消息时必须指定一个窗口句柄表明该消息由那个窗口接收。
而每个窗口都会有自己的窗口过程,所以用户的输入就会被正确的处理。
所有的命名采用了匈牙利表示法。
如消息的前缀使用msg.句柄使用h.函数使用fn等。
Windows程序则至少两个主程序,一个是WinMain(),intWINAPIWinMain(HINSTANCEhInstance,/handletocurrentinstanceHINSTANCEhPrevInstance,/handletopreviousinstanceLPSTRlpCmdLine,/commandlineintnCmdShow/showstate);,Windows程序的特点,另一个是窗口过程函数WndProc,它的函数原型为:
longFARPASCALWndProc(HWNDhWnd,WORDmessage,WORDwParam,LONGlParam);窗口函数与回调函数:
在Windows中,应用程序通过要求Windows完成指定操作,而承担这项通信任务的API函数就是Windows的相应窗口函数WndProc。
应用程序不直接调用任何窗口函数,而是等待Windows调用窗口函数,请求完成任务或返回信息。
为保证Windows调用这个窗口函数,这个函数必须先向Windows登记,然后在Windows实施相应操作时回调,所以窗口函数又称为回调函数。
WndProc是一个主回调函数,Windows至少有一个回调函数。
典型的回调函数有窗口过程、对话框过程和钩子函数。
实际上,也许有不止一个的窗口过程。
例如,每一个不同的窗口类都有一个与之相对应的窗口过程。
实例:
在Windows中,能多次同时运行同一个应用程序,即运行多个副本,每个副本叫做一个“实例”。
Windows程序的特点,WinMain()函数的调用约定是PASCAL。
在这里PASCAL是一个调用约定,由于这种方式最早由PASCAL采用,所以这么叫。
在MSDN中的C+LanguageReference中,CallingConventions这一章都是讲调用约定的。
约定:
微软重定义了许多约定类型,为的是可以让代码更容易跨平台或者跨编译器。
其实,调用约定要解决两个问题,都是针对堆栈操作:
1。
参数传递的顺序(本质是压栈的顺序)2。
谁负责平栈(调用者还是调用对象)一个函数的声明、定义和实现中的调用方式一般都一致。
WINAPI标识符的定义是:
#defineWINAPI_stdcall,_stdcall指Window调用函数的一种方式,也就是如何在堆中存取函数参数的方式。
许多WindowsApi函数调用声明为_stdcall方式。
调用方式,1、_stdcall是Pascal程序的缺省调用方式,通常用于Win32Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。
VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上“”和参数的字节数(堆栈要求分配的字节数)。
如:
_TestMethod42、C调用约定(即用_cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。
另外,在函数名修饰约定方面也有所不同。
_cdecl是C和C程序的缺省调用方式。
每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。
函数采用从右到左的压栈方式。
VC将函数编译后会在函数名前面加上下划线前缀。
是MFC缺省调用约定。
如:
_TestMethod,调用方式,CALLBACK和WINAPI函数是由你设计的函数,但你不能“显式”的调用它,而是Windows系统自己调用,例如窗口过程函数,枚举函数等,这是由Windows系统的工作机制决定的。
至于这些函数为什么要是CALLBACK,且看下面详解:
CALLBACK,WINAPI等在windef.h中被如下定义:
#defineCALLBACK_stdcall#defineWINAPI_stdcall#defineWINAPIV_cdecl#defineAPIENTRYWINAPI#defineAPIPRIVATE_stdcall#definePASCAL_stdcall,调用方式举例,CALLBACK就是要VC编译器在编译时采取非默认(_cdecl)的方式(_stdcall),_stdcall和_cdecl最大的不同就是我前面说的“堆栈指针恢复的责任归属”-C编译器默认(_cdecl)是主函数来恢复stack指针SP,若指定关键字_stdcall,则编译器生成的码是由被调函数自己恢复SP,看看下面的例子:
假如有一个函数声明如int_stdcallgetMax(inta,intb);返回两者中较大值。
在主函数中被调用,调用时VC造出的码如下:
(其它编译器可能不同)0040102Bpush38h;参数b0040102Dpush60h;a0040102FcallILT+15(_getMax)(0x0040100f);callgetMax().为getMax()造出的汇编语句是00401020pushebp00401021movebp,esp.;其它指令00401026popebp00401027ret8;在返回时修改堆栈指针,调用方式举例,若声明及定义时无_stdcal,VC造出的码如下:
0040102Bpush38h0040102Dpush60h0040102FcallILT+15(_getMax)(0x0040100f)00401034addesp,8;esp由主函数恢复.为getMax()造出的汇编语句是00401020pushebp00401021movebp,esp.;其它指令00401026popebp00401027ret;返回现在你该知CALLBACK与不加CALLBACK的函数在调用以及返回上有什么区别了吧。
Windows程序的特点,用位的“或”操作(操作符“|”)把若干个常数组合起来控制消息窗口显示的按钮和图标等。
在Windows.h中,以CS_开头的类样式(ClassStyle)标识符被定义为16位的常量,这些常量都只有某1位为1。
在VC+开发环境中,可以看到CS_VREDRAW=0x0001,CS_HREDRAW=0x0002,CS_DBLCLKS=0x0008,CS_NOCLOSE=0x0200,将这些16进制数转换为2进制数,就可以发现它们都只有1位为1,并且为1的位各不相同。
用这种方式定义的标识符称为“位标志”,我们可以使用位运算操作符来组合使用这些样式。
例如,要让窗口在水平和垂直尺寸发生变化时发生重绘,我们可以使用位或()操作符将CS_HREDRAW和CS_VREDRAW组合起来,如style=CS_HREDRAW|CS_VREDRAW。
假如有一个变量具有多个样式,而我们并不清楚该变量都有哪些样式,现在我们想要去掉该变量具有的某个样式,那么可以先对该样式标识符进行取反()操作,然后再和这个变量进行与(&)操作即可实现。
例如,要去掉先前的style变量所具有的CS_VREDRAW样式,可以编写代码:
style=style&CS_VREDRAW。
在Windows程序中,经常会用到这种位标志标识符,后面我们在创建窗口时用到的窗口样式,也是属于位标志标识符。
Windows程序的特点,在Windows应用程序中,每一个窗口都必须从属于一个窗口类,窗口类定义了窗口所具有的属性,如它的样式、图标、鼠标指针、菜单名称及窗口过程名等。
窗口种类是定义窗口属性的模板,这些属性包括窗口式样,鼠标形状,菜单等等,窗口种类也指定处理该类中所有窗口消息的窗口函数.只有先建立窗口种类,才能根据窗口种类来创建Windows应用程序的一个或多个窗口.创建窗口时,还可以指定窗口独有的附加特性.窗口种类简称窗口类,窗口类不能重名.在建立窗口类后,必须向Windows登记.建立窗口类就是用WNDCLASS结构定义一个结构变量。
窗口的创建过程,窗口的创建过程需要四个步骤,下面列出了创建步骤和这个过程中涉及的类和函数:
1.设计一个窗口类/很多特征(光标,图标,背景)WNDCLASS2.注册窗口类/RegisterClass3.创建窗口/首先定义句柄如:
HWNDhwnd;CreateWindow4.显示及更新窗口/显示窗口:
ShowWindow,更新窗口:
UpdateWindow,理解Window编程所使用的事件驱动模型,事件,消息,消息队列和消息循环Windows应用程序是遵循事件驱动模型,通常Windows程序启动后只是等待某些事件的发生(当然应用程序也可以进行空闲处理-当没有事件发生时(WM_IDLE)执行某些特定的任务)。
事件有多种产生途径,包括:
键盘中某些键按下,鼠标单击,当窗口创建时,尺度发生变化时(Windows系统发给你的),发生移动,被关闭,最小化,最大化或变为可见状态时等。
当某一个事件发生时,Windows会为该事件所针对的应用程序发送一条消息,表明该事件发生了,并在该应用程序的消息队列中增加一条消息,该消息队列只是保存了应用程序所接受的消息的一个优先队列(会根据消息的优先级排列)。
应用程序要做的事,就是在一个消息循环中不断地检查消息队列,当接收到一条消息时,便将其分派给接收该消息的特定窗口的窗口过程。
一个应用程序可能含有很多的窗口,窗口过程是一个与应用程序各窗口相关的特殊函数。
(每一个窗口都有一个窗口过程,但多个窗口可以共用一个窗口过程),他就是我们实现的用于处理消息的函数。
事件驱动编程模型,事件,Window操作系统检测到某一个事件的发生。
作为响应,Windows将给特定程序的消息队列发送一条消息,应用程序A消息队列,应用程序B消息队列,应用程序C消息队列,当一条消息到来时,消息队列会一直将该消息保存到应用程序准备对其进行处理为止。
所以,如果应用层序正处在忙碌状态,该消息便不会立即被处理。
此外,优先级较高的消息会在优先级较低的消息之前优先得到处理。
应用程序A的消息循环,应用程序B的消息循环,应用程序C的消息循环,窗口过程C,窗口过程C,窗口过程C,应用程序的消息循环不断地检查消息是否到来,当“获取”一条消息时,该消息便被发送给窗口过程WndProc,该函数为消息的传递的目的地。
窗口过程是一个由应用程序开发人员定义的函数。
该函数由windows系统自动调用。
注意,一个单个应用程序允许有多窗口函数,CaseWM_CREATE:
CaseWM_KEYDOWN:
CaseWM_DESTROY:
在窗口处理函数内部,编写消息处理操作,消息?
消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉。
一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向Windows发出一个通知,告诉应用程序某个事情发生了。
例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序。
消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。
例如,对于单击鼠标所产生的消息来说,这个记录中包含了单击鼠标时的坐标。
这个记录类型叫做MSG,MSG含有来自windows应用程序消息队列的消息信息。
消息可以由系统或者应用程序产生。
系统在发生输入事件时产生消息。
举个例子,当用户敲键,移动鼠标或者单击控件。
系统也产生消息以响应由应用程序带来的变化,比如应用程序改变系统字体改变窗体大小。
应用程序可以产生消息使窗体执行任务,或者与其他应用程序中的窗口通讯。
消息中有什么?
它在Windows中声明如下:
typedefstructtagMsgHWNDhwnd;接受该消息的窗口句柄UINTmessage;消息常量标识符,也就是我们通常所说的消息号WPARAMwParam;32位消息的特定附加信息,确切含义依赖于消息值LPARAMlParam;32位消息的特定附加信息,确切含义依赖于消息值DWORDtime;消息创建时的时间POINTpt;消息创建时的鼠标/光标在屏幕坐标系中的位置MSG;,Msg消息结构内容,Windows系统的消息机制都包含2个长整型的参数:
WPARAM,LPARAM,可以存放指针,也就是说可以指向任何内容了。
传递的内容因消息各异,消息处理函数会根据消息的类型进行特别的处理,它知道传递的参数是什么含义。
消息在线程内传递时,由于在同一个地址空间中,指针的值是有效的。
但是夸线程的情况就不能直接使用指针了,所以Windows系统提供了WM_SETTEXT,WM_GETTEXT,WM_COPYDATA等消息,用来特殊处理,指针的内容会被放到一个临时的内存映射文件(Memory-mappedFile)里面,通过它实现线程间的共享数据。
消息队列和线程的关系是什么?
消息队列的创建:
当一个线程第一次被创建时,系统假定线程不会用于任何与用户相关的任务。
这样可以减少线程对系统资源的要求。
但是,一旦该线程调用一个与图形用户界面有关的函数(如检查它的消息队列或建立一个窗口),系统就会为该线程分配一个消息队列,以便它能够执行与用户界面有关的任务。
操作系统可能为任何线程创建消息队列,只要该线程调用了消息获取函数,甚至都不需要该线程创建任何窗口。
如果句柄是NULL,DispatchMessage不做任何事。
因此在处理线程消息的时候不能用DispatchMessage处理,而是直接处理,线程一般没有窗口。
如下:
DispatchMessage函数需要输入一个指向MSG结构的指针(当然这个MSG结构可以是以前GetMessage或者PeekMessage获得的)。
DispatchMessage会传送窗口句柄,消息标识符,两个消息参数给窗口过程函数,但是他不会传送时间和鼠标位置信息。
应用程序在处理消息的时候可以通过调用GetMessageTime和GetMessagePos函数获得这两个信息。
消息队列的资源体,线程处理消息的例程,如果参数lpmsg指向一个WM_TIMER消息,并且WM_TIMER消息的参数IParam不为NULL,则调用IParam指向的函数,而不是调用窗口程序。
见线程multi1thread1中的CreateMsgThread和GetMesgThread函数中调用PeekMessage的用意。
没有窗口如何发消息?
向一个线程传递消息可以使用PostThreadMessage,这个函数的接口很明确,要求传递一个线程Id,但是还有PostMessage和SendMessage这样的API,要求传递的是窗口句柄,这可能就是我们此前会迷惑于消息队列是跟窗口相关还是跟线程相关的原因。
事实上,在调用PostMessage或SendMessage的时候,系统要根据传入的窗口句柄,确认是哪一个线程创建了该窗口(我们也可以通过GetWindowsThreadProcessId自行获取是哪个线程创建了一个窗口),然后系统分配一块内存,将消息参数存储在这块内存中,并将这块内存增加到相应线程的登记消息队列中。
线程中可以有消息循环,消息循环将线程中的消息取出来并且进行处理可以自行根据消息的类型进行处理,也可以交给DispatchMessage处理,该API会回调窗口类中的窗口处理函数(依据该窗口所属窗口类别WNDCLASS的不同分别回调不同的消息处理函数)。
如果线程创建了窗口,那么窗口的各种响应事件全部是由消息循环以及相关处理完成的,一个消息循环可以处理很多个窗口的消息。
消息分类,队列消息和非队列消息:
从消息的发送途径上看,消息分两种:
队列消息和非队列消息。
队列消息送到系统消息队列,然后到线程消息队列;非队列消息直接送给目的窗口过程。
这里,对消息队列阐述如下:
Windows维护一个系统消息队列,每个GUI线程有一个线程消息队列(Threadmessagequeue)。
鼠标、键盘事件由鼠标或键盘驱动程序转换成输入消息并把消息放进系统消息队列,例如WM_MOUSEMOVE、WM_LBUTTONUP、WM_KEYDOWN、WM_CHAR等等。
Windows每次从系统消息队列移走一个消息,确定它是送给哪个窗口的和这个窗口是由哪个线程创建的,然后,把它放进窗口创建线程的线程消息队列。
线程消息队列接收送给该线程所创建窗口的消息。
线程从消息队列取出消息,通过Windows把它送给适当的窗口过程来处理。
除了键盘、鼠标消息以外,队列消息还有WM_PAINT、WM_TIMER和WM_QUIT。
这些队列消息以外的绝大多数消息是非队列消息。
消息分类?
windows中的消息虽然很多,但是种类并不繁杂,大体上有3种:
窗口消息、命令消息和控件通知消息。
窗口消息大概是系统中最为常见的消息,它是指由操作系统和控制其他窗口的窗口所使用的消息。
例如CreateWindow、MoveWindow等都会激发窗口消息,还有我们在上面谈到的单击鼠标所产生的消息也是一种窗口消息。
命令消息,这是一种特殊的窗口消息,他用来处理从一个窗口发送到另一个窗口的用户请求,例如按下一个按钮,他就会向主窗口发送一个命令消息。
控件通知消息,是指这样一种消息,一个窗口内的子控件发生了一些事情,需要通知父窗口。
通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框,以及Windows公共控件如树状视图、列表视图等。
例如,单击或双击一个控件、在控件中选择部分文本、操作控件的滚动条都会产生通知消息。
她类似于命令消息,当用户与控件窗口交互时,那么控件通知消息就会从控件窗口发送到它的主窗口。
但是这种消息的存在并不是为了处理用户命令,而是为了让主窗口能够改变控件,例如加载、显示数据。
常见的window消息,VC中存在几种系统定义的消息分类,不同的前缀符号经常用于消息宏识别消息附属的分类,系统定义的消息宏前缀如下:
BM表示按钮控制消息CB表示组合框控制消息DM表示默认下压式按钮控制消息EM表示编辑控制消息LB表示列表框控制消息SBM表示滚动条控制消息WM