windows编程 第四回 windows程序的生与死(下)
-----路过的朋友,若发现错误或有好的建议,欢迎在下面留言,谢谢!-----
致歉
很抱歉,读者看到的这篇文章将是我写的最没什么条理的一篇了,由于这一块儿内容是所有Windows程序的核心与基础,所以我分了三回来写。我想讲得既易懂有又丰富,无奈本人才疏学浅,每每一提笔就要查很多资料,我又尝试着把这些资料的精华融进文章里,但是对我来说谈何容易呀。但我又想或许这些原始资料对不同的读者会有不同的帮助,于是我就这样决定了:在本文开始的部分我再补充一些前两篇文章没讲完的一点内容,在后面我就罗列一下我所收集的原始资料,大部分是上一回文中出现的新概念和我想扩充的内容,有标号与上一回对应。我希望读者对本文前面的部分如同前面几篇文章一样仔细看,对后面我罗列的资料瞟一眼当参考就行了,能看懂多少就看多少,我不强求也不再详细解释了。Windows编程就是这样你看的越多,你发现不懂的问题也就越多。难怪古人云:“生有涯而知无涯”。
还有,本文中引用资料太多,我一时也记不清楚都引用过谁的了,无法一一列举,只得在此一并谢过被引用过的资料的作者了。
好了,或许我太罗嗦了,还是开始吧。
再谈程序之“死”
记得在第二回中我对程序的“死”只是一句话带过,因为我还没有铺垫好,好了现在我们可以详细的分析一下这个过程了。
这还要从while消息循环说起,还记得GetMessage 函数吗?它是一个BOOL类型函数,当它收到WM_QUIT消息时(即消息结构体的message成员变量为WM_QUIT,可见注释⑦),函数返回0,意味着消息循环结束。若收到除WM_QUIT之外的消息,函数就返回非0值了,消息循环继续进行。
再来研究一下WM_CLOSE消息喝和WM_DESTROY消息。不知读者是否注意到第二回与第三回的代码有没有什么不一样的地方?对了,第三回代码多了(加了行69-74这一段,就是用来处理WM_CLOSE消息的)该消息是用户单击Close按钮或者在程序的系统菜单上选择Close是发生的。在这段消息响应代码中,我们首先弹出一个消息框(这个函数讲解见下面),让用户确认是否结束(见图)。如果用户选择“否”,则什么也不做;如果用户选择“是”,则调用DestroyWindow函数销毁窗口,DestroyWindow 函数在销毁窗口后会向窗口过程发送WM_DESTROY 消息(这个函数就是这个作用,参数就是要销毁窗口的句柄,不再深入展开了)。注意,此时窗口虽然销毁了, 但应用程序并没有退出。有不少初学者错误地在WM_DESTROY消息的响应代码中,提示用户是否退出,而此时窗口已经销毁了,即使用户选择不退出,也没有什么意义了。所以如果你要控制程序是否退出,应该在WM_CLOSE消息的响应代码中完成。对 WM_CLOSE 消息的响应并不是必须的,如果应用程序没有对该消息进行响应,系统将把这条消息传给DefWindowProc 函数(参见行79),而DefWindowProc 函数则调用DestroyWindow 函数来默认响应这条WM_CLOSE 消息。
注:DefWindowProc 函数调用默认的窗口过程,对应用程序没有处理的其他消息提供默认处理。对于大多数的消息,应用程序都可以直接调用DefWindowProc函数进行处理。在编写窗口过程时,应该将DefWindowProc 函数(其参数与窗口过程函数参数一致就行)的调用放到default 语句中(请不要忘了写它),该函数的返回值将作为窗口过程函数的返回值。
tip:如果一个程序在终止之前要求来自用户的确认,那么窗口过程就需要捕获WM_CLOSE消息,例如上面所叙述的做法。
DestroyWindow 函数在销毁窗口后,会给窗口过程发送WM_DESTROY消息,我们在该消息的响应代码中调用PostQuitMessage 函数(见行76,用法不讲了,照例子写就行了)。PostQuitMessage函数向应用程序的消息队列中投递一条WM_QUIT 消息并返回。GetMessage 函数收到WM_QUIT 消息时返回0,此时消息循环才结束,程序退出。要想让程序正常退出,我们必须响应WM_DESTROY 消息,并在消息响应代码中调用PostQuitMessage,向应用程序的消息队列中投递WM_QUIT 消息。PostQuitMessage函数的参数值(见代码,通常是0)将作为WM_QUIT 消息的wParam 参数(见注释⑦),此时参数wParam为0。收到WM_QUIT 消息消息循环自然终止,然后程序执行下面的语句:return msg.wParam ;即返回0值正常退出WinMain并终止程序。
请再让我让我补充一点关于程序“出生”时遗漏的信息,当CreateWindow函数创建窗口后会发出WM_CREATE消息直接给窗口函数(怎么消息不进消息循环,自己去注释⑦找答案吧),让窗口函数在此时做一些初始化工作。这下是否你会明了为什么一运行第二回的程序会先出来一个消息框,然后再出来窗口了吧。(不知你当初是否会注意到我这样做的目的,我这样做是为了说明WM_CREATE消息出现的时机,当然我写的消息框函数不会做初始化工作,只是截获这个消息让读者看到罢了,初始化工作怎么完成,这我们就不用管了)。
好了我们再来一次“温故知新”吧,再回顾一下程序的一生,自己看图吧。(来自侯捷老师的《深入浅出MFC》)
过程讲解:
1程序初始化过程中调用CreateWindow,为程序建立了一个窗口,作为程序的屏幕舞台。CreateWindow 产生窗口之后会送出WM_CREATE 直接给窗口函数,后者于是可以在此时机做些初始化动作(例如配置内存、开文件、读初始资料...)。
2. 程序活着的过程中,不断以GetMessage 从消息贮列中抓取消息。如果这个消息是WM_QUIT,GetMessage 会传回0 而结束while 循环,进而结束整个程序。
3. DispatchMessage 透过Windows USER 模块的协助与监督,把消息分派至窗口函数。消息将在该处被判别并处理。
4. 程序不断进行2. 和3. 的动作。
5. 当使用者按下系统菜单中的Close 命令项,系统送出WM_CLOSE。通常程序的窗口函数不栏截此消息,于是DefWindowProc 处理它。(我们写的函数响应了WM_CLOSE,并调用了DestroyWindow,故不会有DefWindowProc参与,如果没写响应WM_CLOSE的DestroyWindow函数则按侯老师这么写的顺序看)
6. DefWindowProc 收到WM_CLOSE 后,调用DestroyWindow 把窗口清除。DestroyWindow 本身又会送出WM_DESTROY。
7. 程序对WM_DESTROY 的标准反应是调用PostQuitMessage。
8. PostQuitMessage 没什么其它动作,就只送出WM_QUIT 消息,准备让消息循环中的GetMessage 取得,如步骤2,结束消息循环。
代码改良(见行26,窗口注册),这样写比原来更好。我觉得本回的前半部分我讲完了,剩下的大家自己看吧,能看会看多少算多少,我就不管了。
自己看吧
LoadIcon 函数(代码行20)
在为 hIcon 变量赋值时,可以调用LoadIcon 函数来加载一个图标资源,返回系统分配
给该图标的句柄。该函数的原型声明如下所示:
HICON LoadIcon( HINSTANCE hInstance, LPCTSTR lpIconName)
LoadIcon 函数不仅可以加载Windows系统提供的标准图标到内存中,还可以加载由用户自己制作的图标资源到内存中,并返回系统分配给该图标的句柄,请参看MSDN 关于LoadIcon的解释。但要注意的是,如果加载的是系统的标准图标,那么第一个参数必须为NULL。LoadIcon 的第二个参数是LPCTSTR 类型,利用goto definition 命令将会发现它实际被定义成CONST CHAR*,即指向字符常量的指针,而图标的ID 是一个整数。对于这种情况我们需要用MAKEINTRESOURCE 宏把资源ID 标识符转换为需要的LPCTSTR 类型。
知识点 在 VC++中,对于自定义的菜单、图标、光标、对话框等资源,都保存在资源脚本(通常扩展名为.rc)文件中。在VC++开发环境中,要访问资源文件,可以单击左边项目视图窗口底部的ResourceView 选项卡,你将看到以树状列表形式显示的资源项目。在任何一种资源上双击鼠标左键,将打开资源编辑器。在资源编辑器中,你可以以“所见即所得”的方式对资源进行编辑。资源文件本身是文本文件格式,如果你了解资源文件的编写格式,也可以直接使用文本编辑器对资源进行编辑。在 VC++中,资源是通过标识符(ID)来标识的,同一个ID 可以标识多个不同的资源。资源的ID 实质上是一个整数,在“resource.h”中定义为一个宏。我们在为资源指定ID 的时候,应该养成一个良好的习惯,即在“ID”后附加特定资源英文名称的首字母,例如,菜单资源为IDM_XXX(M 表示Menu),图标资源为IDI_ XXX(I 表示Icon),按钮资源为IDB_ XXX(B 表示Button)。采用这种命名方式,我们在程序中使用资源ID时,可以一目了然。
LoadCursor 函数(行19)
在为 hCursor 变量赋值时,可以调用LoadCursor 函数来加载一个光标资源,返回系统分配给该光标的句柄。该函数的原型声明如下所示:
HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName);
LoadCursor 函数除了加载的是光标外,其使用方法与LoadIcon 函数一样。
GetStockObject 函数(行18)
调用 GetStockObject 函数可得到系统的标准画刷。GetStockObject 函数的原型声明如下所示:
HGDIOBJ GetStockObject( int fnObject);
参数 fnObject 指定要获取的对象的类型,关于该参数的取值,请参看MSDN。
GetStockObject 函数不仅可以用于获取画刷的句柄,还可以用于获取画笔、字体和调色板(后几回会讲)的句柄。由于GetStockObject 函数可以返回多种资源对象的句柄,在实际调用该函数前无法确定它返回哪一种资源对象的句柄,因此它的返回值的类型定义为HGDIOBJ,在实际使用时,需要进行类型转换。例如,我们要为hbrBackground 成员指定一个黑色画刷的句柄,可以调用如下:
wndclass. hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
当窗口发生重绘时,系统会使用这里指定的黑色画刷擦除窗口的背景。
MessageBox函数(行60)
函数功能:
该函数创建、显示、和操作一个消息框。消息框含有应用程序定义的消息和标题,加上预定义图标与Push(下按)按钮的任何组合。
函数原型:
int MessageBox(HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UINT UType);
参数:hWnd:标识将被创建的消息框的拥有窗口。如果此参数为NULL,则消息框没有拥有窗口。
lpText:指向一个以NULL结尾的、含有将被显示的消息的字符串的指针。
lpCaption:指向一个以NULL结尾的、用于对话框标题的字符串的指针。
uType:指定一个决定对话框的内容和行为的位标志集。(具体请自行查阅MSDN或维基、百度百科,自行测试)
返回值:
如果没有足够的内存来创建消息框,则返回值为零。如果函数调用成功,则返回值为下列对话框返回的菜单项目值中的一个:
IDABORT:Abort 按钮被选中。IDCANCEL:Cancel按钮被选中。IDIGNORE:Ignore按钮被选中。 IDNO:NO按钮被选中。IDOK:OK按钮被选中。IDRETRY:RETRY按钮被选中。 IDYES:YES按钮被选中。
效果图见第二回,消息框点击按钮返回IDOK,本回的消息框点击按钮会返回什么呢?。
行63-67函数,他们实现在窗口显示文字。(具体用法以后讲)
①Windows程序的开头都可看到:
#include <windows.h>
WINDOWS.H是主要的头文件,它包含了其他Windows头文件,这些头文件的某些也包含了其他头文件。这些头文件中最重要的和最基本的是:
WINDEF.H 基本型态定义。
WINNT.H 支援Unicode的型态定义。
WINBASE.H Kernel函数。
WINUSER.H 使用者界面函数。
WINGDI.H 图形装置界面函数。
这些头文件定义了Windows的所有资料型态、函数调用、资料结构和常数识别字,它们是Windows文件中的一个重要部分。
②另一种解释:在Windows环境中,句柄是用来标识项目的,这些项目包括:模块(module)、任务(task)、实例 (instance)、文件(file)、内存块(block of memory)、菜单(menu)、控制(control)、字体(font)、资源(resource),包括图标(icon),光标 (cursor),字符串(string)等、GDI对象(GDI object),包括位图(bitmap),画刷(brush),元文件(metafile),调色板(palette),画笔(pen),区域 (region),以及设备描述表(device context)。
句柄可以理解为用于指向或标识内存的一块“资源”,这些资源如:文件(file)、内存块(block of memory)、菜单(menu)等等。操作系统通过句柄来定位核心对象和系统资源。
类型 |
说明 |
HANDLE |
通用句柄类型 |
HWND |
标识一个窗口对象 |
HDC |
标识一个设备对象 |
HMENU |
标识一个选单对象 |
HICON |
标识一个图标对象 |
HCURSOR |
标识一个光标对象 |
HBRUSH |
标识一个刷子对象 |
HPEN |
标识一个笔对象 |
HFONT |
标识一个字体对象 |
HINSTANCE |
标识一个应用程序模块的一个实例 |
HLOCAL |
标识一个局部内存对象 |
HGLOBAL |
标识一个全局内存对象 |
分享一篇讨论句柄的文章:
所谓句柄实际上是一个数据,是一个Long (整长型)的数据。
句柄是WONDOWS用来标识被应用程序所建立或使用的对象的唯一整数,WINDOWS使用各种各样的句柄标识诸如应用程序实例,窗口,控制,位图,GDI对象等等。WINDOWS句柄有点象C语言中的文件句柄。
从上面的定义中的我们可以看到,句柄是一个标识符,是拿来标识对象或者项目的,它就象我们的姓名一样,每个人都会有一个,不同的人的姓名不一样,但是,也可能有一个名字和你一样的人。从数据类型上来看它只是一个16位的无符号整数。应用程序几乎总是通过调用一个WINDOWS函数来获得一个句柄,之后其他的WINDOWS函数就可以使用该句柄,以引用相应的对象。
如果想更透彻一点地认识句柄,我可以告诉大家,句柄是一种指向指针的指针。我们知道,所谓指针是一种内存地址。应用程序启动后,组成这个程序的各对象是住留在内存的。如果简单地理解,似乎我们只要获知这个内存的首地址,那么就可以随时用这个地址访问对象。但是,如果您真的这样认为,那么您就大错特错了。我们知道,Windows是一个以虚拟内存为基础的操作系统。在这种系统环境下,Windows内存管理器经常在内存中来回移动对象,依此来满足各种应用程序的内存需要。对象被移动意味着它的地址变化了。如果地址总是如此变化,我们该到哪里去找该对象呢?
为了解决这个问题,Windows操作系统为各应用程序腾出一些内存储地址,用来专门登记各应用对象在内存中的地址变化,而这个地址(存储单元的位置)本身是不变的。Windows内存管理器在移动对象在内存中的位置后,把对象新的地址告知这个句柄地址来保存。这样我们只需记住这个句柄地址就可以间接地知道对象具体在内存中的哪个位置。这个地址是在对象装载(Load)时由系统分配给的,当系统卸载时(Unload)又释放给系统。
句柄地址(稳定)→记载着对象在内存中的地址————→对象在内存中的地址(不稳定)→实际对象
本质:WINDOWS程序中并不是用物理地址来标识一个内存块,文件,任务或动态装入模块的,相反的,WINDOWS API给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。
但是必须注意的是程序每次从新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄,而且绝大多数情况的确不一样的。假如我们把进入电影院看电影看成是一个应用程序的启动运行,那么系统给应用程序分配的句柄总是不一样,这和每次电影院售给我们的门票总是不同的一个座位是一样的道理。
③知识点 在 Windows.h 中,以CS_开头的类样式(Class Style)标识符被定义为16位的常量,这些常量都只有某1 位为1。在VC++开发环境中,利用goto definition 功能,
可以看到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 程序中,经常会用到这种位标志标识符,后面我们在创建窗口时用到的窗口样式,也是属于位标志标识符。
常用样式,作为参考
类型 |
说明 |
CS_HREDRAW |
如果窗口的水平尺寸被改变,则重画整个窗口 |
CS_VREDRAW |
如果窗口的垂直尺寸被改变,则重画整个窗口 |
CS_BYTEALIGNCLIENT |
在字节边界上(在X方向上)定位用户区域的位置 |
CS_BYTEALIGNWINDOW |
在字节边界上(在X方向上)定位窗口的位置 |
CS_DBLCLKS |
当连续两次按动鼠标键时向窗口发送该事件的消息 |
CS_GLOBALCLASS |
定义该窗口类是一个全局类。全局类由应用程序或库建立,并且所有的应用程序均可使用全局类 |
CS_NOCLOSE |
禁止系统选单中的Close选项 |
④ Windows下的函数
在进行Windows应用程序设计中,程序员除了需要知道有关一个函数的常用信息(例如函数的名字,近函数或远函数,返回类型以及应如何调用)之外,同时还要知道更多的内容:一个回调函数、引出函数或是一个引入函数。
导出函数(引出函数):这个术语与一个函数如何在一个模块中说明而在另一个模块中被调用有关。导出函数是在一个模块中定义而在这个模块之外被调用的一种函数;或是被Windows或是被另一个模块调用。这些函数必须以一种特定的方式进行说明,并被编译器作特殊的处理。这样,当它们被调用时,它们会被正确地束定到合适的数据段上。DLL为其它模块提供要被调用的函数,因此,每个DLL一般都带有一个DLL库,以便应用程序可以合法地调用DLL中的函数。DLL库由DLL中每个导出函数的入口点组成。整个Windows API就是由构成Windows环境的不同的模块所引出的函数组成,这些API函数的入口点在一个名为IMPORT.LIB的DLL库中说明。
导入函数(引入函数):在DLL中引出的函数若要能为一个模块调用,必须在这个模块中将这个函数说明为导入函数。由此可见导出函数和导入函数表达的是从两种角度处理同一个函数的术语:导出模块中的一个函数使得这个函数能被其它模块调用;调用导出函数的模块通过导入这个函数才能调用它。在制作Windows应用程序时,连接器自动包含一个名为IMPORT.LIB的库文件。这个文件允许应用程序调用Windows API中的函数。这个文件被称为导入库。引入库提供了应用程序与一个到多个DLL中可被这个应用程序调用的函数之间的连接。
回调函数:回调函数是一种特殊的导出函数,是由 Windows环境直接调用的函数。一个应用程序至少要有一个回调函数。当一条消息要交给应用程序处理时,Windows调用这个回调函数。这个函数对应于一个活动窗口,被称为这个窗口的窗口函数。因为许多应用程序至少建立一个窗口,并且Windows需要向这个窗口发送消息,所以,处理消息的函数必须由Windows调用。在请求Windows枚举它所维护的对象时,例如字体或窗口,Windows也要调用应用程序中的回调函数。当向Windows提出这样的请求时,就必须向Windows提供回调函数的地址。
由于导出函数是在不同的模块中被调用的,也就是说,调用者的代码段与被调用的引出函数的代码段不在同一个段中,因此,在所开发的Windows应用程序中,导出函数都被说明为远函数。为了程序运行的效率原因,导出函数都使用Pascal调用约定,这种调用约定不同于C调用约定的地方在于:
- 最左边的参数先入栈:Pascal调用约定的参数进入栈的顺序是函数调用中最左边的参数先入栈。C的调用约定与此相反,它采用最右边的参数先入栈。
- 被调用的函数负责从展中清除参数:Pascal调用约定的函数在返回时负责清除栈中的参数;C调用约定的函数不作这种工作,而由调用者来作;这样,当程序中调用了大量的使用C调用约定的函数时,为清除栈中的参数,在程序中要额外地增加许多代码。
- 全局标识符不保持原来的大小写(一般被为大写形式),也不在标识符前面加下划线。
为便于程序开发活动,在Windows.h中定义了两个类型名,用于在程序说明引出函数:
类型 |
说明 |
WINAPI |
等价于FAR PASCAL,说明该函数是一个导出函数,这个类型名只用于在DLL中说明引出函数,或在应用程序中对DLL中的导出函数进行函数说明时。 |
CALLBACK |
等价于FAR PASCAL,说明该函数是一个回调函数,它常被用在应用程序模块中说明一个窗口函数或其它种类的回调函数。 |
回调函数另一种讲法(孙鑫):
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。回调函数实现的机制是:
(1)定义一个回调函数。
(2)提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者。
(3)当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。
针对 Windows 的消息处理机制,窗口过程函数被调用的过程如下:
(1)在设计窗口类的时候,将窗口过程函数的地址赋值给lpfnWndProc 成员变量。
(2)调用RegsiterClass(&wndclass)注册窗口类,那么系统就有了我们所编写的窗口过程函数的地址。
(3)当应用程序接收到某一窗口的消息时,调用DispatchMessage(&msg)将消息回传给系统。系统则利用先前注册窗口类时得到的函数指针,调用窗口过程函数对消息进行处理。
一个 Windows 程序可以包含多个窗口过程函数,一个窗口过程总是与某一个特定的窗口类相关联(通过WNDCLASS 结构体中的lpfnWndProc 成员变量指定),基于该窗口类创建的窗口使用同一个窗口过程。lpfnWndProc 成员变量的类型是WNDPROC,我们在VC++开发环境中使用gotodefinition 功能,可以看到WNDPROC 的定义:
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
在这里又出现了两个新的数据类型 LRESULT 和CALLBACK,再次使用goto definition,可以看到它们实际上是long 和__stdcall。
从 WNDPROC 的定义可以知道,WNDPROC 实际上是函数指针类型。
注意:WNDPROC 被定义为指向窗口过程函数的指针类型,窗口过程函数的格式必须与WNDPROC 相同。
知识点 在函数调用过程中,会使用栈。__stdcall 与__cdecl 是两种不同的函数调用约定,定义了函数参数入栈的顺序,由调用函数还是被调用函数将参数弹出栈,以及产生函数修饰名的方法。关于这两个调用约定的详细信息,读者可参看MSDN。对于参数个数可变的函数,例如printf,使用的是__cdecl 调用约定,Win32 的API 函数都遵循__stdcall 调用约定。在VC++开发环境中,默认的编译选项是__cdecl,对于那些需要__stdcall 调用约定的函数,在声明时必须显式地加上__stdcall。在Windows 程序中,回调函数必须遵循__stdcall 调用约定,所以我们在声明回调函数时要使用CALLBACK。使用CALLBACK 而不是__stdcall 的原因是为了告诉我们这是一个回调函数。注意,在Windows 98 和Windows 2000 下,声明窗口过程函数时,即使不使用CALLBACK也不会出错,但在Windows NT4.0 下,则会出错。
⑤MAKEINTRESOURCE是一个资源名转换的宏,
VC的定义是(winuser.h):
#define MAKEINTRESOURCEA(i) (LPSTR)((ULONG_PTR)((WORD)(i)))
#define MAKEINTRESOURCEW(i) (LPWSTR)((ULONG_PTR)((WORD)(i)))
#ifdef UNICODE
#define MAKEINTRESOURCE MAKEINTRESOURCEW
#else
#define MAKEINTRESOURCE MAKEINTRESOURCEA
#endif // !UNICODE
这个宏是把一个数字类型转换成指针类型的宏,它不存在释放的问题.
用这个宏的主要原因是有的资源是用序号定义的,而不是字符串.所以要把数字转换成字符串指针,然后再传递给LoadResource之类的函数,这样才加载了资源.
要释放资源(用LoadResource加载的)可以调用FreeResource函数把LoadResource返回的指针传递给FreeResource.
MAKEINTRESOURCE 的作用:
是把一个"数字形ID",转化为"字符串".但是执行前后,输入的数据的内容和长度是不变的!它只不过就是C语言里面"强制类型转换"而已.
请看 Winuser.h 代码:
#define MAKEINTRESOURCEA(i) (LPSTR)((DWORD)((WORD)(i)))
#define MAKEINTRESOURCEW(i) (LPWSTR)((DWORD)((WORD)(i)))
#ifdef UNICODE
#define MAKEINTRESOURCE MAKEINTRESOURCEW
#else
#define MAKEINTRESOURCE MAKEINTRESOURCEA
#endif // !UNICODE
现在,再来归纳它的用法.就用FindResource来说明.(这个函数与MFC的AfxFindResourceHandle)
HRSRC FindResource(
HMODULE hModule, // module handle
LPCTSTR lpName, // resource name
LPCTSTR lpType // resource type
);
就是lpName参数需要使用MAKEINTRESOURCE ,因为它需要LPCTSTR类型的参数输入.那么,情况就很清楚了.凡涉及"资源"的API或者MFC类,在参数类型为LPCTSTR时,就应该使用 MAKEINTRESOURCE.这是针对"资源名字"为"数字类型"时的情况.
⑥窗口样式一览
类型 |
说明 |
WS_BORDER |
创建一个有边框的窗口 |
WS_CAPTION |
创建一个有标题栏的窗口 |
WS_CHILDWINDOW(or WS_CHILD) |
创建一个子窗口(不能与WS_POPUP一起使用) |
WS_CLIPCHILDREN |
当在父窗口内绘制时,把子窗口占据的区域剪切在外,即不在该区域内绘图 |
WS_CLIPSIBLINGS |
裁剪相互有关系的子窗口,不在被其它子窗口覆盖的区域内绘图,仅与WS_CHILD一起使用 |
WS_DISABLED |
创建一个初始被禁止的窗口 |
WS_DLGFRAME |
创建一个有双边框但无标题的窗口 |
WS_HSCROLL |
创建一个带水平滚动杠的窗口 |
WS_VSCROLL |
创建一个带垂直滚动杠的窗口 |
WS_ICONIC |
创建一个初始为图标的窗口,仅可以与WS_OVERLAPPEDWINDOWS一起使用 |
WS_MAXIMIZE |
创建一个最大尺寸的窗口 |
WS_MINIMIZE |
创建一个最小尺寸的窗口(即图标) |
WS_MAXIMIZEBOX |
创建一个带有极大框的窗口 |
WS_MINIMIZEBOX |
创建一个带有极小框的窗口 |
WS_OVERLAPPED |
创建一个重叠式窗口,重叠式窗口带有标题和边框 |
WS_POPUP |
创建一个弹出式窗口,不能与WS_CHILD一起使用 |
WS_SYSMENU |
窗口带有系统选单框,仅用于带标题栏的窗口 |
WS_THICKFRAME |
创建一个边框的窗口,使用户可以直接缩放窗口 |
WS_VISIBLE |
创建一个初始可见的窗口 |
在Windows.h中,还定义了风格WS_OVERLAPPEDWINDOW和WS_POPUPWINDOW。其中,WS_OVERLAPPEDWINDOW由下面的宏进行定义:
#define WS_OVERLAPPEDWINDOW(WS_OVERLAPPED | WS_CAPTION
| WS_SYSMENU |WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
)
WS_POPUPWINDOW定义为:
#define WS_POPUPWINDOW ( WS_BORDER | WS_POPUP | WS_SYSMENU )
但是,在使用WS_POPUPWINDOW时,必须组合WS_CAPTION ,否则不能使系统选单(WS_SYSMENU)在窗口上可见。另外 两个窗口风格是WS_GROUP和WS_TABSTOP,这两个窗口风格的意义在介绍对话框时进行介绍,在介绍对话框时,还将介绍其它窗口风格。
⑦“在 Windows 程序中,消息是由MSG 结构体来表示的。MSG 结构体的定义如下(仅了解,结构体成员变量系统给我们填,我们不用管):
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
该结构体中各成员变量的含义如下:
第一个成员变量 hwnd 表示消息所属的窗口。我们通常开发的程序都是窗口应用程序,一个消息一般都是与某个窗口相关联的。例如,在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。在Windows 程序中,用HWND 类型的变量来标识窗口。
第二个成员变量 message 指定了消息的标识符。在Windows 中,消息是由一个数值来表示的,不同的消息对应不同的数值。但是由于数值不便于记忆,所以Windows 将消息对应的数值定义为WM_XXX 宏(WM 是Window Message 的缩写)的形式,XXX 对应某种消息的英文拼写的大写形式。例如,使用者将鼠标光标放在本程序的显示区域之内,并按下鼠标左按钮,Windows就在消息队列中放入一个消息,该消息的message字段等于WM_LBUTTONDOWN。键盘按下消息是WM_KEYDOWN,字符消息是WM_CHAR,等等。在程序中我们通常都是以WM_XXX 宏的形式来使用消息的。(回忆一下我们上一回是不是见过了类似WM_XXX的消息)
提示:如果想知道WM_XXX 消息对应的具体数值,可以在Visual C++开发环境中选中WM_XXX,然后单击鼠标右键,在弹出菜单中选择gotodefinition,即可看到该宏的具体定义。跟踪或查看某个变量的定义,都可以使用这个方法。
第三、第四个成员变量wParam 和lParam,用于指定消息的附加信息。例如,当我们收到一个字符消息的时候,message 成员变量的值就是WM_CHAR,但用户到底输入的是什么字符,那么就由wParam 和lParam 来说明。wParam、lParam 表示的信息随消息的不同而不同。如果想知道这两个成员变量具体表示的信息,可以在MSDN 中关于某个具体消息的说明文档查看到。读者可以在VC++的开发环境中通过goto definition 查看一下WPARAM 和LPARAM 这两种类型的定义,可以发现这两种类型实际上就是unsigned int和long。
最后两个变量分别表示消息投递到消息队列中的时间和鼠标的当前位置坐标。
进队消息和不进队消息
其实在Windows中消息能够被分为“进队的”和“不进队的”。进队的的消息是由Windows放入程序消息队列中的。在程序的消息循环中,重新返回并分配给窗口过程。不进队的的消息在Windows调用窗口时直接送给窗口过程。也就是说,进队的消息被“发送”给消息队列,而不进队的的消息则直接“发送”给窗口过程。任何情况下,窗口过程都将获得窗口所有的消息--包括进队的和不进队的。窗口过程是窗口的“消息中心”。
进队消息基本上是用户输入的结果,以击键(如WM_KEYDOWN和WM_KEYUP消息)、击键产生的字符(WM_CHAR)、鼠标移动(WM_MOUSEMOVE)和鼠标键(WM_LBUTTONDOWN)的形式给出。进队消息还包含时钟消息(WM_TIMER)、刷新消息(WM_PAINT)和退出消息(WM_QUIT)。
不进队消息则是其它消息。在许多情况下,不进队消息来自调用特定的Windows函数。例如,当WinMain调用CreateWindow时,Windows将创建窗口并在处理中给窗口过程发送一个WM_CREATE消息。当WinMain调用ShowWindow时,Windows将给窗口过程发送WM_SIZE和WM_SHOWWINDOW消息。当WinMain调用UpdateWindow时,Windows将给窗口过程发送WM_PAINT消息。键盘或鼠标输入时发出的进队消息信号,也能在不进队消息中出现。例如,用键盘或鼠标选择了一个菜单项时,键盘或鼠标消息就是进队的,而说明菜单项已选中的WM_COMMAND消息则可能就是不进队的。这一过程显然很复杂,但幸运的是,其中的大部分是由Windows解决的,不关我们的程序的事。
匈牙利表示法
许多Windows程序写作者使用一种叫做“匈牙利表示法”的变量命名规则。这是为了纪念传奇性的Microsoft程序员Charles Simonyi。非常简单,变量名以一个或者多个小写字母开始,这些字母表示变量的数据型态。例如,szCmdLine中的sz代表“以0结尾的字符串”,在hInstance和hPrevInstance中的h前缀表示“句柄”。在命名结构变量时,可以用结构名(或者结构名的一种缩写)的小写作为变量名的前缀,或者用作整个变量名。例如,在 WinMain函数中,msg变量是MSG类型的变量;wndcls是WNDCLASS类型的一个变量。在WinSunProc函数中,ps是一个PAINTSTRUCT结构变量,rect是一个RECT结构变量
表 1.1 匈牙利表示法
前 缀 含 义
a 数组
b 布尔值(int)
by 无符号字符(字节)
c 字符(字节)
cb 字节记数
rgb 保存 RGB 颜色值的长整型
cx,cy 短整型(计算x,y 的长度)
dw 无符号长整型
fn 函数
h 句柄
i 整数(integer)
m_ 类的数据成员
n 短整型或整型
np 近指针
p 指针
l 长整型
lp 长指针
s 字符串
sz 以零结束的字符串
tm 正文大小
w 无符号整型
x,y 无符号整型(表示x 或y 的坐标)。
大写字母标识符
在前面大家一定见过类似CS_HREDRAW,WS_MAXIMIZE的标志符,这些标识符很有规律,都是有两个(有的有三个,只是我们还没碰到)大写字母并跟一个下划线组成前缀。这些标识符都是在Windows的头文件中定义的数值常量。前缀指示该常量所属的类别。其前缀含义见下表。
前缀 |
类别 |
CS |
窗口类别样式 |
CW |
建立窗口 |
DT |
绘制文字 |
IDI |
图示ID |
IDC |
游标ID |
MB |
消息框 |
SND |
声音 |
WM |
窗口消息 |
WS |
窗口样式 |