vc调试技巧:VC编写Demo Scene技巧



目前国内DemoScene基本处在0起步阶段已经有了些小团体打算去参加欧洲比赛但是还没有规模同时对于制作这类网上也没有系统资料使得制作DemoScene被看成种高深事情

下面我就说说目前在Windows平台下使用最常用开发工具(VisualC)如何来制作个符合64kbdemo框架和常用窍门技巧当然这只是些次要手法最核心还是3d引擎、mod音乐设计那些资料很好找所以就不再涉及



我将介绍下面几个方面技术:



1.如何产生体积最小

2.如何不使用C运行库开发

3.如何实现高速GDI绘图

4.对于NT5.0提供LayeredWindow使用--不规则窗体、窗口AlphaBlend渲染、鼠标事件穿透

5.如何将所有数据(代码、图片等)整合在个C文件中

6.其他些编译窍门技巧

7.个完整举例代码

问题和需求

SceneDemo中有个项目为4kb-ro或者64kb-ro他要求Demo体积必须小于或者正好等于4/64kb而往往正是这类Demo在国内流传最广大家都认为那么小体积能播放长时间高品质3d动画和音乐是不可思议得甚至有人将45分钟demo动画看成是avi视频45分钟音乐算作44KHz采样wav计算出将他们压缩到64kb完全是不可能(见farbrausch作品:theproduct中介绍说明字幕)当然这只是忽悠外行吓人话其实写过游戏引擎人都知道那只是通过实时渲染而音乐本身就是体积在12kb左右mod音乐序列(见我以前写过文章)



目前很多机器都已安装最新版本DirectX而OpenGL是windows默认库的这样DemoScene设计者般就不需要自己去编写基本3d引擎动画部分几个基本特效代码不会超过30kb(这里假设开发者具有较高设计素质)些复杂网格模型纹理贴图即时采用bmp保存也在100kb-300kb左右加上mod音乐和其播放引擎个64kbDemoScence原始体积般应在600kb而不是通过用等效为avi文件计算思路方法算出几个G天文数字



不过问题就产生了实际体积只有64kb在这600kb->64kb还是有相当距离如何尽可能去减少这部分文件大小以及其中伴随些窍门技巧就是本文所要讨论



