大致说来windows编程有两种方法: 1.windwos c方式(SDK), 2.c++方式:即对SDK函数进行包装,如VC的MFC,BCB的OWL等,如果要深入下去,还是要熟悉SDK。
两种方法有哪些区别呢:SDK编程就是直接调用windows的API进行编程,但是有上千个API组成(win95的API有两千多个),这种数目太大了,对于编程显然不利。而MFC把这些API封闭起来,共有一百多个类组成。一般只需20多个windows类和另外20多个通用的非windows类就可"干活"了,这一改变无疑是有很大好处的。尽管MFC如此方便,但是要学VC,直接去学MFC却是不明智的选择。只有在熟悉了MFC的运行机制的情况下,才有可能深入下去。那些如多少天精通什么什么的书籍其实讲的全是些如怎么使用VC这种工具的话题,学来学去学会了怎么会使用VC这种工具,而不能深入MFC编程。象VB这类工具就更令人感觉到太闷了,不过各有各的好处。
MFC虽然提高了程序员编程的效率,但是也失去了SDK编程的灵活性.....不好意思,跑题了....
本专题假定:
C语言是windows的源语言,用C进行SDK编程是最合适的,你应该至少学过C语言,知道C语言的各种语法等,当我说到指针等类似概念的时候,你不至于茫然就行了。
你还应该安装了Visual C++6.0,低版本的就不用用了吧,D版的才几块钱吗?
推荐与参考资料:
1.Petzold的[programming windows by petzold],这是我看过的一本书(电子书),不过是E文的,我只看了一半,"下载空间"一栏能供了该电子书。中文版的是[Windows 程序设计](包括上,下卷),Charles Petzold著,北京博彦发展有限公司译,北京大学出版社出版,本书唯一的缺点就是太贵,价格是160元。一些朋友让我多推荐一些这方面的书,我也没办法,一是这类书目前本就很少,二是如果你非要买的话,肯定要买这本书了,可是经典呢!!
2.还有一本老书值得一提,可能市面上已经买不到了,书名[Windows 编程],刘振安著,人民邮电出版社出版发行。一个朋友曾借给我看了几天我又还了,很容易读懂的,不过一看日期,95年出版,得!没希望了。我们学校图书馆好像也有几本,不过好像都很烂。
3.VC的在线帮助MSDN,有关SDK的资料在"Platform SDK"一栏中。
下面再加深几点概念:
什么是API:
什么是API,有必要再谈一下,特别对于从TC过来的朋友和初学编程的朋友。API,全
称application program interface,意思是应用程序编程接口(说起API并不仅仅指windows而言,windows支持的API叫winapi)。winapi就是应用程序和windows之间通讯的一个编程界面。windows提供了上千个API函数,以方便程序员来编写应用程序。做数据库管理系统是很少和API 打交道的,我以前学PB,看的十几本书都没有专门的章节来介绍API的。只有到网上看那些PB技巧之类的文章才谈及API,不过还要加入一大堆声明之类的东东。好像VB也是这样。总之,进行系统编程,API是必不可少的。API是每个VC高手的必过的一关。
什么是WinSDK编程:
WinSDK程序设计就是API方式的windows程序设计。SDK,全称Software Developers Kit,意思是软件开发工具箱。它是在windows程序设计早期,程序员进行windows程序设计所必须购买的一个软件包(不知道那时候有没有D版的)。我两个月前见SDK还不知所云。开始进行WinSDK编程用C 语言最好,可以撇开C++的特性专心熟悉一下Win32编程的思路以及了解windows的消息运行机制。等把这些有所了解后,慢慢转向C++和MFC编程。不过我的观点是WinSDK编程永远要慢慢进行下去,直接接触底层代码对MFC源码的理解是有很大好处的。上周开始用MFC时,按F11进行单步跟踪,一开始见到"_tWinMain"这样古怪的东东也没有感觉到太大的迷惑,不就是相当于SDK编程中的WinMain函数吗?
什么是MFC:
MFC,全称Microsoft Foundation Classes,伪软把WinAPI进行封装的类库。它是一个类的集合,通过覆盖WinAPI,为编程提供了一个面向对象的界面。它使windows程序员能够利用C++面象对象的特性进行编程,类似BCB的OWL,Delphi的VCL组件。它把那些进行SDK编程时最繁琐的部分提供给程序员,使之专注于功能的实现。你不妨把它想象成类似TC提供的函数库吧。用
Visual C++这种开发工具编程时,可以用MFC,也可以不用,但MFC是VC的精华所在,从某种意义上来说,不会MFC就是不会使用VC。研究MFC源代码是件既快乐又痛苦的事情,不过,阵痛之后,嘻嘻,......
怎么进行WinSDK编程:
Visual C++6.0:File-->new-->Projects-->Win32 Application(project name)-->......
我们既然用C语言进行,就选第一项"An empty project"......
SDK编程讲座之第二讲:WinMain与两只小板凳的故事(转载)
第一只小板凳:
打开"an empty project"后,在VC的IDE中,打开文本编辑器。输入以下内容:
#include "windows.h"
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCm dLine, int iCmdShow)
{MessageBox (NULL, TEXT ("你好,欢迎来到VC之路"), TEXT ("欢迎"), 0) ;
return 0 ;
}
把它保存成以".c"为扩展名的文件,并加入空项目中。编译......
看到了吧,这就我们的第一只小板凳。它就类似于TC下的"hello world"吧!虽然什么功能也没实现,但它比"hello world"强多了,因为它向我们展示了一个小窗口。
支解第一只小板凳:
像TC一样,它包含一个头文件"windows.h",这是一个最基本的头文件,它包含其它的windows 头文件.在你的"X:\\Program Files\\Microsoft Visual Studio\\VC98\\Include"目录下,打开windows.h这个文件,你就知道它包含什么东东了。
像dos下的C编程的main()一样,windows下的入口是WinMain()函数。
把光标放在WinMain()上,按F1键。启动MSDN,如果你没安装的话,一定要安装一下,MSDN是最好的学习VC的资料,当然,对于我们入门者,有一本书从头到尾学一下最好。
第一个参数:应用程序的当前实例句柄。
第二个参数:应用程序的前一个实例句柄,别管它,对于Win32位而言,它一般是NULL.
第三个参数:指向任何传给程序的命令行参数。PSTR代表"指向字符串的指针"。
第四个参数:它告诉应用程序如何初始化窗口,如最大化,最小化等状态。
WinMain()所起的作用:初始化,展示,销毁应用程序等。
MessageBox(),如果大家在windows下编程,这是一个很常用的API,不用我费话了吧!
几个要点:
1.句柄(handle):在标准C库中句柄用来对文件输入输出。如下面的代码:
int handle;
handle=open("filename
if(handle)
{read(handle,block,bytesToread);
}
close(handle);
在文件被成功打开后,open()返回一个句柄,在read()中使用这个句柄来阅读这个文件。句柄不是指针。程序不能直接使用句柄来阅读文件中的信息。如果不能把它传送给输入输出函数调用的话,句柄就没有用了。句柄不返回零。句柄命名以h开始。这是匈牙利表示法的规定。
2.匈牙利表示法:
初开始看到这个程序时,你一定感觉到很怪,我当时就是这样的。不知道那些字母怎么回事。因为所有的命名采用了,匈牙利表示法。如消息的前缀使用msg.句柄使用h.函数使用fn等。多说了,习惯了就好了.
3.有效使用MSDN:
想成为VC高手,还要强迫自己习惯于使用MSDN,看得多了,不怕看不懂,如果仅仅知道功能不知道具体的函数,可用类似的英语单词去搜索。我平时就用金山词霸一点点的啃。唉,说起来惭愧,本人英语四级没过,这学期连报考都没有。以后努力了,总感觉到大学的教育很有问题,好像每个人都在为四六级而奔忙。
呵呵,说了这些,真怀疑对你真的有用。有不对的地方,请指出。希望大家共同进步,把VC活吃了.......
第二只小板凳
打开VC,file-->new-->progects-->win32 application,并在project name 中输入hello.顺路走下来,选择第二项。在编辑器中输入以下程序覆盖向导产生的代码:
#include "stdafx.h" //注意,这个向导产生的头文件不能去掉
#include LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {static TCHAR szAppName[] = TEXT ("HelloWin") ; HWND hwnd ; MSG msg ; WNDCLASS wc ; wc.style = CS_HREDRAW | CS_VREDRAW ; wc.lpfnWndProc = WndProc ; wc.cbClsExtra = 0 ; wc.cbWndExtra = 0 ; wc.hInstance = hInstance ; wc.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wc.hCursor = LoadCursor (NULL, IDC_ARROW) ; wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wc.lpszMenuName = NULL ; wc.lpszClassName = szAppName ; if (!RegisterClass (&wc)) { MessageBox (NULL, TEXT ("This program requires Windows N T!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, / / window class nameTEXT ("欢迎你的到来!"), // w indow caption WS_OVERLAPPEDWINDO W, // window style CW_USEDEFAUL T, // initial x position CW_USEDEFAUL T, // initial y position CW_USEDEFAUL T,// initial x size CW_USEDEFAUL T, // initial y size NUL L, // parent window handle NUL L, // window menu handle hInstanc e, // program instance handle NUL L) ; // creation parameters ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARA M lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; DrawText (hdc, TEXT ("你好,欢迎你来到VC之路!"), -1, &rec t, DT_SINGLELINE | DT_CENTER | DT_VCENTE R) ;EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } 按前两节讲的方法编译后,就见到我们的第二只小板凳了,可别小看它,对它的全面理解对于学习MFC的封装是有很大好处的。windows的消息循环机制等都可通过这个东东解释出来。好了,咱们年后继续努力吧,应该应付一下该死的考试了...... SDK编程讲座之第三讲:Windows消息机制(1) Dos的过程驱动与Windows的事件驱动 在讲本程序的消息循环之前,我想先谈一下Dos与Windows驱动机制的区别: DOS程序主要使用顺序的,过程驱动的程序设计方法。顺序的,过程驱动的程序有一个明显的开始,明显的过程及一个明显的结束,因此程序能直接控制程序事件或过程的顺序。虽然在顺序的过程驱动的程序中也有很多处理异常的方法,但这样的异常处理也仍然是顺序的,过程驱动的结构。 而Windows的驱动方式是事件驱动,就是不由事件的顺序来控制,而是由事件的发生来控制,所有的事件是无序的,所为一个程序员,在你编写程序时,你并不知道用户先按哪个按纽,也不知道程序先触发哪个消息。你的任务就是对正在开发的应用程序要发出或要接收的消息进行排序和管理。事件驱动程序设计是密切围绕消息的产生与处理而展开的,一条消息是关于发生的事件的消息。 Windows编程的特点: C语言编程至少有一个主程序,其名字是main()。Windows程序则至少两个主程序,一个是WinMain(), int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // command line int nCmdShow // show state ); 另一个是窗口过程函数WndProc,它的函数原型为: long FAR PASCAL WndProc(HWND hWnd,WORD message,WORD wParam,LONG lParam); Windows应用程序的编程就围绕这两个部份进行的。其中WinMain函数为应用程序的入口点,它的名字一定要是WinMain。 在Windows中,应用程序通过要求Windows完成指定操作,而承担这项通信任务的API函数就是Windows的相应窗口函数WndProc。在dos里,程序能直接控制事件的发生顺序,结果等。而在Windows里,应用程序不直接调用任何窗口函数,而是等待Windows调用窗口函数,请求完成任务或返回信息。为保证Windows调用这个窗口函数,这个函数必须先向Windows登记,然后在Windows实施相应操作时回调,所以窗口函数又称为回调函数。WndProc是一个主回调函数,Windows至少有一个回调函数。 回调函数WndProc在哪里定义的呢,请看这个语句:wc.lpfnWndProc = WndProc ;将在第七讲里详谈. 实例:在Windows中,能多次同时运行同一个应用程序,即运行多个副本,每个副本叫做一个“实例”。现在让我们把这个程序层层剥解开来,我把自己的理解慢慢地展示给你: 我把这个程序支解为四块:(一)建立,注册窗口类.(二)创建窗口.(三)显示和更新窗口.(四)创 建消息循环.(五)终止应用程序.(六)窗口过程.(七)处理消息. (一)注册窗口类: (1)建立窗口类 WinMain()是程序的入口,它相当于一个中介人的角色,把应用程序(指小窗口)介绍给windows. 首要的一步是登记应用程序的窗口类. 窗口种类是定义窗口属性的模板,这些属性包括窗口式样,鼠标形状,菜单等等,窗口种类也指定处理该类中所有窗口消息的窗口函数.只有先建立窗口种类,才能根据窗口种类来创建Windows应用程序的一个或多个窗口.创建窗口时,还可以指定窗口独有的附加特性.窗口种类简称窗口类,窗口类不能重名.在建立窗口类后,必须向Windows登记. 建立窗口类就是用WNDCLASS结构定义一个结构变量,在这个程序中就是指WNDCLASS wc ;然后用自己设计的窗口属性的信息填充结构变量wc的域. 要WinMain登记窗口类,首先要填写一个WNDCLASS结构,其定义如下所示: typedef struct _WNDCLASSA {UINT style ; //窗口类风格 WNDPROC lpfnWndProc ; //指向窗口过程函数的指针 int cbClsExtra ; //窗口类附加数据 int cbWndExtra ; //窗口附加数据 HINSTANCE hInstance ; //拥有窗口类的实例句柄 HICON hIcon ; //最小窗口图标 HCURSOR hCursor ; //窗口内使用的光标 HBRUSH hbrBackground ; //用来着色窗口背景的刷子 LPCSTR lpszMenuName ; //指向菜单资源名的指针 LPCSTR lpszClassName ; // 指向窗口类名的指针 } 在VC6.0里面,把光标定位在WNDCLASS上,按F1,即可启动MSDN,在MSDN里你可看到这个结构原形.在下节讲解这些参数在本程序中的具体用法. (2)注册窗口类 (1)第一个参数:成员style控制窗口的某些重要特性,在WINDOWS.H中定义了一些前缀为CS的常量,在程序中可组合使用这些常量.也可把sytle设为0.本程序中为 wc.style = CS_HREDRAW | CS_VREDRAW,它表示当窗口的纵横坐标发生变化时要重画整个窗口。你看:无论你怎样拉动窗口的大小,那行字都会停留在窗口的正中部,而假如把这个参数设为0的话,当改动窗口的大小时,那行字则不一定处于中部了。 (2)第二个参数:lpfnWndProc包括一个指向该窗口类的消息处理函数的指针,此函数称为窗口过程函数。它将接收Windows发送给窗口的消息,并执行相应的任务。其原型为:long FAR PASCAL WndProc(HWND ,unsigned,WORD,LONG);并且必须在模快定义中回调它。WndProc是一个回调函数(见第五节),如果暂时无法理解这个模糊的概念意味着什么,可先放过,等到讲消息循环时再详谈。 (3)第三,四个参数:cbWndExtra域指定用本窗口类建立的所有窗口结构分配的额外字节数。当有两个以上的窗口属于同一窗口类时,如果想将不同的数据和每个窗口分别相对应。则使用该域很有用。这般来讲,你只要把它们设为0就行了,不必过多考虑。 (4)第五个参数:hInstance域标识应用程序的实例hInstance,当然,实例名是可以改变的。 wc.hInstance = hInstance ;这一成员可使Windows连接到正确的程序。 (5)第六个参数:成员hIcon被设置成应用程序所使用图标的句柄,图标是将应用程序最小化时出现在任务栏里的的图标,用以表示程序仍驻留在内存中。Windows提供了一些默认图标,我们也可定义自己的图标,VC里面专有一个制作图标的工具。 (6)第七个参数: hCursor域定义该窗口产生的光标形状。LoadCursor可返回固有光标句柄或者应用程序定义的光标句柄。IDC_ARROW表示箭头光标. (7)第八个参数:wc.hbrBackground域决定Windows用于着色窗口背景的刷子颜色,函数GetStockObject返回窗口的颜色,本程序中返回的是白色,你也可以把它改变为红色等其他颜色.试试看 (8)第九个参数:lpszMenuName用来指定菜单名,本程序中没有定义菜单,所以为NULL。 (9)第十个参数:lpszClassName指定了本窗口的类名。 当对WNDCLASS结构域一一赋值后,就可注册窗口类了,在创建窗口之前,是必须要注册窗口类的, 注册窗口类用的API函数是RegisterClass,注册失败的话,就会出现一个对话框如程序所示,函数 RegisterClass返回0值,也只能返回0值,因为注册不成功,程序已经不能再进行下去了。 在本程序中注册窗口类如下: if (!RegisterClass (&wc)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName,MB_ICONERROR) ; return 0 ;} SDK编程讲座之第三讲:Windows消息机制(2) (二)创建窗口 注册窗口类后,就可以创建窗口了,本程序中创建窗口的有关语句如下: hwnd = CreateWindow (szAppName, // win dow class name TEXT ("欢迎你的到 来!"), // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, // initial x position CW_USEDEFAULT, // initial y positionCW_USEDEFAULT, // initial x size CW_USEDEFAULT, // initial y size NULL, // parent window handle NULL, // window menu handle hInstance, // program instance handle NULL) ; // creation parameters 参数1:登记的窗口类名,这个类名刚才咱们在注册窗口时已经定义过了。 参数2:用来表明窗口的标题。 参数3:用来表明窗口的风格,如有无最大化,最小化按纽啊什么的。 参数4,5:用来表明程序运行后窗口在屏幕中的坐标值。 参数6,7:用来表明窗口初始化时(即程序初运行时)窗口的大小,即长度与宽度。 参数8: 在创建窗口时可以指定其父窗口,这里没有父窗口则参数值为0。 参数9:用以指明窗口的菜单,菜单以后会讲,这里暂时为0。 最后一个参数是附加数据,一般都是0。 CreateWindow()的返回值是已经创建的窗口的句柄,应用程序使用这个句柄来引用该窗口。如果返回值为0,就应该终止该程序,因为可能某个地方出错了。如果一个程序创建了多个窗口,则每个窗口都有各自不同的句柄. (三)显示和更新窗口 API函数CreateWindow创建完窗口后,要想把它显示出现,还必须调用另一个API 函数ShowWindows.形式为: ShowWindow (hwnd, iCmdShow);其第一个参数是窗口句柄,告诉ShowWindow()显示哪一个窗口,而第二个参数则告诉它如何显示这个窗口:最小化(SW_MINIMIZE),普通(SW_SHOWNORMAL),还是最大化(SW_SHOWMAXIMIZED)。WinMain在创建完窗口后就调用ShowWindow函数,并把iCmdShow参数传送给这个窗口。你可把iCmdShow改变为这些参数试试。 WinMain()调用完ShowWindow后,还需要调用函数UpdateWindow,最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新. (四)创建消息循环 主窗口显示出来了,WinMain就开始处理消息了,怎么做的呢? Windows为每个正在运行的应用程序都保持一个消息队列。当你按下鼠标或者键盘时,Windows 并不是把这个输入事件直接送给应用程序,而是将输入的事件先翻译成一个消息,然后把这个消息放入到这个应用程序的消息队列中去。应用程序又是怎么来接收这个消息呢?这就讲讲消息循环了。 应用程序的WinMain函数通过执行一段代码从她的队列中来检索Windows送往她的消息。然后WinMain就把这些消息分配给相应的窗口函数以便处理它们,这段代码是一段循环代码,故称为"消息循环"。这段循环代码是什么呢?好,往下看: 在咱们的第二只小板凳中,这段代码就是: ...... MSG msg; //定义消息名 while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ;//翻译消息 DispatchMessage (&msg) ;//撤去消息 } return msg.wParam ; MSG结构在头文件中定义如下: typedef struct tagMSG{ HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG, *PMSG; MSG数据成员意义如下: 参数1:hwnd是消息要发送到的那个窗口的句柄,这个窗口就是咱们用CreateWindows函数创建的那一个。如果是在一个有多个窗口的应用程序中,用这个参数就可决定让哪个窗口接收消息。 参数2:message是一个数字,它唯一标识了一种消息类型。每种消息类型都在Windows文件中定义了,这些常量都以WM_开始后面带一些描述了消息特性的名称。比如说当应用程序退出时,Windows就向应用程序发送一条WM_QUIT消息。 参数3:一个32位的消息参数,这个值的确切意义取决于消息本身。 参数4:同上。 参数5:消息放入消息队列中的时间,在这个域中写入的并不是日期,而是从Windows启动后所测量的时间值。Windows用这个域来使用消息保持正确的顺序。 参数6:消息放入消息队列时的鼠标坐标. 消息循环以GetMessage调用开始,它从消息队列中取出一个消息: GetMessage(&msg,NULL,0,0),第一个参数是要接收消息的MSG结构的地址,第二个参数表示窗口句柄,NULL则表示要获取该应用程序创建的所有窗口的消息;第三,四参数指定消息范围。后面三个参数被设置为默认值,这就是说你打算接收发送到属于这个应用程序的任何一个窗口的所有消息。在接收到除WM_QUIT之外的任何一个消息后,GetMessage()都返回TRUE。如果GetMessage 收到一个WM_QUIT消息,则返回FALSE,如收到其他消息,则返回TRUE。因此,在接收到WM_QUIT 之前,带有GetMessage()的消息循环可以一直循环下去。只有当收到的消息是WM_QUIT时,GetMessage才返回FALSE,结束消息循环,从而终止应用程序。均为NULL时就表示获取所有消息。 消息用GetMessage读入后(注意这个消息可不是WM_QUIT消息),它首先要经过函数TranslateMessage()进行翻译,这个函数会转换成一些键盘消息,它检索匹配的WM_KEYDOWN和WM_KEYUP消息,并为窗口产生相应的ASCII字符消息(WM_CHAR),它包含指定键的ANSI字符.但对大多数消息来说它并不起什么作用,所以现在没有必要考虑它。 下一个函数调用DispatchMessage()要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程。我们在讲到登记窗口类时曾提到过,登记窗口类时,我们曾指定Windows把函数WindosProc作为咱们这个窗口的窗口过程(就是指处理这个消息的东东)。就是说,Windows会调用函数WindowsProc()来处理这个消息。在WindowProc()处理完消息后,代码又循环到开始去接收另一个消息,这样就完成了一个消息循环。 SDK编程讲座之第三讲:Windows消息机制(3) (五)终止应用程序: Windows是一种非剥夺式多任务操作系统。只有的应用程序交出CPU控制权后,Windows才能把控制权交给其他应用程序。当GetMessage函数找不到等待应用程序处理的消息时,自动交出控制权,Windows把CPU的控制权交给其他等待控制权的应用程序。由于每个应用程序都有一个消息循环,这种隐式交出控制权的方式保证合并各个应用程序共享控制权。一旦发往该应用程序的消息到达应用程序队列,即开始执行GetMessage语句的下一条语句。 当WinMain函数把控制返回到Windows时,应用程序就终止了。应用程序的启动消息循环前要检查引导出消息循环的每一步,以确保每个窗口已注册,每个窗口都已创建。如存在一个错误,应用程序应返回控制权,并显示一条消息。 但是,一旦WinMain函数进入消息循环,终止应用程序的唯一办法就是使用PostQuitMessage 把消息WM_QUIT发送到应用程序队列。当GetMessage函数检索到WM_QUIT消息,它就返回NULL,并退出消息外循环。通常,当主窗口正在删除时(即窗口已接收到一条WM_DESTROY消息),应用程序主窗口的窗口函数就发送一条WM_QUIT消息。 虽然WinMain指定了返回值的数据类型,但Windows并不使用返回值。不过,在调试一应用程序时,返回值地有用的。通常,可使用与标准C程序相同的返回值约定:0表示成功,非0表示出错。PostQuitMessage函数允许窗口函数指定返回值,这个值复制到WM_QUIT消息的wParam参数中。为了的结束消息循环之后返回这个值,我们的第二只小板凳中使用了以下语句: return msg.wParam ;//表示从PostQuitMessage返回的值 例如:当Windows自身终止时,它会撤消每个窗口,但不把控制返回给应用程序的消息循环,这意味着消息循环将永远不会检索到WM_QUIT消息,并且的循环之后的语句也不能再执行。Windows 的终止前的确发送一消息给每个应用程序,因而标准C程序通常会的结束前清理现场并释放资源,但Windows应用程序必须随每个窗口的撤消而被清除,否则会丢失一些数据。 (六)窗口过程,窗口过程函数如前所述,函数GetMessage负责从应用程序的消息队列中取出消息,而函数DispatchMessage()要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程。然后出台的就是这个窗口过程了,这个窗口过程的任务是干什么呢?就是最终用来处理消息的,就是消息的处理器而已,那么这个函数就是WindowProc,在Visual C++6.0中按F1启动MSDN,按下面这个路径走下来: PlatForm SDK-->User Interface services-->Windows user Interface-->Windowing-->Window Procedures-->Window Procedure Reference-->Windows Procedure Functions -->WindowProc 啊,太累了,不过我们终于的MSDN中找到了这个函数,前几次我讲解这些API函数的时候,都是的知道的情况下搜索出来的,所以没有详细给出每个函数的具体位置,而这次我却是一点点去找的,还好,没被累死,体会到MSDN的庞大了吧,不过我用的是MSDN2000,是D版的,三张光盘装。你用的MSDN如果按这个路径走下去的话,可能会找不到,不过我想大致也是在这个位置了,找找看!!! LRESULT CALLBACK WindowProc ( HWND hwnd, // handle to window UINT uMsg,// message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ); 这个函数我们的第二只小板凳里被我们称为WndProc. 下面讲解: 不知你注意到了没有,这个函数的参数与刚刚提到的GetMessage调用把返回的MSG结构的前四个成员相同。如果消息处理成功,WindowProc的返回值为0. Windows的启动应用程序时,先调用WinMain函数,然后调用窗口过程,注意:在我们的这个程序中,只有一个窗口过程,实际上,也许有不止一个的窗口过程。例如,每一个不同的窗口类都有一个与之相对应的窗口过程。无论Windows何时想传递一个消息到一窗口,都将调用相应的窗口 过程。当Windows从环境,或从另一个应用程序,或从用户的应用程序中得到消息时,它将调用窗口过程并将信息传给此函数。总之,窗口过程函数处理所有传送到由此窗口类创建的窗口所得到的消息。并且窗口过程有义务处理Windows扔给它的任何消息。我们在学习Windows程序设计的时候,最主要的就是学习这些消息是什么以及是什么意思,它们是怎么工作的。 令我们不解的是,在程序中我们看不出来是哪一个函数在调用窗口过程。它其实是一个回调函数.前面已经提到,Windows把发生的输入事件转换成输入消息放到消息队列中,而消息循环将它们发送到相应的窗口过程函数,真正的处理是在窗口过程函数中执行的,在Windows中就使用了回调函数来进行这种通信。 回调函数是输出函数中特殊的一种,它是指那些在Windows环境下直接调用的函数。一个应用程序至少有一个回调函数,因为在应用程序处理消息时,Windows调用回调函数。这种回调函数就是我们前面提到的窗口过程,它对对应于一个活动的窗口,回调函数必须向Windows注册,Windows实施相应操作即行回调。 每个窗口必须有一个窗口过程与之对应,且Windows直接调用本函数,因此,窗口函数必须采用FAR PASCAL调用约定。在我们的第二只小板凳中,我们的窗口函数为WndProc,必须注意这里的函数名必须是前面注册的窗口类时,向域wc.lpfnWndProc所赋的WndProc。函数WndProc就是前面定义的窗口类所生成的所有窗口的窗口函数。 在我们的这个窗口函数中,WndProc处理了共有两条消息:WM_PAINT和WM_DESTROY. 窗口函数从Windows中接收消息,这些消息或者是由WinMain函数发送的输入消息,或者是直接来自Windows的窗口管理消息。窗口过程检查一条消息,然后根据这些消息执行特定的动作未被处理的消息通过DefWindowProc函数传回给Windows作缺海上处理。 可以发送窗口函数的消息约有220种,所有窗口消息都以WM_开头,这些消息在头文件中被定义为常量。引起Windows调用窗口函数的原因有很多,如改变窗口大小啊,改变窗口在屏幕上的位置啊什么的。 Windows已经把任务扔给窗口过程了,窗口过程是怎么处理消息的呢?稍息一下,让我们进行下一节:处理消息...... SDK编程讲座之第三讲:Windows消息机制(4) (七)处理消息 窗口过程处理消息通常以switch语句开始,对于它要处理的每一条消息ID都跟有一条case语句。大多数windows proc 都有具有下面形式的内部结构: switch(uMsgId) {case WM_(something): //这里此消息的处理过程 return 0; case WM_(something else): //这里是此消息的处理过程 ruturn 0; default: //其他消息由这个默认处理函数来处理 return DefWindowProc(hwnd,uMsgId,wParam,lParam); } 在处理完消息后,要返回0,这很重要-----它会告诉Windows不必再重试了。对于那些在程序中不准备处理的消息,窗口过程会把它们都扔给DefWindowProc进行缺省处理,而且还要返回那个函数的返回值。在消息传递层次中,可以认为DefWindowProc函数是最顶层的函数。这个函数发出WM_SYSCOMMAND消息,由系统执行Windows环境中多数窗口所公用的各种通用操作,例如,画窗口的非用户区,更新窗口的正文标题等等等等。 再提示一下,以WM_的消息在Windows头文件中都被定义成了常量,如WM_QUIT=XXXXXXXXXXX,但我们没有必要记住这个数值,也不可能记得住,我们只要知道WM_QUIT就OK了。 在第二只小板凳中我们只让窗口过程处理了两个消息:一个是WM_PAINT,另一个是WM_DESTROY,先说说第一个消息---WM_PAINT. 关于WM_PAINT: 无论何时Windows要求重画当前窗口时,都会发该消息。也可以这样说:无论何时窗口非法,都必须进行重画。哎呀,什么又是"非法窗口"?什么又是重画啊?你这人有没有完,嗯? 稍安勿燥,我比你还烦呢?我午饭到现在还没吃呢!你有点耐心,来点专业精神好不好???我开始在MSDN里面找有关这个方面的内容了,别急,我找找看: Platform SDK-->Graphics and Multimedia Services-->Windows GDI-->Painting and Drawing-->Using the WM_PAINT Message-----终于找到了。 下面是一大套理论:让我们把Windows的屏幕想像成一个桌面,把一个窗口想像成一张纸。当我们把一张纸放到桌面上时,它会盖住其他的纸,这样被盖住的其他纸上的内容都看不到了。但我们只要把这张纸移开,被盖住的其他纸上的内容就会显示出来了---这是一个很简单的道理,谁都明白。 对于我们的屏幕来说,当一个窗口被另一窗口盖住时,被盖住的窗口的某些部分就看不到了,我们要想看到被盖住的窗口的全部面貌,就要把另一个窗口移开,但是当我们移开后,事情却起了变化-----很可能这个被盖住的窗口上的信息被擦除了或是丢失了。当窗口中的数据丢失或过期时,窗口就变成非法的了---或者称为"无效"。于是我们的任务就来了,我们必须考虑怎样在窗口的信息丢失时"重画窗口"--使窗口恢复成以前的那个样子。这也就是我们在这第二只小板凳中调用UpdateWindow的原因。 你忘记了吗?刚才我们在(三)显示和更新窗口中有下面的一些文字: WinMain()调用完ShowWindow后,还需要调用函数UpdateWindow,最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新.---这是程序第一次调用了这条消息。 为重新显示非法区域,Windows就发送WM_PAINT消息实现。要求Windows发送WM_PAINT的情况有改变窗口大小,对话框关闭,使用了UpdateWindows和ScrollWindow函数等。这里注意,Windows并非是消息WM_PAINT的唯一来源,使用InvalidateRect或InvalidateRgn函数也可以产生绘图窗口的WM_PAINT消息...... 通常情况下用BeginPaint()来响应WM_PAINT消息。如果要在没有WM_PAINT的情况下重画窗口,必须使用GetDC函数得到显示缓冲区的句柄。这里面不再扩展。详细见MDSN。 这个BeginPaint函数会执行准备绘画所需的所有步骤,包括返回你用于输入的句柄。结束则是以EndPaint(); 在调用完BeginPaint之后,WndProc接着调用GetClientRect: GetClientRect(hwnd,&rect); 第一个参数是程序窗口的句柄。第二个参数是一个指针,指向一个RECT类型的结构。查MSDN,可看到这个结构有四个成员。 WndProc做了一件事,他把这个RECT结构的指针传送给了DrawText的第四个参数。函数DrawText的目的就是在窗口上显示一行字----"你好,欢迎你来到VC之路! 关于WM_DESTROY 这个消息要比WM_PAINT消息容易处理得多:只要用户关闭窗口,就会发送WM_DESTROY消息(在窗口从屏幕上移去后)。 程序通过调用PostQuitMessage以标准方式响应WM_DESTROY消息: PostQuitMessage (0) ;这个函数在程序的消息队列中插入一个WM_QUIT消息。在(四)创建消息循环中我们曾有这么一段话: 消息循环以GetMessage调用开始,它从消息队列中取出一个消息: ....... 在接收到除WM_QUIT之外的任何一个消息后,GetMessage()都返回TRUE。如果GetMessage收到一个WM_QUIT消息,则返回FALSE,如收到其他消息,则返回TRUE。因此,在接收到WM_QUIT 之前,带有GetMessage()的消息循环可以一直循环下去。只有当收到的消息是WM_QUIT时,GetMessage才返回FALSE,结束消息循环,从而终止应用程序。 SDK编程讲座之第四讲: Windows基础结构(1) 一、设备描述表 设备描述表是Windows应用程序、设备驱动程序和输出设备之间的桥梁,它与一个特定的设备相关联。例如,对显示器来说,设备描述表通常指显示器上的某个窗口。设备描述表描述了所选定的绘图工具、字体、字体颜色、工具在设备上绘制(或者说是映射)的方式及设备上可使用的输出区域等属性。WindowsGDI函数实际上是在设备描述表里显示正文及绘图的。当程序显示文字或绘图时,首先必须获得一个设备描述表句柄,完成输出文字或绘图之后,还必须及时释放该句柄,否则会大大减少Windows的存储单元。释放之后的句柄就不会再有效了。一般说来,在处理某条消息时,获取和释放设备描述表句柄必须成对出现,而且不同设备描述表的获取与释放的方法也不同。而这只是所使用的函数不同而已。 二、显示缓冲区 Windows环境是基于图形操作的,图形设备接口(GDI)是一个在Windows应用程序中执行与设备无关的函数库。这些函数在不同的输出设备上产生图形及文字输出。 显示缓冲区是一种”设备缓冲区”,特别用于窗口用户区的输出。设备缓冲区定义设备,绘图工具及有关设备的完整信息。显示缓冲区只定义与窗口用户区有关的内容,包括输出设备、当前绘图工具、颜色,以及其它一些GDI输出函数产生输出所需的信息。在窗口中绘图,需要使用窗口的句柄,根据窗口句柄,可以得到窗口用户区的显示缓冲区句柄,所有GDI输出函数都需要一个显示缓冲区句柄,没有它就无法完成输出。 可以根据输出的需要选择获得显示缓冲区句柄的方法。画和写操作可以存在于应用程序中的任何地方(包括WinMain函数中),倡大多数应用程序把它们放在窗口函数中。每当对窗口的操作可能影响用户区内容时,Windows发送WM_PAINT消息给窗口函数。应用程序通常在响应WM_PAINT消息时,完成画和写。Windows发送WM_PAINT消息给窗口函数,并由它刷新用户区,因为只有应用程序才知道用户区的内容。 通常用BeginPaint函数来响应WM_PAINT消息。如果要在没有WM_PAINT消息的时刻画用户区,必须使用GetDC函数得到显示缓冲区的句柄。 当应用程序需要得到窗口的显示缓冲区时,Windows把它暂时借给应用程序。显示缓冲区是一种共享资源,一个应用程序占有它之后,其它应用程序就无法得到它。因此,应用程序在利用显示缓冲区画完窗口内容之后,就必须使用ReleaseDC函数释放它。同理,要求用EndPaint 函数释放由BeginPaint函数获得的显示缓冲区。 显示缓冲区中有缺省的画笔、画刷和缺省字模。 (一)GetDC函数 在处理非WM_PAINT消息时,应用程序获取窗口用户区设备描述表句柄使用GetDC函数,它常常用来对用户的某些动作提供反馈。例如,当用户移动鼠标光标穿越窗口时,在屏幕上画一条线。GetDC函数返回一个显示缓冲区句柄,它可以用于任何GDI输出函数。 请看下面这个程序段:case WM_PAINT: hDC=GetDC(hWnd); TextOut(hDC,10,10,”你好,欢迎来到VC之路”,20); ReleaseDC(hWnd,hDC); Break; 在这里,我们用设备环境句柄hDC定义了一个设备描述表句柄hDC,然后利用函数GetDC取 得hWnd参数所标识窗口的显示缓冲区的显示描述表赋给hDC,再通过TextOut函数在窗口用户区(10,10)位置显示包含20个字符(计入空隔)的字符串,使用完之后,及时用函数RealeaseDC 释放这个显示描述句柄hDC。 使用GetDC函数获得显示缓冲区句柄,在窗口函数中处理WM_PAINT消息。当窗口函数接收 到影响用户区内容的WM_PAINT消息时,用户区中先前已画的内容可能被擦掉。这是因为在处理WM_PAINT消息的过程中,Windows发送WM_ERASEBKGND消息给窗口函数,如果把WM_ERASEBKGND 消息交给DefWindowProc函数处理,DefWindowProc函数使用这种窗口类的背景色填满受影响的区域,并擦掉原先已经画的内容。 (二)WM_PAINT消息 在前面我们已经认识了WM_PAINT,让我们再来看一下。由于Windows是一个多任务环境,某个应用程序的窗口上面可能被对话框或窗口覆盖,当撤消这些对话框或窗口时,这个应用程序窗口中就有一个”空洞”,这个”空洞”就是一块无效的用户区域。为重新显示无效用户区域,Windows发送WM_PAINT消息实现。要求Windows发送WM_PAINT的情况有:改变窗口大小,覆盖用户区的菜单或对话框关闭,使用UpdateWindow和ScrollWindow函数等。 Windows发送WM_PAINT消息时,把它放到应用程序队列的最后,使得其它的输入能够先于WM_PAINT消息被处理。GetMessage函数也得到队列中WM_PAINT消息之后的其它消息,即只有有没有其它消息的情况下,才从队列中取出WM_PAINT消息进行处理。这样做是 为了让应用程序首先完成影响窗口显示结果的其它操作,不致因为频繁地执行输出操作而引起显示器的闪烁。Windows把WM_PAINT消息放在队列最后就是这个原因。 Windows并非WM_PAINT消息的唯一来源,使用InvalidateRect或InvalidateRgn函数也可以产生绘图窗口的WM_PAINT消息。这两个函数把用户区全部或部分标记成无效用户区而要求重新显示。下面的函数调用是把整个用户区标记成无效: InvalidateRect(hWnd,NULL,TRUE); 这个例子把hWnd句柄参数指定的窗口用户区标记成无效。作为钜形结构的NULL参数指定整个用户区。TRUE参数表示擦除背景。 InvalidateRect和InvalidateRgn函数并不实际产生WM_PAINT消息。当用户区无效时,Windows就发送一个WM_PAINT消息,如果用户区的其它部分也被标记成无效,Windows 就不再发送另一条WM_PAINT消息,而是把已有的无效区域合并,以便根据同一条WM_PAINT 消息处理所有这些区域。 如果想改变重新显示的用户区,可以调用ValidateRect和ValidateRgn函数使相应的用户区有效,这两个函数取消原有的无效区,并在没有无效区的情况下取消队列中的WM_PAINT消息。 如果不想等待应用程序队列中的WM_PAINT消息,使用UpdateWindow函数直接发送WM_PAINT消息给指定窗口的窗口函数。如果还存在无效的用户区,UpdateWindow函数从队列中取出WM_PAINT消息,并把它直接发送给指定窗口的窗口函数。UpdateWindow函数通常用在窗口需要立即更新它的用户区,如在窗口刚被创建时使用。 用BeginPaint函数获得输出文字和画点的显示缓冲区句柄: PAINTSTRUCT ps; … case WM_PAINT: hdc=BeginPaint(hWnd,&ps); /* 输出操作 */ EndPaint(hWnd,&ps); Break; 结构PAINTSTRUCT函数原型如下: typedef struct tagPAINTSTRUCT{ HANDLE hDC; //设备描述表句柄 BOOL rErase; //确定背景是否已被重画 RECT rePaint; //给出无效钜形的边界 BOOL fRestore; /内部使用的保留字段 BOOL fIncUpdate; //保留字段 BYTE rgbReserved; //保留字段 }PAINTSTRUCT; 无效区域是一个钜形区域,它是一个RECT结构,其在window.h定义为typedef struct tagRECT{ int left; //钜形左上角的X坐标 int top; //钜形左上角的Y坐标 int right; //钜形右下角的X坐标 int bottow; //钜形右下角的Y坐标 }RECT 例中BeginPaint函数返回一个显示缓冲区句柄,它可用于其它的GDI输出函数中。 EndPaint函数通知Windows所有输出操作均已处理完毕,并释放显示缓冲区。 SDK编程讲座之第四讲: Windows基础结构(2) (三)坐标系统 为了准备显示缓冲区,Windows调整设备的原点,使它位于用户区而不是显示器的左上角,并且还设置了一个剪辑区,使显示缓冲区的输出被”剪辑”到用户区。也就是说,用户区范围之外的输出并不送到显示器上。 显示缓冲区默认的坐标系统很简单,用户区的左上角是原点,即点(0,0)。向右的每个像素表示X轴方向的一个单位,向下的每个像素表示Y轴方向的一个单位。 通过修改映射模式和显示原点,可以改变坐标系统。映射模式定义坐标系统单位。Windows 应用程序可通过调用SetMapMode函数来设定映射模式,其中参数定义了所采取的坐标系统单位,即将一个逻辑单位映射为设备单位的比例。设备单位可为任意个像素,英寸或毫米,还定义了设备的X轴和Y轴方向。默认的映射模式为MM_TEXT,即默认的坐标系统。当前设备描述表的映射方式可通过调用GetMapMode函数得到。通过调用SetViewPortOrg函数,可以把坐标系统原点移到任何位置。 1.SetMapMode函数 语法: int SetMapMode(HDC hdc,int nMapMode); 说明: 参数hDC是设备描述表,参数nMapMode是映射模式。映射共有8种映射模式,取值及含义如下: 值含义 MM_ANISOTROPIC 逻辑单位被映射成任意的物理单位,X轴和Y轴成任意比例 MM_HIENGLISH 一个逻辑单位被映射成0.001英寸,正X向右,正Y向上 MM_HIMETRIC 一个逻辑单位被映射成0.01英寸,正X向上,正Y向上 MM_ISOTROPIC 逻辑单位被映射成任意的物理单位,X轴和Y轴的变换变换比例相同 MM_LOENGLISH 一个逻辑单位被映射成0.01英寸,正X向右,正Y向上 MM_LOMETRIC 一个逻辑单位被映射成0.1毫米,正X向右,正Y向上 MM_TEXT 一个逻辑单位被映射成一个设备像素,正X向右,正Y向下 MM_TWIPS 一个逻辑单位被映射成打印机点的二十分之一,正X向右,正Y向上 本例用MM_ANISOTROPIC模式。这种模式把逻辑单位映射成任意的物理单位,X轴和Y轴成任意比例。 2.GetMapMode函数 语法:DWORD SetViewPortOrg(HDC hDC) 说明: 该函数取得当前设备描述表的映射模式。 3.SetViewPortOrg函数 语法:DWORD SetViewPortOrg(HDC hDC,int x,int y)说明: 该函数为hDC设置视口原点。返回低字节是原点X坐标,高字节为原点Y坐标。三、画图函数 GDI提供各种各样的输出操作,从画线到写字应有尽有。为了画线、钜形、圆、扇形和写字,可相应地调用一些函数。这些函数使用已选择的笔和刷画边框,并填写图形内部区,以及使用已选择的字体写字。 1.画点函数SetPixel 画点函数SetPixel的原型为: COLORREF SetPixel(HDC hDC,int x,int y,COLORREF cclrref); 该函数把X和Y指定的点置为clrref指定的颜色。 2.画线函数LineTo与移动函数MoveTo LineTo函数用来画线,并且通常与MoveTo函数配合使用,如画一条从点(10,70)到点(250,100)的线: MoveTo(hDC,10,70); LineTo(hDC,250,100); 3.画钜形函数Rectangle Rectangle函数用来画钜形。它使用已选择的笔画边框,使用已选择的刷子填满钜形内部。下面的例子画一个左上角位于点(10,20),右下角位于点(40,100)的钜形: Rectangle(hDC,10,20,40,100); 4.画圆或椭圆函数Ellipse Ellipse函数用来画圆或椭圆。它使用已选择的笔画框,使用已选择的刷填满圆或椭圆的内部。下面的例子画一个用点(10,20)和点(40,100)构成钜形框中的椭圆:Ellipse(hDC,10,20,40,100); 5.画圆弧函数Arc Arc函数用来画一段弧,这段弧由包围它的钜形和弧的开始点和结束点共同定义。下面的例子在点(10,90)和点(360,120)所指定的钜形中画一段弧,它的起点和终点分别是点(15,90)和点(360,90): Arc(hDC,10,90,360,120,15,90,360,110); 弧的起点坐标和终点坐标精确地位于弧上。 6.画扇形函数Pie Pie函数用来画扇形。扇形由一段弧和两条从弧焦点到弧端点的半径组成。Pie函数使用已选择的笔画框,使已选择的刷填满扇形内部。下面的例子画一个用点(310,30)和点(360,80)构成的钜形围成的扇形。其起点和终点分别为点(360,30)和点(360,80): Pie(hDC,310,30,360,80,310,30,360,80); 弧的起点和终点不必精确地位于弧线上。下载本文