用条件编译与DLL封装Resource相结合,减小应用程序体积2011-01-21 15:00:06| 分类: 个人日记 | 标签: |字号大中小 订阅 如果开发/维护一个由许多个模块组成一个项目的应用程序,可以单独编译成许多个单应用程序,也可写一个外壳统一管理、调用这些单个模块,由于开发和维护需要,我们用VSS来进行代码同步管理,将代码托管到服务器进行统一,另外,由于这些模块之间基础类是公用的,为免于公共类的更改导自各个模块全部重新编译或是多人合作开发时未及时通知等原因导致程序版本的不统一,人为规定对公共部分代码更改后及时[Check In],并规定每次打开项目前对于公共部分代码进行[Get latest version]操作,这样VSS会以最根据本地代码与服务器代码的差异性自动同步为最新代码,以此保证不论谁,不论何时编译何种实验模块,始终是最新版本程序。
问题提出:此各情况优点是代码维护、更改简单,而且公共部分一次更改全体模块都被强行更改掉,只需要[Get latest version]一下即可编译出最新版本应用程序,无特殊情况不用作任何代码修改。但此种情况也有两点不足:一是所有CPP代码均编译并链接到应用程序里面,虽然我们可以根据不同的需要编译出不同的应用程序界面与应用,但实际上全体代码实现均在这个EXE里面,只要这个EXE被破解,那么就可以得到一个模块而使用全体分析程序;二是由于所有项目组成一个工程,各界面的皮肤文件都放于此工程的Resource里面,随着加入的模块越来越多,Resource的体积会越来越庞大,而EXE对bmp类型资源不会压缩,这样就导致多个模块存在的项目每个编译出来的应用程序体积达到23M之大,其实剥去Resource的体积,EXE编译出来不会超过2M,体积过大在向客户传更新版本与远程维护等方面都会受到一定的影响。
问题解决:即然知晓问题症结,那就围绕这两点来进行更改,让我们的项目即便于维护,又可达到体积轻便,利于传输,同时针对不同的模块实现,删掉其它模块的实现,这样即使EXE文件被破解,也不会得到其它模块的分析程序。
一、所有分析实现均被编译链接于应用程序的解决 在VC6编译器中,所有项目中所包含的CPP文件都会被编译,除非自己编写makefile文件,但那样做一来维护超级麻烦,一个项目里面可能有上千个CPP文件,要一个一个的去查并剃掉不用的CPP,并且每个模块都得做一次操作,工作量很大,且容易出现头文件包含之间的问题,此方法被否。另一种方法,仿照头文件的防止重复包含宏定义,也做一个条件宏,但是在CPP里面,因为头文件之间可能有相互关联,贸然去掉可能改致编译报错,经实践检测,此方法能保证正常编译的情况下将CPP中的实现去掉。具体实现方法如下: 在项目APP头文件中定义一个enum enum ZINDEX_XXX{ XX1, XX2, XX3, XX4, XX5, XX6, XX7, XX8, XX9, XX10 }; 然后定义一个宏用作当前编译模块标识 #define CURRENT_MODULE_NUMBER XX8 最后在每个模块相关CPP文件里面添加上条件宏判断,类似如下 #include "../../stdafx.h" #include "../../XXT100.h" #include "../../MainDlg.h" #include "XXT100Dlg.h" #if CURRENT_MODULE_NUMBER == XX8 … … #endif // CURRENT_MODULE_NUMBER == XX8 这样,既保证VC编译规则符合的情况下,又对CPP内的实现代码进行了保护,指定某个模块的编译,EXE文件里面就只有该模块的实现,其它模块的实现均被屏蔽,即使被破解也只有得到找不到入口或实现代码的提示。
二、Resource文件加入过多导致EXE文件体积庞大的解决 其实从第一步操作下来,整个EXE体积已经会小一部分了,但是这一部分与整个应用程序比起来相当的小,那我们接下来解决多图片Resource引起的EXE程序体积过大问题。三种思路:一种是用外部工具将EXE压缩一遍;一种是将这些图片资源专门放置一文件夹中,使用的时候动态调入;三种是使用DLL封装这些资源,使用的时候载入DLL内的资源即可。 第一种办法最简单,如使用UPX等加壳工具,既可对EXE程序进行简单加壳,增加破解难度,也可以对资源内的图片等大体积文件进行一定的压缩,但这种方法的有可能导致程序的不稳定,另外这种方式对于程序的编译效率提升无作用。第二种办法比较简单,但是需要在应用程序安装文件夹下多一资源文件,而且这些文件可能被外部修改,导致莫名错误,最重要的是,如果采用这种方式,那么原工程内的调用图片资源之处代码改动较大。第三种办法较为可行,只需要建一DLL项目,将原项目里面的res文件夹及RC文件拷到新DLL项目里面,去掉项目配置里面的_AFXDLL项,编译生成DLL即可,然后将RC里面的资源定义放入一头文件,在所需项目里面rc文件资源后引入这个头文件即可。 我们采用第一种办法和第三种办法混合:先分离出资源文件,除窗口和控件定义保留外,其余的图片、按钮、文字资源什么的全部导出到DLL,然后在项目APP类的CPP文件里面申明该变量并载入我们生成的DLL资源文件。最后在需要使用该句柄的地方申明并使用即可。最后再将生成的EXE用UPX工具加壳压缩一下即成最终版本。 生成RES头文件类似于 #ifndef _AFX_DLL_RES_HAD_DEFINED #define _AFX_DLL_RES_HAD_DEFINED
#define IDC_POINTER 104 #define IDC_CURSOR 158 #define IDB_BITMAP_MAINFACE 132 #define IDB_BITMAP_OPENFILE 141 … … #define IDI_ICON_MODE 234
#endif // _AFX_DLL_RES_HAD_DEFINED 在使用资源DLL的项目里面更改资源头文件Resource.h //{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by XXTT100.rc // #define IDM_ABOUTBOX 0x0010 #define IDD_ABOUTBOX 100 … … #define IDD_EXPBASE_DIALOG 10001
#include "XXX-100_res.h" // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 245 #define _APS_NEXT_COMMAND_VALUE 32771 #define _APS_NEXT_CONTROL_VALUE 1321 #define _APS_NEXT_SYMED_VALUE 127 #endif #endif Stdafx.cpp文件中增加项 //外部资源载入句柄 HMODULE g_hResHandle; 项目app类cpp文件中增加项 extern HMODULE g_hResHandle; … … BOOL CXXT100App::InitInstance() { … … g_hResHandle = LoadLibrary("TMV_RES.DLL"); if (!g_hResHandle) AfxMessageBox("Load library error..."); … … } int CXXT100App::ExitInstance() { … … if (g_hResHandle) FreeLibrary(g_hResHandle); … … return CWinApp::ExitInstance(); } 如此即可在最少改动代码的基础上继续使用原来项目中定义的资源,而在项目CPP中资源的体积则大大减小。最主要的是,如果以后程序要做多语言版本的程序,那么,我们只需要把资源文件DLL的工程里的RES文件夹拷贝一份,将其中的文字啊,图片什么的重新改成相应语言的版本即可,有闲心重命名一下DLL,改一下项目里面LoadLibrary的文件名,其它不变,没空的话啥都不改,直接将新生成的DLL替换原DLL,一个另外语言版的程序就出来了,多方便!
最后对比了一下,一个包含9个相关模块的项目,原来的方法生成出的EXE文件22.9M,剥离资源后的EXE文件1.1~1.4M,DLL文件为21M,再将DLL和EXE用UPX压缩一下,用最大压缩比压缩UPX -9 XXX.EXE, UPX -9 XXX_RES.DLL,最终结果EXE文件:351K,DLL文件690K总共加起来1.01M,约为原来体积的1/23!!!
PS:生成纯资源DLL时,注意在Project配置里面选择Link选项卡,将里面的Project Option填入/NOENTRY参数才行。
|
|