SomeTricks
对于减小代码体积自然对coding窍门技巧有定要求不过这不是问题所在大家都知道使用VC编译产生即使就写了句prf(\"HelloWorld\");也会产生出100kb以上代码但是实际上这行语句有效代码只是:

WriteFile(StdOut,\"HelloWorld\",12,NULL,NULL);
WriteFile只是句APIcall,实际上对应汇编大致为(这里只是介绍说明性描述):

pushNULL

pushNULL

push12

pushoff\"HelloWorld\"

pushStdOut

callWriteFile

就这么几行汇编指令大致也就几十字节数据但VC却占用了大量体积而那些多余文件数据主要是下面这些内容:

1.C运行库
这应该是最主要原因中会编译近大部分c代码信息同时开始执行点到逻辑上认为起始位置:的间也填充了大块C运行库代码完成化工作(化堆数据、获取命令行数据)对于完成通常编程任务来说这些代码是十分必要但是对于个需要尽可能小体积且具有足够经验DemoScene开发者来说这些代码绝对是鸡肋 [Page]

因此减少体积要务就是将C运行库完全从代码中剥离不过要实现这个需要满足几个前提:

a.不能使用C运行库这是当然不过有人会问些很常用诸如prf没有了该如何办回答只有是:自己使用等效API实现不过后文也会介绍些办法

b.尽量不要使用C语言原因是对于些操作诸如析构操作/delete运算符这些本该是语言特性语句实际上在编译时会去c运行库来完成

c.不要使用tchar.h

d.关闭VC后续版本提供堆栈安全检查、异常处理等特性

e.完全采用Win32API

对于很多人来说要满足这些条件已经无法正常编写可能这也是DemoScene个门槛这里有个变通办法就是采用微软提供精简版C运行库(LIBCTINY.LIB)或者使用ATL/WTL中精简版C运行库也能大幅减小体积(实际上在kernel.dll和ntdll.dll中也提供了C运行库API接口)

在符合上述条件后就能大胆将C运行库去除具体办法就是在链接设置中取消默认库或者用下面语句:

#pragmacomment(linker,\"/nodefaultlib\")
此时不会有任何C运行库被编译进但是基本windowsAPI还是需要链接因此起码需要kernel32.lib

#pragmacomment(lib,\"kernel32.lib\")
接下来可以按照需要添加相关lib或者用LoadLibrary自行加载其他库具体相信也不需要我废话了

不过需要注意是几个特列位于Winnt.h中有如下定义:

#RtlFillMemory(Destination,Length,Fill)mem((Destination),(Fill),(Length))
#RtlZeroMemory(Destination,Length)mem((Destination),0,(Length))
相信MS这样做无非是考虑到运行效率和debug需要但是这样也给我们工作造成了麻烦如果在中直接或间接使用了这2个(还有其它情况)仍旧会被linker告知symbol_mem不存在最直接办法可以去修改这个winnt.h但相信这是个十分愚蠢做法因此推荐办法是先un这些定义再重新import

#undefmem
#undefRtlFillMemory
extern\"C\"NTSYSAPIBOOLNTAPI
RtlFillMemory(VOID*Source1,DWORDSource2,BYTEFill);
#mem(Destination,Fill,Length)RtlFillMemory((Destination),(Length),(Fill))


其他类似情况也可以这样处理同时大家也可以开动智慧将部分C运行库用kernel32.dll或者ntdll.dll中现成等价替换这样对于暂时不习惯完全采用WINAPI编写开发者来说能带来些便利

到目前为止在代码逻辑上已经将C运行库从剥离了但实际上编译还是不会通过原因在于目前真正起始位置还不是或者WinMain(...)这些在执行这些逻辑上开始位置前还有不少C运行库Wrapper

要将这个Wrapper去除直接办法就是在link选项中修改入口地址到目前(WinMain)或者用等效语句:

#pragmacomment(linker,\"/ENTRY:MyMain\") [Page]
此时如果中有MyMain这个那么他将真正作为入口点(OEP)不过要注意作为OEP不能带参数传统或者WinMain中那些命令行信息参数都是由的前C运行库Wrapper获取提供而直接从OEP启动时候是没有参数提供给这样将造成堆栈不平衡(但实际影响不是很大)

在做到这步后就可以尝试编译以VC中创建SDKhelloworld举例为例经过目前操作产生应该在2kb左右或更小(作者并未测试只是估计值)不过这里要注意目前个问题:进程无法结束这是在原始C运行库Wrapper中执行完了WinMain等ExitProcess终止进程因此在我们新OEP适当地方加入此语句即可

对于原先WinMain等中参数获取
要获取诸如命令行参数或者HINSTANCE值其实可以相应API实现对于命令行可以:

GetCommandLine(void);
对于HINSTANCE可以

GetModuleHandle(NULL);
其他参数这里就不列举了可以查阅相关文档或者反汇编原始CWrapper分析

2.段合并问题
段(Section)是个PE文件格式中术语具体可以参照MSDN中篇很完善PE格式介绍文章\"AnIn-DepthLookotheWin32PortableExecutableFileFormat\"(http://msdn2.microsoft.com/en-us/magazine/cc301805.aspx)对于采用VC生成文件般会带有下面3个段:

.text

.data

.rdata

般而言代码主体将被置于.text些常量数据和资源文件会分配在其余2个段中对于个段来说大小不是随意因此当代码或者资源较少时就会在段内存在大量冗余数据(般都是0)这样就白白占用了体积解决办法就是把它们合并到个或多个段中压缩体积

采用如下语句合并2个段:

#pragmacomment(linker,\"/MERGE:.data=.text\")
也可以重新定义个段然后将所有段合并到新定义段中

#pragmacomment(linker,\"/SECTION:tiny,\")
#pragmacomment(linker,\"/MERGE:.data=tiny\")
#pragmacomment(linker,\"/MERGE:.text=tiny\")
#pragmacomment(linker,\"/MERGE:.rdata=tiny\")
这样也能在定程度上减少体积

不过注意这个办法适用范围并不大尤其是对于含有资源基本很难成功运行而且他对体积减少贡献不是很大

3.将加壳
如果仅仅利用上述手段实际上只是得到了本应该具有体积但是就像前面提到个典型demo也要占用600kb空间那么如果做近步缩减呢?

思路方法其实就是数据压缩对于代码本身、纹理贴图(假设为非压缩bmp)、MOD音乐(其中音色为wav格式)这些都含有很高冗余信息具有很大压缩潜力因此可以使用些压缩加壳工具将加壳就Demo而言般采用UPX和由farbrausch专门为demo设计kkrunchy同时也十分推荐国内由Dwing制作加壳工具:WinUpack [Page]



这几个工具均能从网络上免费获取下面我做点简要点评

对于UPX和WinUnpack
采用是LZ系列压缩算法而WinUnpack采用些改进措施所以压缩效果比UPX好同样采用LZ系列压缩算法是人人皆知WinZip和WinRAR工具对于这2个压缩壳而言定位于通用加壳所以具有较高稳定性而UPX历史悠久因此基本上可以应对所有情况

对于kkrunchy
他采用是目前号称压缩比最高PAQ7-9算法

参考:

对于各类压缩算法比较:http://www.maximumcompression.com/index.html

PAQ算法在wikipedia介绍:http://en.wikipedia.org/wiki/PAQ(大陆需要设置代理访问)

PAQ算法相关介绍说明和论文:http://www.cs.fit.edu/~mmahoney/compression/

PAQ虽然具有很高压缩比但是代价就是缓慢解压缩和压缩速度有人曾测试说解压缩1MB数据需要半分钟但是很多demo体积都不大而且自身加载后还需要近1分钟预先计算过程所以这还可以接受

不过kkrunchy还有个缺陷就是他会采用上面提到段合并技术因此很多带有资源加壳后便无法执行

这里谈谈我本人看法我比较倾向使用前2者是这些加壳工具在压缩比率上差别不是很大不过UPX目前可以看成是开发技术WinUnpack为国人开发而kkrunchy开发者farbrausch自身就是DemoScene参赛者

不过这只是本人看法

在经过了加壳处理后原先600kb很容易缩小到了64kb左右这样个“神奇”demo诞生了(当然真正能让人称上神奇应该是本身画面和音乐)
Tags:  vc编写dll vc编写windows服务 vc编写计算器 vc调试技巧

延伸阅读

最新评论

发表评论