声 明
再写下后面的东西以前,我觉得有必要专门花一页的篇幅进行下面的申明.在我的眼里它的必要性不亚于在吃中餐前,先阅读中国筷子的使用说明书.在以后的文字中,我都准备尽可能以一种轻松,略带调侃的语气来完成,但是下面的文字我却会写得很正式,因为谁也不想笑着看完筷子使用说明书后,在吃饭时被筷子卡住喉咙,那样的话就一点都不好笑了.
其一,如果你希望看到一篇教条的文字,那么它并不适合你.因为这篇文字以及后来你们将要看到的程序从一开始就并不是你们想象中的那种循规蹈矩的风格.
其二,如果你仅仅是一个游戏爱好者,那么这篇文章的主人一定会成为你的好朋友;如果你是一个游戏开发者,那么这篇文章的主人很愿意和你结交成一个良师益友,并且十分愿意和你共同探讨游戏编程的众多的技术问题,一起学习,一起进步;如果你是一个希望进入游戏开发的朋友,首先,我要提醒你,你要考虑清楚了,不要被现在游戏业红火的表面现象所迷惑,如果在慎重考虑以后,你仍然义无反顾(你死定了!),那么这篇文字非常适合你进入游戏开发领域,它将会是从基础的游戏的构架一直讲到游戏的开发(再次强调,进入这行后,后悔了可不要怪我哦J).如果你不是一个游戏爱好者,甚至没有进入这行的打算,那么我要说得是真遗憾,早点进来吧,游戏的大门永远为你敞开.
其三,这篇文字以及其后讲到的所有源程序绝对是原创, 因为这篇文字的主人崇尚"将原创进行到底"的主义.如果你对文字有什么不满意的地方,或者你对里面讲的东东有什么不理解的地方;或者你认为这本身就是一篇很垃圾的东西.都欢迎你来信BT我.
其四, 闷炮工作室这样做是为了鼓励更多的人加入游戏制作的行列,只希望以本文为"中华游戏之崛起"而贡献一点微薄之力!
其五,对于我提供的代码和文字,你可以使用和学习,但本人不承担责任。不能用于商业用途,如有需要,请与本人联系。在保留作者的版权申明的条件下,你可以自由传播这些代码。
其六, 一点点个人的民族气节哈^_^,同时,你还需要遵守以下条款:
1: 不能喜欢S H E.
2: 支持一个中国,BS台独分子.
3: 尽量抵制日货物.需有明显的反日倾向.
一旦你使用了我的代码和资料.表示你同意以上条款.
其他,……还没有想好.
感觉象在写《南京条约》,那个又愿意写这样的文字呢,不过没有规矩不成方圆,所以赖着头皮写下上面的东东,希望大家理解.
石磬 著
By Shi Qing
Email: shiqingstudio@yahoo.com.cn
QQ: 16398943
http://blog.csdn.net/ronaldo17/
第一章 离开游戏说编程
我为什么会用这个名字呢?那是因为我要告诉大家,首先游戏编程不是这个世界上编程的唯一东西,而且还要忠告所有玩家,游戏也不是这个世界上的唯一,如果有时间多做些户外运动,游戏切记适可而止;另外,更重要的是要告诉大家,就目前微软统制的这个时代,几乎所有的编程都属于win32编程,自从上个世纪,微软成就了盖茨以来,同时面向对象编程和基于窗体的win32编程也同时成就了今天的软件行业.所以基础的win32编程就成为了不可不说的话题.
1.1编程环境
这里要说的编程环境不是我们的生活环境,不是象论坛里面每天讨论的程序员的工资,也不是政府给软件业的大环境,更不是世界软件的何去何从,这里要说的是我们所用的开发工具和软件接口.这里所有讲到的编译器和图形接口都是用得微软的东东.也就是我将向大家讲述的所有游戏编程内容都是基于visual studio.net2003和direct7.
1.1.1 编译器
我们以后用的开发工具全是基于visual studio.net2003,所以我先来说说这个编译器的一些基本使用方法(不要BS我^_^).首先启动vc.net(然后等,无休止的等,微软的东西慢的很^_^), 会看见一个"起始页",点击"新建项目"一栏,并在出现的窗口选择"Visual C++ 项目"一栏中的"Win32 控制台项目",填好"名称"和"位置",按"确定",这时会出现一个"Win32 应用程序向导"。此时需要在"附加选项"一栏把"空项目"前的方框勾上,以防VC.net在工程中加入一些无意义的垃圾. 最后按"Finish"就完成了工程的创建。
然后大家就建立完一个工程了,这个时候编译环境实际上已经达建好了,如果你是一个老鸟,你还可以修改一些你自己习惯的设置,让它用起来更顺手. 大家会注意到现在工程还没有文件,所以接下去我们需要学习如何新建一个文件,如果你想新建的文件是C++程序文件(.cpp),那么应该在"源文件"上按右键,选择"添加" --- "添加新项",在出现的窗口中选择"C++ 文件",定好名字,再按"开始"即可(假如你加入的文件叫"star.cpp",将象下图所示);如果你想新建的文件是头文件(现在先不要管头文件是什么),在"头文件"按右键,也选择"添加" --- "添加新项",在出现的窗口中选择"头文件",也定好名字并按"开始"就行了。
最后我们来看看一个重要的操作,即如何把LIB文件(现在也不要管LIB文件是什么…)加入工程中:首先在"解决方案"窗口中找到工程名,然后在上面按右键并选择"属性",在出现的窗口中选择"连接器" --- "输入" --- "添加依附项",最后填上要加入的LIB文件名即可。在这里,由于我们主要是研究游戏的编程,所以为了告诉编译器我们需要使用DirectDraw,我们要在程序文件中加入#include <ddraw.h>,并把"ddraw.lib"和"dxguid.lib"加入工程,记住,做完了这些工作后DirectDraw程序才能被正常编译。
1.1.2 图形接口
什么是图形接口? DirectX就是一个图形接口,DirectX是Microsoft开发的基于Windows平台的一组API,它是为高速的实时动画渲染、交互式音乐和环境音效等高要求应用开发服务的;Direct X是微软公司专为PC游戏开发的API(应用程序接口),特点是:比较容易控制,可令显卡发挥不同的功能,与Windows 95和Windows NT兼容性较好。在Direct X 5.0中共分六个部分:DirectDraw—管理游戏的视频输出、Direct 3D—管理游戏的3D图形、DirectPlay—管理游戏的网络通讯、DirectSound—管理游戏的声音输出、DirectInput—管理游戏摇杆控制、Direct Setup—管理游戏的安装。以上这些都是微软给我们的解释,简单的说,它终究都是一些微软给我们封装好了的API函数,我们通过对这些函数的调用和操作可以达到直接对硬件的操作,包括加速,显卡的显示,游戏手柄的使用,声卡的使用………,既然可以对显卡和声卡以及手柄进行操作,自然而然我们就可以编写游戏.所以我们游戏的开发都是基于direct编程.
1.2 win32编程
现在的所以编程都是面相对象编程,在大学里面头发花白的老头就不断的给我们灌输这个思想,什么是对象呢,所有的窗体,组件,包括封装的类,他们都是对象,然而现在我们家庭软件绝大多数都是windows的窗体程序,所以可以说我们现在的编程都是基于窗体的win32编程,就是windows下的32位编程(听说下一代64位机有望在今年面试,不过走入家庭不知道又是什么时候了),自然容纳我们的游戏的容器就是windows窗体,所以这一章的重点就是下面的窗体的建立,只有建立了这个容器,我们才能在上面编制其他的程序,无论是游戏或者其他的什么东西.
1.2.1 消息机制
win32编程的核心思想就是消息机制,每一个程序员都必须了解它,它也是最低层的东西了,不知道看本文的朋友有没有看过候捷大师的《深入浅出MFC》和李维老师的《VCL构架剖析》(没有听说过?你不会是火星来的吧^_^),如果没有,强烈建议你去买来看看,两本书讲的不是同一个编译器,(一个是micosoft的,一个是borland),不是同一种语言(一个是c,一个是pascl),前一本出来的时间比较早,现在俨然成为华人地区程序员的圣经,后一本书是昨年才出来的,但是两本书讲的东西都是一样的,想学好编程一定要看看这两本书,他们旁博引证,引经论典,丝丝入扣,给我的启发颇深,看来也是显而易懂(怎么觉得想在给两个老师作宣传J),看完以后你就知道我们平时看着简单的程序到底是怎么运行的,他们在后台是怎么样实现的.由于我的表达能力有限,所以在这里只能尽量把我的理解大概讲讲,讲的不好,不要拿砖头砸我啊.
我想先谈一下Dos与Windows驱动机制的区别:
DOS程序主要使用顺序的,过程驱动的程序设计方法。顺序的,过程驱动的程序有一个明显的开始,明显的过程及一个明显的结束,因此程序能直接控制程序事件或过程的顺序。虽然在顺序的过程驱动的程序中也有很多处理异常的方法,但这样的异常处理也仍然是顺序的,过程驱动的结构。
而Windows的驱动方式是事件驱动,就是不由事件的顺序来控制,而是由事件的发生来控制,所有的事件是无序的,所为一个程序员,在你编写程序时,你并不知道用户先按哪个按纽,也不知道程序先触发哪个消息。你的任务就是对正在开发的应用程序要发出或要接收的消息进行排序和管理。事件驱动程序设计是密切围绕消息的产生与处理而展开的,一条消息是关于发生的事件的消息。
1.2.2 窗体的注册和初始化
了解了消息机制以后,我们可以创建窗体了.创建窗体前我们看看windows程序的流程,大致流程图如下:
了解了消息机制以后,我们可以创建窗体了.创建窗体前我们看看windows程序的流程,大致流程图如下:在这之后,我们可以创建Windows窗体了
//*********************************************************
//函数:WinMain( )
//功能:Windows程序入口函数。创建主窗口,处理消息循环
//*********************************************************
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg; //消息结构
InitWindow(hInstance,nCmdShow); //初始化窗体
while(1) //消息循环
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message==WM_QUIT) //退出消息循环
break;
TranslateMessage(&msg); //得到消息,处理回调函数
DispatchMessage(&msg);
}
}
return msg.wParam;
}WinMain( )函数的原型如下:
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
第一个参数hInstance是标识该应用程序的句柄。不过句柄又是什么呢?其实就是一个指向该程序所占据的内存区域的指针,它唯一地代表了该应用程序,Windows使用它管理内存中的各种对象。
第二个参数是hPrevInstance,应用程序的前一个实例句柄,别管它,对于Win32位而言,它一般是NULL.
第三个参数是lpCmdLine,是指向应用程序命令行参数字符串的指针。比如说我们运行"test hello",则此参数指向的字符串为"hello"。
最后一个参数是nCmdShow,是一个用来指定窗口显示方式的整数。它告诉应用程序如何初始化窗口,如最大化,最小化等状态。
创建以后我们还不能直接调用它,我还必须得和其他应用软件一样,对它进行注册(为什么微软的东西都要注册啊,真是麻烦),而且还要自己设置窗体的其他属性.
//*********************************************************
//函数:InitWindow( )
//功能:创建窗口
//*********************************************************
static BOOL InitWindow( HINSTANCE hInstance, int nCmdShow )
{
WNDCLASS wc;
wc.style = NULL; //窗口类风格
wc.lpfnWndProc = (WNDPROC)WinProc; //指向窗口过程函数的指针
wc.cbClsExtra = 0; //窗口类附加数据
wc.cbWndExtra = 0; //窗口类附加数据
wc.hInstance = hInstance; //拥有窗口类的实例句柄
wc.hIcon = NULL; //最小窗口图标
wc.hCursor = NULL; //窗口内使用的光标
wc.hbrBackground = NULL; //用来着色窗口背景的刷子
wc.lpszMenuName = NULL; //指向菜单资源名的指针
wc.lpszClassName = "menpao_RPG_DEMO";// 指向窗口类名的指针
RegisterClass(&wc); //注册窗口
hwnd=CreateWindow("menpao_RPG_DEMO","menpao_RPG_DEMO",WS_POPUP|WS_MAXIMIZE,0,0,GetSystemMetrics( SM_CXSCREEN ),GetSystemMetrics( SM_CYSCREEN ), NULL,NULL,hInstance,NULL);
if( !hwnd ) return FALSE;
ShowWindow(hwnd,nCmdShow); //显示窗口
UpdateWindow(hwnd); //刷新窗口
return TRUE;
}
(1)第一个参数:成员style控制窗口样式的某些重要特性,在WINDOWS.H中定义了一些前缀为CS的常量,在程序中可组合使用这些常量.
(2)第二个参数:lpfnWndProc,给它消息处理函数的函数名称即可,必要时应该进行强制类型转换,将其转换成WNDPROC型。
(3)第三,四个参数:cbWndExtra域指定用本窗口类建立的所有窗口结构分配的额外字节数。当有两个以上的窗口属于同一窗口类时,如果想将不同的数据和每个窗口分别相对应。则使用该域很有用。这般来讲,你只要把它们设为0就行了,不必过多考虑。
(4)第五个参数:hInstance成员,给它的值是窗口所对应的应用程序的句柄,表明该窗口与此应用程序是相关联的。
(5)第六个参数:成员hIcon被设置成应用程序所使用图标的句柄,图标是将应用程序最小化时出现在任务栏里的的图标,用以表示程序仍驻留在内存中。Windows提供了一些默认图标,我们也可定义自己的图标,VC里面专有一个制作图标的工具。
(6)第七个参数: hCursor域定义该窗口产生的光标形状。LoadCursor可返回固有光标句柄或者应用程序定义的光标句柄。IDC_ARROW表示箭头光标.
(7)第八个参数:hbrBackground成员用来定义窗口的背景色。这里设为NULL。
(8)第九个参数:lpszMenuName用来指定菜单名,本程序中没有定义菜单,所以为NULL。
(9)第十个参数:lpszClassName指定了本窗口的类名。本程序命名为“menpao_RPG_DEMO”。
当对WNDCLASS结构域一一赋值后,就可注册窗口类了,在创建窗口之前,是必须要注册窗口类的,注册窗口类用的API函数是RegisterClass,注册失败的话,就会出现一个对话框如程序所示,函数RegisterClass返回0值,也只能返回0值,因为注册不成功,程序已经不能再进行下去了。
注册完了以后,就是创建该窗体,一般我们一时调用API函数中的CreatWindows()函数完成的
以上面注册的这个窗体为例
hwnd = CreateWindow(
"menpao_RPG_DEMO", //创建的这个窗体类的名称
"menpao_RPG_DEMO", //窗口标题
WS_POPUP|WS_MAXIMIZE, //窗口风格,全部风格见后表
0, //窗口位置x坐标
0, //窗口位置y坐标
GetSystemMetrics(SM_CXSCREEN ), //窗口高度,这里是全屏
GetSystemMetrics( SM_CYSCREEN ),//窗口高度,这里是全屏
NULL, //父窗口句柄
NULL, //菜单句柄
hInstance, //应用程序句柄
NULL); //最后一个参数是附加数据,一般都是0
参数1:登记的窗口类名,这个类名刚才咱们在注册窗口时已经定义过了。
参数2:用来表明窗口的标题。可以和第一个参数一样。
参数3: 用来表明窗口的风格,如有无最大化,最小化按纽啊什么的。
在DirectX编程中,我们一般使用的是WS_POPUP | WS_MAXIMIZE,用这个标志创建的窗口没有标题栏和系统菜单且窗口为最大化,可以充分满足DirectX编程的需要。
参数4,5: 用来表明程序运行后窗口在屏幕中的坐标值。
参数6,7: 用来表明窗口初始化时(即程序初运行时)窗口的大小,即长度与宽度。
参数8: 在创建窗口时可以指定其父窗口,这里没有父窗口则参数值为0。
参数9: 用以指明窗口的菜单,菜单以后会讲,这里暂时为0。
最后一个参数是附加数据,一般都是0。
如果窗口创建成功,CreateWindow( )返回新窗口的句柄,否则返回NULL。
不要以为创建和注册完了以后就大功告成,这样的话你是在屏幕上什么也看不见,我们必须要调用另外一个API函数才能看见窗体就是ShowWindow,他的原型是:
ShowWindow (hwnd, iCmdShow)
其第一个参数是窗口句柄,告诉ShowWindow()显示哪一个窗口,而第二个参数则告诉它如何显示这个窗口。
WinMain()调用完ShowWindow后,还需要调用函数UpdateWindow,最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新.
1.2.3 回调函数
看了上面这么多内容以后,你一定很疑惑了(我们知道了windows程序是怎么样运行的了,也知道了窗体是怎么样建立的,可是我们怎么通过消息让窗体触发事件的呢),这个问题很关键,对,我们就是通过回调函数来实现用消息触发事件的.下面我们来看看什么是回调函数.
windows就是一个大的消息循环程序,当遇到外部有其他命令时,就会暂停这个大循环,而进行外部给的命令的事件,而外部命令的这个函数就是回调函数(winproc()),他是通过句柄(hwnd)来确定这个命令触发的窗体。同样我们先写回调函数的原型。在这个函数中,不同的消息将被switch语句分配到不同的处理程序中去。Windows的消息处理函数的原型是这样定义的:
LRESULT CALLBACK WindowProc(
HWND hwnd, //接收消息窗口的句柄
UINT uMsg, //主消息值
WPARAM wParam, //副消息值1
LPARAM lParam //副消息值2
);
消息处理函数必须按照上面的这个样式来定义,当然函数名称可以随便取。
举个例说明一下,我们要实现分别点击鼠标左右键在窗体里面分别弹出"left button!"和"right button!"的功能.
//************************************************************
//函数:WinProc( )
//功能:处理主窗口消息
//************************************************************
LRESULT CALLBACK q( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch( message )
{
case WM_KEYDOWN://击键消息
switch( wParam )
{
case VK_ESCAPE:
MessageBox(hWnd,"quit!","Keyboard",MB_OK);
PostQuitMessage( 0 );//退出
break;
}
return 0;
case WM_LBUTTONDOWN:
MessageBox(hWnd,"left button!","Mouse",MB_OK);
return 0;
case WM_RBUTTONDOWN:
MessageBox(hwnd,"right button!","mouse",MB_OK);
return 0;
case WM_MBUTTONDBLCLK:
MessageBox(hwnd,"the other!","mouse",MB_OK);
return 0;
case WM_DESTROY:
PostQuitMessage( 0 );
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam); //调用缺省消息处理过程
}
看明白没有啊?case后面就是触发的消息,当不同的击键产生的时候会触发不同的消息,于是就通过这个回调函数结束前面的循环,产生消息事件,事件完成以后再回到消息循环里面.(哦,天啊,多么怀念当年的vb编程啊,IDE为我们封装了所有的事件和方法,只是调用就是了),同时我们更加应该感到庆幸,因为我们终于明白了windows的运行机制.
2.3 编后语
win32编程是一个博大精深的知识,我这里只是肤浅的介绍了一下我对于基础的消息机制的运行原理的理解,再次建议想要进入程序员行业的朋友一定要看看我开始推荐的两本书,(绝对不是书托呀^_^).他里面除了包含最基础的消息机制以外,还有包含所以基础类和派生类的原理,以及com的构架,以及Interface的运作机制,还有更为重要的就是当前流行的.net框架的基础Framework的运行机制和构架设计.这才是这两年编程的命脉,象医生一样,只有把握了命脉,才能把握编程环境的大方向,我们初学者才可以有地放失的学习对自己有用的技术,学习起来效果将会事半功倍.从下一章开始,我们就完全进入游戏编程的介绍了.
最后用候捷老师的话作为这章的结束语,顺便作为给大家一个忠告∶"勿在浮沙筑高台!"。
//*********************************************************
//函数:InitWindow( )
//功能:创建窗口
//*********************************************************
static BOOL InitWindow( HINSTANCE hInstance, int nCmdShow )
{
WNDCLASS wc;
wc.style = NULL; //窗口类风格
wc.lpfnWndProc = (WNDPROC)WinProc; //指向窗口过程函数的指针
wc.cbClsExtra = 0; //窗口类附加数据
wc.cbWndExtra = 0; //窗口类附加数据
wc.hInstance = hInstance; //拥有窗口类的实例句柄
wc.hIcon = NULL; //最小窗口图标
wc.hCursor = NULL; //窗口内使用的光标
wc.hbrBackground = NULL; //用来着色窗口背景的刷子
wc.lpszMenuName = NULL; //指向菜单资源名的指针
wc.lpszClassName = "menpao_RPG_DEMO";// 指向窗口类名的指针
RegisterClass(&wc); //注册窗口
hwnd=CreateWindow("menpao_RPG_DEMO","menpao_RPG_DEMO",WS_POPUP|WS_MAXIMIZE,0,0,GetSystemMetrics( SM_CXSCREEN ),GetSystemMetrics( SM_CYSCREEN ), NULL,NULL,hInstance,NULL);
if( !hwnd ) return FALSE;
ShowWindow(hwnd,nCmdShow); //显示窗口
UpdateWindow(hwnd); //刷新窗口
return TRUE;
}
(1)第一个参数:成员style控制窗口样式的某些重要特性,在WINDOWS.H中定义了一些前缀为CS的常量,在程序中可组合使用这些常量.
(2)第二个参数:lpfnWndProc,给它消息处理函数的函数名称即可,必要时应该进行强制类型转换,将其转换成WNDPROC型。
(3)第三,四个参数:cbWndExtra域指定用本窗口类建立的所有窗口结构分配的额外字节数。当有两个以上的窗口属于同一窗口类时,如果想将不同的数据和每个窗口分别相对应。则使用该域很有用。这般来讲,你只要把它们设为0就行了,不必过多考虑。
(4)第五个参数:hInstance成员,给它的值是窗口所对应的应用程序的句柄,表明该窗口与此应用程序是相关联的。
(5)第六个参数:成员hIcon被设置成应用程序所使用图标的句柄,图标是将应用程序最小化时出现在任务栏里的的图标,用以表示程序仍驻留在内存中。Windows提供了一些默认图标,我们也可定义自己的图标,VC里面专有一个制作图标的工具。
(6)第七个参数: hCursor域定义该窗口产生的光标形状。LoadCursor可返回固有光标句柄或者应用程序定义的光标句柄。IDC_ARROW表示箭头光标.
(7)第八个参数:hbrBackground成员用来定义窗口的背景色。这里设为NULL。
(8)第九个参数:lpszMenuName用来指定菜单名,本程序中没有定义菜单,所以为NULL。
(9)第十个参数:lpszClassName指定了本窗口的类名。本程序命名为“menpao_RPG_DEMO”。
当对WNDCLASS结构域一一赋值后,就可注册窗口类了,在创建窗口之前,是必须要注册窗口类的,注册窗口类用的API函数是RegisterClass,注册失败的话,就会出现一个对话框如程序所示,函数RegisterClass返回0值,也只能返回0值,因为注册不成功,程序已经不能再进行下去了。
注册完了以后,就是创建该窗体,一般我们一时调用API函数中的CreatWindows()函数完成的
以上面注册的这个窗体为例
hwnd = CreateWindow(
"menpao_RPG_DEMO", //创建的这个窗体类的名称
"menpao_RPG_DEMO", //窗口标题
WS_POPUP|WS_MAXIMIZE, //窗口风格,全部风格见后表
0, //窗口位置x坐标
0, //窗口位置y坐标
GetSystemMetrics(SM_CXSCREEN ), //窗口高度,这里是全屏
GetSystemMetrics( SM_CYSCREEN ),//窗口高度,这里是全屏
NULL, //父窗口句柄
NULL, //菜单句柄
hInstance, //应用程序句柄
NULL); //最后一个参数是附加数据,一般都是0
参数1:登记的窗口类名,这个类名刚才咱们在注册窗口时已经定义过了。
参数2:用来表明窗口的标题。可以和第一个参数一样。
参数3: 用来表明窗口的风格,如有无最大化,最小化按纽啊什么的。
在DirectX编程中,我们一般使用的是WS_POPUP | WS_MAXIMIZE,用这个标志创建的窗口没有标题栏和系统菜单且窗口为最大化,可以充分满足DirectX编程的需要。
参数4,5: 用来表明程序运行后窗口在屏幕中的坐标值。
参数6,7: 用来表明窗口初始化时(即程序初运行时)窗口的大小,即长度与宽度。
参数8: 在创建窗口时可以指定其父窗口,这里没有父窗口则参数值为0。
参数9: 用以指明窗口的菜单,菜单以后会讲,这里暂时为0。
最后一个参数是附加数据,一般都是0。
如果窗口创建成功,CreateWindow( )返回新窗口的句柄,否则返回NULL。
不要以为创建和注册完了以后就大功告成,这样的话你是在屏幕上什么也看不见,我们必须要调用另外一个API函数才能看见窗体就是ShowWindow,他的原型是:
ShowWindow (hwnd, iCmdShow)
其第一个参数是窗口句柄,告诉ShowWindow()显示哪一个窗口,而第二个参数则告诉它如何显示这个窗口。
WinMain()调用完ShowWindow后,还需要调用函数UpdateWindow,最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新.
1.2.3 回调函数
看了上面这么多内容以后,你一定很疑惑了(我们知道了windows程序是怎么样运行的了,也知道了窗体是怎么样建立的,可是我们怎么通过消息让窗体触发事件的呢),这个问题很关键,对,我们就是通过回调函数来实现用消息触发事件的.下面我们来看看什么是回调函数.
windows就是一个大的消息循环程序,当遇到外部有其他命令时,就会暂停这个大循环,而进行外部给的命令的事件,而外部命令的这个函数就是回调函数(winproc()),他是通过句柄(hwnd)来确定这个命令触发的窗体。同样我们先写回调函数的原型。在这个函数中,不同的消息将被switch语句分配到不同的处理程序中去。Windows的消息处理函数的原型是这样定义的:
LRESULT CALLBACK WindowProc(
HWND hwnd, //接收消息窗口的句柄
UINT uMsg, //主消息值
WPARAM wParam, //副消息值1
LPARAM lParam //副消息值2
);
消息处理函数必须按照上面的这个样式来定义,当然函数名称可以随便取。
举个例说明一下,我们要实现分别点击鼠标左右键在窗体里面分别弹出"left button!"和"right button!"的功能.
//************************************************************
//函数:WinProc( )
//功能:处理主窗口消息
//************************************************************
LRESULT CALLBACK q( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch( message )
{
case WM_KEYDOWN://击键消息
switch( wParam )
{
case VK_ESCAPE:
MessageBox(hWnd,"quit!","Keyboard",MB_OK);
PostQuitMessage( 0 );//退出
break;
}
return 0;
case WM_LBUTTONDOWN:
MessageBox(hWnd,"left button!","Mouse",MB_OK);
return 0;
case WM_RBUTTONDOWN:
MessageBox(hwnd,"right button!","mouse",MB_OK);
return 0;
case WM_MBUTTONDBLCLK:
MessageBox(hwnd,"the other!","mouse",MB_OK);
return 0;
case WM_DESTROY:
PostQuitMessage( 0 );
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam); //调用缺省消息处理过程
}
看明白没有啊?case后面就是触发的消息,当不同的击键产生的时候会触发不同的消息,于是就通过这个回调函数结束前面的循环,产生消息事件,事件完成以后再回到消息循环里面.(哦,天啊,多么怀念当年的vb编程啊,IDE为我们封装了所有的事件和方法,只是调用就是了),同时我们更加应该感到庆幸,因为我们终于明白了windows的运行机制.
2.3 编后语
win32编程是一个博大精深的知识,我这里只是肤浅的介绍了一下我对于基础的消息机制的运行原理的理解,再次建议想要进入程序员行业的朋友一定要看看我开始推荐的两本书,(绝对不是书托呀^_^).他里面除了包含最基础的消息机制以外,还有包含所以基础类和派生类的原理,以及com的构架,以及Interface的运作机制,还有更为重要的就是当前流行的.net框架的基础Framework的运行机制和构架设计.这才是这两年编程的命脉,象医生一样,只有把握了命脉,才能把握编程环境的大方向,我们初学者才可以有地放失的学习对自己有用的技术,学习起来效果将会事半功倍.从下一章开始,我们就完全进入游戏编程的介绍了.
最后用候捷老师的话作为这章的结束语,顺便作为给大家一个忠告∶"勿在浮沙筑高台!"。