windows程序设计第一章讲义.docx
《windows程序设计第一章讲义.docx》由会员分享,可在线阅读,更多相关《windows程序设计第一章讲义.docx(16页珍藏版)》请在冰豆网上搜索。
windows程序设计第一章讲义
第一章
这节课我们主要讲解WINDOWS的内部运行原理,要了解VISUALC++程序的开发过程,先要理解WINDOWS应用程序的运行机制,因为VISUALC++是WINDOWS开发语言,我们要明白在WINDOWS环境下编程和在其他环境下一些根本的差别,全面讨论WINDOWS工作原理需要一本书的内容,我们没了解那么细,但还是要了解一些根本的概念原理。
一.Windows应用程序,操作系统,计算机硬件之间的相互关系
Windows程序设计是完全不同于传统的DOS系统的设计方法,它采取事件驱动的机制的程序设计模式,主要是基于消息的。
当用户要实现某种功能时,会调用操作系统的某种支持,然后操作系统将用户的需要包装成消息,并投递到消息队列中,最后应用程序从消息队列中取走消息并进行响应。
向下的箭头①③④表示操作系统能够操作输出设备以执行特定的功能,如让声卡发出声音,显卡画出图形等。
向上的箭头②表示操作系统能够感知输出设备状态的变化,如鼠标移动,键盘按下,并能够知道鼠标移动的位置,键盘按下的哪个键,这就是操作系统和计算机硬件的交互关系。
应用程序开发者通常不需要知道其具体的实现细节,我们关心的是应用程序与操作系统的交互关系。
向下的箭头③表示应用程序可以通知操作系统执行某个具体的动作,如操作系统能够控制声卡发出声音,但它并不知道应该何时发出何种声音,需要应用程序告诉操作系统该发出什么样的声音。
这个关系好比有个机器人能够完成行走的功能,但是,如果人们不告诉它往哪个方向上走,机器人是不会主动行走的。
这里的机器人就是操作系统,人们就是应用程序
那么,应用程序是如何通知操作系统执行某个功能的呢?
有过编程经验的读者都应该知道,在应用程序中要完成某个功能,都是以函数调用的形式实现的,同样,应用程序也是以函数调用的方式来通知操作系统执行相应的功能的。
操作系统所能够完成的每一个特殊功能通常都有一个函数与其对应,也就是说,操作系统把它所能够完成的功能以函数的形式提供给应用程序使用,应用程序对这些函数的调用就叫做系统调用,这些函数的集合就是Windows操作系统提供给应用程序编程的接口(ApplicationProgrammingInterface),简称WindowsAPI。
如CreateWindow就是一个API函数,应用程序中调用这个函数,操作系统就会按照该函数提供的参数信息产生一个相应的窗口。
在这里注意:
我们不要把API与JAVA或其他的API混淆。
API是一个已经被广泛使用的专业术语,如果说某个系统某个设备提供给应用程序对其进行编程操作的函数,类,组件等的集合。
如做呼叫购买语音卡,提供一个开发包,开发包的函数集合称为语音卡的API。
向上的箭头④表示操作系统能够将输入设备的变化上传给应用程序。
如用户在某个程序活动时按了一下键盘,操作系统马上能够感知到这一事件,并且能够知道用户按下的是哪一个键,操作系统并不决定对这一事件如何作出反应,而是将这一事件转交给应用程序,由应用程序决定如何对这一事件作出反应。
好比有个蚊子叮了我们一口,我们的神经末梢(相当于操作系统)马上感知到这一事件,并传递给了我们的大脑(相当于应用程序),我们的大脑最终决定如何对这一事件作出反应,如将蚊子赶走,或是将蚊子拍死。
对事件作出反应的过程就是消息响应。
2.关于消息及消息队列
操作系统是怎样将感知到的事件传递给应用程序的呢?
这是通过消息机制(Message)来实现的。
操作系统将每个事件都包装成一个称为消息的结构体MSG来传递给应用程序,参看MSDN。
MSG结构定义如下:
typedefstructtagMSG{
HWNDhwnd;
UINTmessage;
WPARAMwParam;
LPARAMlParam;
DWORDtime;
POINTpt;
}MSG;
解释:
(MSDN是微软提供的程序开发的在线帮助系统,这个系统包括了微软的所有开发语言开发工具的帮助文件,信息很全每年发布4次,一个季度一次。
)
参数1HWNDhwnd;解释:
handletowindow
新的数据类型,指示窗口的句柄,何谓窗口?
屏幕上的一块矩形区域叫窗口,我们通常的开发的应用程序叫窗口程序,如VISUALC++由许多窗口构成,每个窗口又要起标识,当你去定义一个变量来存储这些标识时,你需要有存储定义这些标识的类型来定义变量,像定义整形变量存放整型数据,定义浮点类型变量存放浮点型数据,要存放窗口标识,需要有窗口句柄来定义这样的变量。
句柄句柄(HANDLE),资源的标识。
操作系统要管理和操作这些资源,都是通过句柄来找到对应的资源。
句柄类似指针,也需要占据一块内存,那么对于资源占据一块内存,我们要所引这块内存,索引资源,就是通过句柄找到的。
按资源的类型,又可将句柄细分成图标句柄(HICON),光标句柄(HCURSOR),窗口句柄(HWND),应用程序实例句柄(HINSTANCE)等等各种类型的句柄。
操作系统给每一个窗口指定的一个唯一的标识号即窗口句柄。
参数2UINTmessage无符号整型,消息就是具体一个消息,比如,按下鼠标左键,左键作为参数传递过来,整数表示方式。
数值不好记忆,微软定义很多宏,以WM开头,windowmessage,如鼠标左键按下LBUTTONDOWN,键盘按键,KEYDOWN
参数3WPARAMwParam;消息的附加信息
LPARAMlParam;都是整型。
关于消息的附加信息。
如按下A键,WM_CHAR消息,收到消息,我们并不知道那个字母,要想知道,把字母的ASCII码放到附加参数里,这样我们收到消息通过附加参数知道键盘上字母的ASII码。
MSDN查看WM_CHAR消息
既然WPARAMLPARAM都是整型,为什么要再产生两个新的数据类型?
我们看别人写的程序:
intx,y;
x=30;
y=30;
//x和y既可以用来表示坐标点,也可以用来表示宽度和高度,还可以用来表示身高和体重。
typedefintWIDTH
typedefintHEIGHT
WIDTHx;
HEIGHTy;
//好处:
我们从变量的类型上就可以知道x和y是用来表示宽度和高度。
这就是为什么在WINDOWS中微软为我们定义出这么多新的数据类型,虽然都是整型,
参数5DWORD32位整数,指示消息传递出的时间。
参数6POINT点结构体,x,y坐标,当消息被传递时光标在屏幕的位置。
拷贝,查看MSDN
由上看出消息结构体包含很多信息,它包含消息与哪个窗口相关,消息的具体内容是什么,消息的附加参数,消息投递时间和光标位置。
消息队列:
操作系统为每个应用程序都建立一个消息队列,是一个先进先出的缓冲区,通常是某种变量类型的数组,队列中的每个元素都是一个消息,操作系统将生成的每个消息按先后顺序存放到消息队列,应用程序总是取走队列中的第一个消息,消息取走后,第2条成为第1条,后面的依次前提,应用程序取得消息后就知道用户的操作和程序的变化,例如取得WM_CHAR消息,那么一定是用户输入了字符,并且知道是哪个字符。
应用程序得到消息后就要处理消息,这就是消息响应。
消息响应通过编码来实现,这就是WINDOWS程序的主要代码区,需要编写一些编码,这个过程叫消息响应。
在实现过程可以调用API函数,以便完成特定的功能,例如,我们收到关闭窗口的消息,那么可以调用销毁窗口的函数来关闭窗口,或者用弹出消息框的形式询问用户是否真的关闭窗口。
通过以上分析,我们知道要想开发WINDOWS应用程序,除了要具备良好的C++基础,还需要掌握两方面的东西:
一消息本身,知道不同的消息不同的消息代表的用户操作和程序状态本,。
二。
对于不同消息我们要让操作系统执行特定的功能去响应响应
WINMAIN函数
二,Windows程序的结构
1Windows程序的入口函数
intWINAPIWinMain(
HINSTANCEhInstance,//handletocurrentinstance
HINSTANCEhPrevInstance,//handletopreviousinstance
LPSTRlpCmdLine,//commandline
intnCmdShow//showstate
);
查看MSDN
说明:
参数1HINSTANCEhInstance:
应用程序实例句柄,何谓运行程序实例句柄,即运行中的程序,叫实例,再次运行,有两个实例。
这两个实例需要有个标识,同样需要有一种数据类型来定义变量来存放表示,这个类型就是HINSTANCE
参数2HINSTANCEhPrevInstance:
先前实例句柄,对第2个实例,前一个实例就是这个参数。
兄弟实例
注意:
基于w32的应用程序该参数为空如98,NT下开发不需要此参数,为空。
参数3LPSTRlpCmdLine类型是长指针,指向字符串首地址的指针。
命令行参数,
在DOS下MAIN函数可以有两个参数,argv[],argc[],argv[]指针数组,存放命令行参数,argc[]主要接受存放命令行参数个。
同样在WINDOWS程序中也可以接受命令行参数
运行“notepad1.txt”1.txt就是命令行参数
我们的程序中也可以接收命令行参数,project/settings/debug选项卡下programarguments输入“weixin.txt”,在程序中intWINAPIWinMain开始位置设置断点,然后调试运行。
参数4intnCmdShow显示状态,程序运行时是最大化,最小化或隐藏显示。
注意:
WinMain函数是入口点函数,由操作系统调用。
当操作系统启动我们的程序,它会给运行中的程序分配一个实例号,通过参数就传递进来,如果说启动时命令行参数,操作系统会将参数放在第3个参数中,参数都是由操作系统赋值。
2创建一个完整的窗口需要经过下面四个操作步骤:
设计一个窗口类;
注册窗口类;
创建窗口;
显示及更新窗口。
设计窗口WINDOWS已经做好框架,我们只需要填写一部分,如同做填空题一样,这就需要数据结构,结构体
设计一个窗口类;
原因:
程序运行时窗口的样式风格可以不一样,如光标在窗口是箭头还是十字,窗口的标题,背景等等,需要我们在设计窗口时就要确定的
产生一个窗口和设计汽车是一样,生产汽车前,我们都需要在图纸上画出汽车的图,雏形,然后起个名字,比如奔腾600,有了图纸照着生产汽车。
类似产生一个窗口也要设计窗口的特征,没有汽车复杂,windows已经给我们设计了窗口应该有的基本属性,我们要做的就跟做填空题一样,把它填写完整。
(查看MSDN)
typedefstruct_WNDCLASS{
UINTstyle;窗口类的类型,很多。
结合程序看含义-,水平重画和垂直重画,就跟汽车喷漆一样。
WNDPROClpfnWndProc;指针指向窗口过程,也称回调函数
intcbClsExtra;类额外数据,操作系统为系统中每个窗口类管理类结构,当程序注册窗口类,可让系统为追加额外内存空间,我们称为类的附加内存,由属于这种窗口类的所有窗口共享,cbClsExtra,通常为0
intcbWndExtra;操作系统为系统中每个窗口管理类结构,当程序注册窗口,可让系统为追加额外内存空间,我们称为窗口的附加内存
HANDLEhInstance;实例号,设计窗口类,要知道属于哪个
HICONhIcon;图标句柄,通过LoadIcon加载图标
HCURSORhCursor;
HBRUSHhbrBackground;
LPCTSTRlpszMenuName;
LPCTSTRlpszClassName;新类的名字
}WNDCLASS;
UINTstyle
参数1UINTstyle;窗口类的类型,很多。
结合程序看含义-,水平重画和垂直重画,就跟汽车喷漆一样。
在我们的程序中经常要用到一类变量,这个变量里的每一位(bit)都对应某一种特性。
当该变量的某位为1时,表示有该位对应的那种特性,当该位为0时,即没有该位所对应的特性。
当变量中的某几位同时为1时,就表示同时具有几种特性的组合。
一个变量中的哪一位代表哪种意义,不容易记忆,所以我们经常根据特征的英文拼写的大写去定义一些宏,该宏所对应的数值中仅有与该特征相对应的那一位(bit)为1,其余的bit都为0。
我们使用gotodefinition就能发现CS_VREDRAW=0x0001,CS_HREDRAW=0x0002,CS_DBLCLKS=0x0008,CS_NOCLOSE=0x0200。
他们的共同点就是只有一位为1,其余位都为0。
如果我们希望某一变量的数值既有CS_VREDRAW特性,又有CS_HREDRAW特性,我们只需使用二进制OR(|)操作符将他们进行或运算相组合,如style=CS_VREDRAW|CS_HREDRAW|CS_NOCLOSE。
如果我们希望在某一变量原有的几个特征上去掉其中一个特征,用取反(~)之后再进行与(&)运算,就能够实现,如在刚才的style的基础上去掉CS_NOCLOSE特征,可以用style&~CS_NOCLOSE实现。
参数2WNDPROClpfnWndProc;指针指向窗口过程,也称回调函数
回调函数的原理是这样的,当应用程序收到给某一窗口的消息时(还记得前面讲过的消息通常与窗口相关的吗?
),就应该调用某一函数来处理这条消息。
这一调用过程不用应用程序自己来实施,而由操作系统来完成,但是,回调函数本身的代码必须由应用程序自己完成。
对于一条消息,操作系统到底调用应用程序中的哪个函数(回调函数)来处理呢?
操作系统调用的就是接受消息的窗口所属的类型中的lpfnWndProc成员指定的函数。
每一种不同类型的窗口都有自己专用的回调函数,该函数就是通过lpfnWndProc成员指定的。
举例:
汽车厂家生产汽车好比应用程序创建窗口,用户使用汽车好比操作系统管理窗口,某种汽车在销售前就指定好了修理站(类似回调函数),当用户的汽车出现故障后(类似窗口收到消息),汽车用户(类似操作系统)自己直接找到修理站去修理,不用厂家(类似应用程序)亲自将车送到修理站去修理,但修理站还得由厂家事先建造好。
参数3intcbClsExtra;类额外数据,操作系统为系统中每个窗口类管理类结构,当程序注册窗口类,可让系统为追加额外内存空间,我们称为类的附加内存,由属于这种窗口类的所有窗口共享,cbClsExtra,通常为0,赋为0不是说内存为0,而是我们不需要这个参数。
参数4intcbWndExtra;操作系统为系统中每个窗口管理类结构,当程序注册窗口,可让系统为追加额外内存空间,我们称为窗口的附加内存
参数5HANDLEhInstance;实例号,设计窗口类,要知道属于哪个应用程序实例,因此,当我们对实例赋值时,从winmain中的这个参数,传递过来。
参数6HICONhIcon;图标句柄,通过LoadIcon加载图标,那图标句柄如何赋值?
查看MSDN:
HICONLoadIcon(
HINSTANCEhInstance,//handletoapplicationinstance
LPCTSTRlpIconName//namestringorresourceidentifier
);
当取标准图标时,null。
在windows编程中,当需要某些功能的函数时,可以凭感觉根据语义组合一些名称,如果,createwindow,destroywindow等。
当然符合不符合需要还得去看看含义。
时间长就记住了。
参数7HCURSORhCursor;光标句柄,用LoadCursor与LoadIcon接近,查MSDN
HCURSORLoadCursor(
HINSTANCEhInstance,//handletoapplicationinstance
LPCTSTRlpCursorName//nameorresourceidentifier
);
参数8HBRUSH画刷句柄hbrBackground=GetStockObject()
查MSDN:
HGDIOBJGetStockObject(
intfnObject//stockobjecttype
);
可以修改例子中的参数,改为白色背景。
就跟装修房子刷墙一样,刷什么颜色,墙就是什么颜色。
要有强制类型转换。
参数9LPCTSTRlpszMenuName;指向常量字符串的指针,指向菜单的名字
参数10LPCTSTRlpszClassName;新类的名字,
注册窗口类
设计完窗口类后,要向操作系统注册,RegisterClass(&类名)就如设计完汽车并不能直接生产,要上报进行注册。
查MSDNATOMRegisterClass(
CONSTWNDCLASS*lpWndClass//classdata窗体类结构体的指针
);
创建窗口
首先要定义一个句柄,因为创建窗口和生产汽车一样,生产多少汽车应该有数,同样创建窗口之后,哪个窗口的标识应该保存下来,有哪些窗口。
函数:
CreateWindow(“类名”),查msdn
CreateWindow(//创建具体窗口句柄
参数1LPCTSTRlpClassName//窗口类名,必须与注册时的名字相同,”weixin”,如果换一下,运行后看到,窗口没有产生,但是程序还是运行,从任务管理器可以看到winmain
原因?
参数2LPCTSTRlpWindowNameszTitle,//窗口名字,即标题栏内容
参数3DWORDdwStyle窗口类型即//显示方式,实例中的该参数点右键到gotodefinition
#defineWS_OVERLAPPEDWINDOW(WS_OVERLAPPED|\重叠窗口,有一个标题栏和边框
WS_CAPTION|\有标题栏
WS_SYSMENU|\
WS_THICKFRAME|\创建具有可调边框的
WS_MINIMIZEBOX|\
WS_MAXIMIZEBOX)
这就是以前说的二进制位表示特征,想要某位时置为1,否则置为假,我们可以看看上面这些宏,都是只有1位为1.演示去掉最大化按钮
参数4intx,CW_USEDEFAULT,//初始的X位置
参数5inty,CW_USEDEFAULT,//初始的Y位置
参数6intnWidthCW_USEDEFAULT,/窗口的宽度
参数7intnHeight,//窗口的高度
参数8HWNDNULL,//父窗口句柄,必须与注册时的名字相同
参数9HMENUNULL,//窗口菜单句柄,必须与注册时的名字相同
参数10HINCTANCEhInstance,//实例句柄,必须与程序使用的名字相同
参数11LPVOIDlpParamNULL//窗口创建时作为WMCREATE消息的lpParam附加消息通过结构体传入窗口的数据的指针
当窗口创建好后,就好像汽车造好,要开新闻发布会推出去一样,也需要显示出来。
显示调用函数ShowWindow(hWnd,nCmdShow);
其中第1个参数是窗口句柄,第2个参数是窗口初始显示的形式,nCmdShow参数说明是最大化显示还是最小化显示,应用程序窗口是活动的窗口还是图标,它可置为以“SW_”开始的任何常量,这些常量在windows.h中定义,在WinMain函数中,也是nCmdShow常量决定如何显示主窗口。
在用nCmdShow作为参数调用函数ShowWindow之后,WinMain还要调用UpdateWindow函数。
更新窗口。
不影响窗口更新,可有可无。
3消息循环
MSGmsg;//消息
MSGmsg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);转化翻译消息,它的作用是对取到的消息对进行转换,什么样的消息对?
当我们在键盘上按下某个按键时,系统会发出WM_KEYDOWN和WM_KEYUP的消息,并且参数中提供的键盘上刚才按键的扫描码,但有时候用户按下的某个键,我们想得到的是表示用户输入的字符的消息,也就是键盘上的某个字母,在消息的补充参数,就是附加参数中提供字母按键的ASCII码。
TranslateMessage能够将WM_KEYDOWN和WM_KEYUP这样的消息对转换为一个WM_CHAR消息,并且将转化后的新消息投递到消息队列中,这个转换过程不会影响原来的消息,只能增加一个消息。
DispatchMessage(&msg);是将我们收到的消息传给回调函数去处理。
我们可以理解为将这个消息路由给操作系统,操作系统去调用窗口过程函数,即回调函数进行处理。
};
}
BOOLGetMessage(
LPMSGlpMsg,//messageinformation,指向消息结构体的指针,out标识表明在传参时不需要给结构体内部数据成员进行初始化,我们只需要定义结构体的变量,将它的地址放在这就可以了。
那么通过函数调用会自动填充消息结构体内部的成员变量。
当
HWNDhWnd,//handletowindow获取哪个窗口消息,NULL,可以获取所有消息
UINTwMsgFilterMin,//firstmessage消息队列中有很多消息,有的我们感兴趣,有的不,也可以指定消息范围,指定消息最小值。
WM_KEYFIRST指定键盘第一个消息,WM_MOUSEFIRST来指示第一个鼠标消息。
UINTwMsgFilterMax//lastmessage消息最大值,
如果这两个值都设为0,就没有范围过滤了,所有消息都感兴趣。
);
这个消息返回值是BOOL,当取出一个消息时返回真,队列中只要有消息,返回值就是真,就构成循环。
回顾消息循环
我们再看一下窗口过程函数。
MSDN查看WNDCLASS
LRESULTCALLBACKWindowProc(
HWNDhwnd,//handletowindow
UINTuMsg,//messageidentifier
WPARAMwParam,//firstmessageparameter
LPARAMlParam//secondmessageparameter
);
注意:
:
这是C++程序开发时规定的格式,不能改的,函数名可以变,但是参数类型不能变,名字可以随便取。
LRESULT是LONG型的,长整型。
这4个参数和消息结构体的前4个参数比较类似,实际上是一样的,当DispatchMessage(&msg)执行时,把消息的前4个参数传递过来,消息的后2个参数没有用到,消息投递的时间和坐标在屏幕上的位置。
每当有消息产生时,都要调用窗口过程函数,因此要判断是哪个消息。
RESULTCALLBACKWinSunProc(
HWNDhwnd,//handletowindow
UINTuMsg,