VC编写Demo Scene技巧
2012/6/13 11:26:00 请友读忠(更多) E界MRP开发下载网 234阅 欢迎进入C/C++编程社区论坛,与300万技术人员互动交流 >>进入 目前国内Demo Scene基本处在0起步的阶段,已经有了一些小团体打算去参加欧洲的比赛,但是还没有一定的规模。同时,对于制作这类程序网上也没有系统的资料。使得制作Demo Scene被看成一种高深的事情。 下面我就说说目前在Windows平台下,使用最常用的开发工具(Visual C++)如何来制作一个符合64kb demo的程序框架和常用技巧。当然这只是一些次要手法,最核心的还是3d引擎、mod音乐的设计。因为那些资料很好找。所以就不再涉及。 我将介绍下面几个方面的技术: 1.如何产生体积最小的程序 2.如何不使用C运行库开发程序 3.如何实现高速GDI绘图 4.对于NT5.0提供的LayeredWindow的使用--不规则窗体、窗口的AlphaBlend渲染、鼠标事件穿透 5.如何将所有数据(代码、图片等)整合在一个C文件中 6.其他的一些编译技巧 7.一个完整的示例程序代码 问题和需求 Scene Demo中有一个项目为4kb-intro 或者 64kb-intro。 他要求Demo的程序体积必须小于或者正好等于4/64kb。而往往正是这类Demo程序在国内流传最广。因为大家都认为那么小的体积能播放长时间的高品质3d动画和音乐是不可思议得。甚至有人将45分钟的demo动画看成是avi视频,45分钟的音乐算作44KHz采样的wav。计算出将他们压缩到64kb完全是不可能的(见farbrausch的作品: the product 中的说明字幕)。当然这只是忽悠外行的吓人话。其实写过游戏引擎的人都知道那只是通过实时渲染的到的,而音乐本身就是体积在12kb左右的mod音乐序列(见我以前写过的文章)。 目前很多机器都已安装最新版本的DirectX,而OpenGL是windows的默认库之一。这样Demo Scene设计者一般就不需要自己去编写基本的3d引擎。动画部分几个基本特效的代码不会超过30kb(这里假设开发者具有较高的设计素质),而一些复杂网格模型的纹理贴图即时采用bmp保存,也在100kb-300kb左右。加上mod音乐和其播放引擎。一个64kb DemoScence程序的原始体积一般应在600kb。而不是通过用等效为avi文件计算方法算出的几个G的天文数字。 不过,问题就产生了,实际程序体积只有64kb。在这600kb->64kb还是有相当距离的。如何尽可能的去减少这部分文件大小,以及其中伴随的一些技巧就是本文所要讨论的。 Some Tricks对于减小程序代码体积,自然对coding的技巧有一定要求。不过这不是问题所在。大家都知道使用VC编译产生的程序,即使就写了句printf(\“HelloWorld\“);,也会产生出100kb以上的代码。但是实际上这行语句的有效代码只是: WriteFile(StdOut, \“Hello World\“,12,NULL,NULL);
WriteFile只是句API call, 实际上对应汇编大致为(这里只是说明性描述): push NULL push NULL push 12 push offset \“Hello World\“ push StdOut call WriteFile 就这么几行汇编指令,大致也就几十字节的数据,但VC却占用了大量的体积。而那些多余的文件数据主要是下面这些内容: 1. C运行库
这应该是最主要的因素,程序中会编译近大部分的c函数库的代码信息。同时,在程序的开始执行点到逻辑上认为起始位置:main函数之间也填充了大块的C运行库代码完成初始化工作(初始化堆数据、获取命令行数据)。对于完成通常编程任务来说,这些代码是十分必要的。但是对于一个需要尽可能小体积,且具有足够经验的Demo Scene开发者来说,这些代码绝对是鸡肋。 因此,减少程序体积的第一要务就是将C运行库完全从程序代码中剥离。不过要实现这个需要满足几个前提: a.程序不能使用C运行库,这是当然的。不过有人会问一些很常用的函数,诸如printf没有了该怎么办。回答只有是:自己使用等效的API实现。不过后文也会介绍一些办法 b.尽量不要使用C++语言,原因是对于class的一些操作,诸如析构操作。new/*运算符。这些本该是语言特性的语句,实际上在编译时会去调用c运行库来完成。 c.不要使用t*.h d.关闭VC后续版本提供的堆栈安全检查、异常处理等特性 e.完全采用Win32 API 对于很多人来说,要满足这些条件已经无法正常编写程序了。可能这也是Demo Scene的一个门槛。这里有一个变通办法,就是采用微软提供的精简版C运行库(LIBCTINY.LIB)或者使用ATL/WTL中的精简版C运行库。也能大幅减小体积。(实际上,在kernel.dll和ntdll.dll中也提供了C运行库的API接口) 在符合上述条件后,就能大胆的将C运行库去除。具体办法就是在链接设置中取消默认库,或者用下面语句: #pragma comment(linker, \“/nodefaultlib\“)
此时,不会有任何的C运行库被编译进程序,但是基本的windows API还是需要链接的。因此起码需要kernel32.lib。 #pragma comment(lib, \“kernel32.lib\“)
接下来可以按照需要添加相关的lib或者用LoadLibrary自行加载其他库。具体相信也不需要我废话了。 不过需要注意的是几个特列。位于Winnt.h中有如下定义: #define RtlFillMemory(Destination,Length,Fill) memset((Destination),(Fill),(Length))
define RtlZeroMemory(Destination,Length) memset((Destination),0,(Length))
相信MS这样做无非是考虑到运行效率和debug的需要。但是这样也给我们的工作造成了麻烦。如果在程序中直接或间接的使用了这2个函数(还有其它情况)的话,仍旧会被linker告知symbol _memset不存在。最直接的办法可以去修改这个winnt.h。但相信这是个十分愚蠢的做法。因此,推荐的办法是先undefine这些定义,再重新import。 #undef memset
undef RtlFillMemory
extern \“C\“ NTSYSAPI BOOL NTAPI
RtlFillMemory ( VOID *Source1,DWORD Source2,BYTE Fill );
define memset(Destination,Fill,Length) RtlFillMemory((Destination),(Length),(Fill))
发表评论