[attach]1219[/attach]
一些VC文章在机子里存了好久,发现有些没心机看,想全部删掉,删之前拿上来分享一下。
[此贴子已经被作者于2004-12-04 11:26:49编辑过]
Visulal C++是什么?--兼谈其他
这个问题很奇怪吗?大概是。不过,当我又看到有人发表诸如“Windows/Office是VC编写的”或者“VC是无所不能的”这种高论,我就禁不住这样问自己。
Visual C++究竟是什么?你平常在其中工作的那个标记着“Microsoft Visual C++”的窗口,真的就代表Visual C++吗?
按照我的理解,Visual C++是一个开发工具包,它大概可以分成三个主要的部分:
1. Developer Studio,这是一个集成开发环境,我们日常工作的99%都是在它上面完成的,再加上它的标题赫然写着“Microsoft Visual C++”,所以很多人理所当然的认为,那就是Visual C++了。其实不然,虽然Developer Studio提供了一个很好的编辑器和很多Wizard,但实际上它没有任何编译和链接程序的功能,真正完成这些工作的幕后英雄后面会介绍。我们也知道,Developer Studio并不是专门用于VC的,它也同样用于VB,VJ,VID等Visual Studio家族的其他同胞兄弟。所以不要把Developer Studio当成Visual C++, 它充其量只是Visual C++的一个壳子而已。这一点请切记!
2. MFC。从理论上来讲,MFC也不是专用于Visual C++,Borland C++,C++Builder和Symantec C++同样可以处理MFC。同时,用Visual C++编写代码也并不意味着一定要用MFC,只要愿意,用Visual C++来编写SDK程序,或者使用STL,ATL,一样没有限制。不过,Visual C++本来就是为MFC打造的,Visual C++中的许多特征和语言扩展也是为MFC而设计的,所以用Visual C++而不用MFC就等于抛弃了Visual C++中很大的一部分功能。但是,Visual C++也不等于MFC。
3. Platform SDK。这才是Visual C++和整个Visual Studio的精华和灵魂,虽然我们很少能直接接触到它。大致说来,Platform SDK是以Microsoft C/C++编译器为核心(不是Visual C++,看清楚了),配合MASM,辅以其他一些工具和文档资料。上面说到Developer Studio没有编译程序的功能,那么这项工作是由谁来完成的呢?是CL,是NMAKE,和其他许许多多命令行程序,这些我们看不到的程序才是构成Visual Studio的基石。
为什么我会觉得“Windows是用VC开发的”这种说法很奇怪?因为它太含糊了。用VC,可以编写MFC应用,也可以编写纯SDK程序,不论哪一种方式,都不一定是非VC不可。只要乐意,我完全可以用UltraEdit来写出一个MFC程序,再用CL编译之,没有必要一定动用VC这个大家伙。而且有许多黑客和买不起Visual Studio的人就是这么干的!用SDK编程就更不需要VC了,Down一个Borlan C++ Compiler下来,或者用lcc之类的编译器,同样可以达到目的。再说了,Windows可不是一个单纯的产品。用VC来编写Windows外围程序是完全不成问题的,可是操作系统的核心部分呢?就算可以用VC来编写代码,调试怎么办?VC自身的调试器对一般的应用功能是够强大的,可是对于系统级的调试根本无能为力,因为这个调试器本身就是依赖于操作系统的。只有系统级的调试程序如debug,SoftIce和Wdebug这些工具才能完成如此重大的任务。
从历史上来看,Visual C++ 1.0的出现晚于Windows 3.0,而且那时候的MFC只有一个雏形而已,用来开发操作系统根本是不可能的事情。在Visual C++ 1.0的前面倒是有一个Microsoft C/C++ 7.0,但是它整体水平不如Borland C++ 3.1,在扩展内存管理方面的功能又不如Watcom C++ ,所以一直没有占据很大的市场。它现在已经不作为单独的产品,但仍然作为Platform SDK的主要组成部分而存在于Visual Studio产品中,而且其功能比过去也不可同日而语了。到Windows 95问世的时候,MFC仍然在尽力追赶操作系统的功能。应该说Visual C++ 5.0是一个转折点,一方面MFC已经发展比较完善,另一方面,操作系统的基本结构也已经稳定,后面就主要着眼于系统整合与完善作为商务平台的功能。已经稳定的系统不可能再进行翻天覆地的修改,所以,我比较能够接受“Windows系统是用Microsoft C++和MASM作为编译器完成的”这种说法。研究Windows的系统文件可以看出,很多文件显示出来的Linker Version明显是Microsoft C++编译器。至于代码是用什么编写的?我不知道,也不想知道,除了Developer Studio的编辑器之外,任何好的文本编辑器都能够做到这一点。
Visual C++是无所不能的吗?唔,最好也是分开来说。Developer Studio肯定不是—它只是个外壳而已。MFC呢?也不是。一方面它是对API的封装,离开了API它就什么也干不了;另一方面,MFC对API的封装也不够全面,有些时候还是要直接调用API函数才能够“为所欲为”。至于Platform SDK,倒真的可以说它几乎是无所不能的。不过,过分强调这一点并没有太大意义。只要有一套完整的编译器,和必须的支持文件,其他开发工具也可以说是“无所不能”的,比如Borland C++ Compiler或者lcc都可。
老实说,我并不喜欢“无所不能”这类字眼。关键在于各人的理解不同。如果我较起真来,说能不能写个VC程序让电脑拿起鼠标砸向我不喜欢的老板,你说它能办得到吗?所谓的“无所不能”究竟有何意义?让我用VC写一个Server,能在普通工作站上支持每秒几千万的访问量,杀了我也办不到,不管VC的优化手段是多么有效。在具体的平台上,在特定的操作系统中,不论多么强大的工具,最终还是要受到平台和系统本身的限制。大家应该知道这个悖论吧:上帝能否制造出一块他自己也举不起来的石头?
我也常常看到“MFC永远不会过时”或者“C++是不会灭亡的”这种发言。我理解发言人的心情,不过这种说法绝不客观。一种语言也好,一个Application Framework也罢,它们之所以有今天的地位,并不是纯粹自然形成的,有许多复杂因素的作用,也有时势造英雄的理由在内,所谓“居高声自远,非是籍秋风”是也。历史的舞台从来不是为某人专设,即使真有所谓万古长青的怪胎,恐怕也正应了那句老话:“众人都死了,只剩咱们两个老妖精,有什么意思!”我们现在使用的语言,不论Basic,Pascal还是C++,甚至如日中天的Java和C#,终究都会有功成身退的一天。这并不是我们的损失,相反,薪尽火传,一种语言中好的,合理的因素,肯定会被后续者所继承和发扬,自然界的新陈代谢本该如此。
天空没有飞翔的痕迹,而飞鸟已经飞过。一种语言只要曾经在历史上留下浓墨重彩的一笔,完成它“为先贤继圣学,为万世开太平”的历史使命(有点夸张),这就够了,何必缠绵不舍作儿女之态!不知道我有生之年会不会看到C++的消亡,如果真有这么一天,我会拍手欢呼,因为这说明已经有了另外一种更新更好的语言来代替它(或许是几种)。不过照我猜想,像C++这种轰动武林惊万教的语言,其灭亡恐怕也不会是悄无声息,而多半属于“始皇既没,余威震于殊俗”那种情况。
最后请允许我发表一点感慨。语言的优劣其实是一个无需讨论的问题,个人的经历和所处环境在很大程度上就决定了你对某种语言的看法,这是很个人的东西。好比碰到一位法国朋友,他多半会自豪的告诉你:法语是世界上最好最优美的语言。对这种说法我会微笑表示赞同,并且欣赏他的民族自豪感,而不会觉得这是对汉语或者英语的贬低—虽然我心底里一直深信,汉语才是世界上最好的语言。当然,如果他对我说“你们那些破烂中文是些什么玩意”,那我可能就是另外一种反应了。
说了这么多,意思无非是想少些无谓的争论罢了。常在论坛上看到“XXX是最好的语言(编译器),XX是什么东西”之类发言。我不想和他们争论,一个人对一样东西既然完全失去了接触和了解的兴趣,那么说什么大概都没有用了。只是觉得遗憾而已。人世间的隔膜与误会,大多是由于彼此不了解而引起,而多少悲剧正是由此而发生呵。在编程的世界里大概不会这么严重,不过言为心声,多少也可看出个人的品行。如果某个人A常在我面前说B的坏话,那么我对B不会有恶感,相反我对A的印象分要减去20。自己不了解的人或者事,不管,不说,也就是了。何至于恶言相加呢。
我参加工作的时间不长,各种各样的软件工程师倒是见过不少。就我看到的情况,程序员实在是很沉默寡言的一类人,平时总是表现的温文尔雅,有时候却难得的能见到他们大发脾气,扔鼠标,砸键盘,捶显示器,干什么的都有(这种情况多半是遇到没有办法除掉的Bug了)。面对亲人和朋友的时候他们有时候会选择长久的沉默,只有坐到机器前面时才会发现他们的痴迷和狂热。虽然普遍的不善言谈,但是他们似乎总能在游戏或者网络中找到发泄的方式。所以我在各种各样的论坛上看到语法错误不忍卒读的文字,看到互相指责乃至于人身攻击的情况,甚至看到许多不雅的词汇,虽然心情无论如何愉快不起来,但是我想我能理解。只是,我仍然感到担心,毕竟程序员这一行干几年就了不起了,而人生还有很长的路要走呢。没有一个健全的心态,没有足够为人处世的技巧,30岁以后的人生该如何把握呢?
这并非哗众取宠, 通常学习一种电脑技术有两种方法. 一种是自己摸索, 在错误的方向上一错再错, 屡战屡败, 不过最后得道成功. 另一种是有人或好的材料指导, 因而事半功倍, 在正确的方向上走了速成的捷径. 就象KFC 的鸡一样. 第一种学法能学出电脑天才, 因为所谓电脑高手, 其实就是排错试错的高手. 而第二种则出电脑专才. 这个两小时(?)的学习, 不能使你深入的掌握DD, 不过可以给你编制DD的框架. 能给你一个起始点, 这个教程就算成功了.
DirectDraw编程需要一些背景知识:
DirectDraw是为在 Windows95/NT 下实现高速图形显示所写的程式库.
高速图形显示的基本方法是用一种叫做 Page Flipping的技术. 关于什么是 Page Flipping, 参见古技介绍.如果你不急的话, 看到下面, 你也会看到.
在 Windows95/NT下做 Page Flipping 分为全屏的和窗口的两种. 在全屏下Page Flipping 叫做Flip, 在窗口下叫做 Blit.
知道了这些背景知识, 我们可以开始写程式了.
写所有 DirectDraw的程式, 差不多都有以下几个步骤,
1. 初始化, 这是每个程式都需要的劳什子. 2. 设置显示模式. 3. 在内存里建立PageFlipping所需要的两个页, 前页和后页. 4. 给显示的区域加个画框以免画到外面来. 5. 在后页画图, 然后"刷"的一下子换到前页来.
步骤一: 初始化
DirectDraw 是一个面向对象的函数库. "面向对象"的意思并不是指面对著你的女朋友, "对象" 在这里, 你可以简单地想象成是一个模板, 比方说,"政府", 一旦你说:"我成立了一个政府". 别人就会立即把你套入"政府模板", 自然而然地认为你有印钞票的功能. 在我们的程式里, 你一旦声明一个变量(比如 myDD)是 DirectDraw对象 (DirectDraw对象的正式名为 LPDIRECTDRAW) , 这个myDD就有了 DirectDraw对象的所有的功能和特性. 定义的语法是:
LPDIRECTDRAW pMyDD; 除了 DD的对象外, 还有几个重要的对象, "页面", "裁剪板" 和 "调色板". "页对象"用来定义"前页"和"后页". 定义如下:
LPDIRECTDRAWSURFACE pMyDDSFront; LPDIRECTDRAWSURFACE pMyDDSBack; 一个"裁剪板对象", 在窗口模式下用来剪去画出窗口边界的部份.
LPDIRECTDRAWCLIPPER pMyClipper; "调色板"设定屏幕的颜色表, 在读取256色的 Bitmap时要用到. LPDIRECTDRAWPALETTE myDDPal; 最最重要的"对象"就是这些了. 当然 DirectX还有很多复杂晦涩的对象. 这是速成不起来的.
编制 Windows 程式有一大堆变量和对象是 Windows所要求的, 这也是我最烦 Microsoft的地方. Microsoft 似乎知道这点. 所以在 VC4.0后的版本有了 Wizard的功能帮你自动生成代码. 尽量地去用它的 Wizard使我们的生活变得容易.
由于我们的程式可能会占用一个窗口, 就给这个窗口一个 handle:
HWND myWnd 初始化的工作还没有完, 我们要把这些对象指向一个安全的地方 Null.
pMyDD = NULL; pMyDDSFront = NULL; pMyDDSBack = NULL; pMyClipper = NULL; pMyDDPal=NULL;
最后, 在 Windows系统为我们的 myDD对象开辟相应的区域:
DirectDrawCreate( NULL, //用当前的显示驱动 &pMyDD, NULL)) Okay, 烦人的初始化总算完了.
步骤二: 设置屏幕的显示方式.
DirectDraw 有自己的设置屏幕的方式, 而且它的屏幕模式分为"全屏"( exclusive mode)和"窗口"( normal mode). 各有各的设置方法. 设置的主要区别在于 SetCooperativeLeve的参数.
SetCooperativeLeve 在"窗口"模式下这样设置: pMyDD->SetCooperativeLevel(AfxGetMainWnd()->GetSafeHwnd(),DDSCL_NORMAL); 而在"全屏"模式下这样设置:
pMyDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ); 如果它们的返回值为 DD_OK表示成功. 我们就可以把屏幕调节成我们想要的样子, 例如 640x480x8. 也就是256色. 究竟有那些屏幕模式可用取决于你的显示卡
pMyDD->SetDisplayMode( 640, 480, 8 ); 现在, 我们已经有了一个屏幕, 不过还不能在上面画画, 我们需要步骤三来替我们建立一个可供画画涂涂用的画板.
步骤三: 建立前后页(两块画板).
两块画板的好处是可以一边在一块上面画, 一边给别人看已经画好的另一块. 等这块画好了, 两块板就对调一下, 让别人看新画好的这块. 如果画的足够快, 换的足够快. 看的人就会看到动画了, 就象电影的效果一样. 我们把这叫做 Page Flipping. 这里先要介绍的是怎样在系统中建立两块画板( double buffering), 不过你也可以根据需要建立三块,四块画板. DDSURFACEDESC ddsd; //这个结构描述"页"的特徵. ddsd.dwFlags = DDSD_CAPS; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;//指定我们用的是前页.
ddsd.dwSize = sizeof(ddsd); //尺寸
// 做前页: HRESULT result; result = pMyDD->CreateSurface(&ddsd, &pMyDDSFront, NULL); 当发生错误时, 要记得 Release对象.
if (result!=DD_OK) { pMyDD->Release(); pMyDD = NULL;
}
ddsd.dwWidth = scr_width; //设定后页的大小, ddsd.dwHeight = scr_height;
//指定 我们要后页 ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
//做后页 result = pMyDD->CreateSurface(&ddsd, &pMyDDSBack, NULL);
}
步骤四: 给显示区加一个画框(裁剪板).
在窗口下. 为了防止 DirectDraw 画到窗口外面去. 需要加一个画框(裁剪板). 可以用 CreateClipper来创建剪贴板. result = pMyDD->CreateClipper(0, &pMyClipper, NULL);
创建后,把它套到窗口上去, 所以要知道是那一个窗口( Handle).
myWnd = AfxGetMainWnd()->GetSafeHwnd();// 从系统中拿到窗口的 Handle result = pMyClipper->SetHWnd(0, myWnd);
// 把剪贴板加到窗口上去 result = pMyDDSFront->SetClipper(myClipper);
步骤五: 在后页画图, 前后页互换.
其实到这里才是真正开始写游戏的地方, 以前在 DOS下写游戏, 就是直接从这个步骤开始的. 以上这些工作, 都是 Microsoft强加给我们的.
写游戏之前我们先来解决前后互换的问题.
// 如果前页的内存被 Windows"征用"了, 这里把它要回来. 这个检察常常会被忘记. if (pMyDDSFront->IsLost() == DDERR_SURFACELOST) pMyDDSFront->Restore();
DirectDraw 用来互换的语句有 Blt和 BltFast. BltFast据称比 Blt快10%.
result = pMyDDSFront->Blt(&rcTo, pMyDDSBack, &rcFrom, DDBLT_WAIT,NULL); result = pMyDDSFront->BltFast( 0, 0, pMyDDSBack, &rcFrom, DDBLTFAST_SRCCOLORKEY);
如果程式工作在"全屏"模式下. 前后页互换容易得多, 只是一句:
result = pMyDDSFront->Flip( NULL, 0 ); 现在就到了游戏的主要部份了, 我们称之为"游戏逻辑"部, 在普通的游戏中, "游戏逻辑" 通常要做很多事, 如画游戏场景, 故事处理等等. 做完这些事后, 再和前页做换屏. 不过怎样做你的游戏, 就没有我什么事了:-)
所有的步骤都讲完了. 是不是觉得特容易? 半个小时就够了? okay, 剩下的一个半小时让你把它们变成真正的代码吧. 打开你的 Visual C++, 我用的是 VC5.0, 不过你也可以用 VC4.0. 再低恐怕就不可以了. 别忘了检查一下你的 DirectX SDK 有没有安装好. 打开VC, 选择 MFC app EXE Wizard 来生成程式的框架, 我假设学DirectDraw的人应该会用VC, 所以怎样用 Wizard, 我就不再赘述.
进入 IDE环境后, 加入一个新 CPP file, 把上面用到的子函数的代码打入. 当然你还需要一个 .H file 来放变量名, 对象名和子程式名.
MFC(Microsoft Fried Checken)的 Wizard会帮你生成 ::InitInstance() 和 ::OnIdle(LONG lCount), 把你的初始化部份, 建页等步骤放在 InitInstance子类里. 把"游戏逻辑"和换页放在 OnIdle里.
最后我想给大家一个完整的可运行的例子, 不过平均每分钟六个汉字的速度打得我头昏眼花. 四肢发麻. 过两天再说吧...