版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://dreamerate.blogbus.com/logs/4747515.html
一、序
本文通过描述一些方法来告诉你如何打造一个最小的镜像文件(DLL/EXE)。这些方法包括: 1) 剔除C运行时Stub; 2) 编译器(cl.exe)和链接器(link.exe)的一些参数设置。
如题,这里所指的编译器及链接器我主要集中在MSVC6上(这些方法通常也适用于MSVC5)。当一些出现在这里的观念在应用于其它开发环境中的命令行参数及#pragmas出现明显差异时,请参考您的环境文档。
二、抛开C运行时(C-Runtime)
C运行时是一个专为程序员准备的函数库。这些函数是独立于平台的,并且它担当了一个位于你程序与操作系统之间的抽像层角色。虽然这些函数都是汇编语言所编,但它在某一方面,会为我们的程序带来一些负面影响:
1) BUG。尽管大多数的C运行时函数都测试的很好,但是也有一些在您引入这些函数到您程序中时,可能会带来更多的Bug; 2) 它会占用程序空间。为了使用C运行时函数,您的应用程序必须包含C运行时的代码,或是根据你的指示仅调用一个共享的DLL。一个通常的动作是,编译器在编译代码时会把C运行时函数的代码塞到你的程序中(这就是C运行时Stubs); 3) 这个抽像层并不是比操作系统提供的操作更简单,它仅仅是能跨平台而已。其实,多数的任务都能直接使用操作系统层提供的API以更少的代码量来完美的演绎完成; 4) 使用C运行时也同样会牺牲由操作系统带来的更多的功能,牺牲创建一个应用程序的更简洁、更多的可提升性能的潜力。
如你和我一样,无法接受如上的折衷,那么就应该做几个完全地去除C运行时的工作。如下:
1) 停止不再使用C运行时函数。但是,还有一些以内部形式存在的函数您可继续使用(字符串及内存操作)。当然,您也可以直接地使用操作系统提供的等价的API函数。但是你要做的,更多的工作是替换那些操作系统没有提供等价的服务的函数; 2) 实现几个C/C++编译器假定存在的C运行时函数。如C++模块可能是必需的new、delete及_purecall操作。与此同时,也要为程序提供一个入口函数(EntryPoint)。什么是入口函数?入口函数就是操作系统在加载进程后,第一个执行您程序代码的入口点。在我们还没有去掉C运行时的时候,那个操作是由它来自动定位我们提供的main(console)、WinMain(windows)的; 3) 为了生成不依赖于C运行时环境的目标文件,您的一些编译器的开关可能需要改变; 4) 为了防止链接器把C运行时的函数库包含进来,您可能需要改变链接器的一些设置; 5) 注意:记住C运行时的启动代码主要负责初始化全局对象。不要在已脱离C运行时环境下中使用需要初始化的全局对象,否则应用程序可能会把它当作是没有定义的对象。
三、一些可继续使用的函数
下列以编译器内在形式存在的函数是可用的。注意尽管这些函数是以内在形式存在的,也可能不是像那些库函数一样是最优化的。这意味着您可编写更高效的替代方案。
○ memcmp ○ memcpy ○ memset ○ strcmp ○ strcpy ○ strlen ○ strcat ○ strset
四、需要构造的函数
毫无疑问,C++编译器需要您实现__purecall、new和delete。如果您开启了C++异常处理可能需要更多,我不会教你怎么写那些代码,您只能从两个方案中选其一: 1) 不要使用C++异常处理; 2) 找到那些已实现异常处理的*.obj目标文件,然后链接到您的工程中。
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- void* __cdecl operator new ( unsigned int cb ) void __cdecl operator delete ( void* pv ) extern "C" int _cdecl _purecall( void ) 另外,在添加上面的函数之后,将要为您的应用程序提供一个新的入口点。一个应用程序典型的启动是由操作系统调用函数main/WinMian/DllMain。实际上,那些函数是由C运行时的入口点调用的。下面就是C运行时入口点函数的原型及名称:(它们内部的实现过程和MASM32的入口点代码几乎一样,学过MASM32的一定会知道如何实现下面的函数代码^_^) EXTERN_C int WINAPI mainCRTStartup();
通常,在我们的代码执行到离开main(或是WinMain)函数时,应用程序将结束。导致这个原因的是上面的函数缺省实现了:在我们的主函数执行完时调用了操作系统的API函数ExitProcess(MASM32的FANS们会心一笑)。当然如果您坚决不调用ExitProcess也行,那么您的应用程序这时将不会根据您的指示而结束,它会一直等到——当它所有的线程完全地关闭时才会善罢甘休结束。
自己手工实现一个如C运行时入口点代码一样的入口点函数是非常有用的,例如调试器。如下: EXTERN_C int WINAPI WinMainCRTStartup()
下面的那张表格描述了MSVC++6编译器应该设置的确保成功编译的开关:
八、链接器开关 如果编译器已正确配置的话,下面的开关是可选的。但是,如果刚好工程中有一个obj文件漏网的话,C运行时的入口点代码可能会被调用。当然,如查你不放心的话,那么,下面的一个或多个开关你可能需要设置:
九、更多的MSVC6链接器设置 微软的链接器早在6.0版本之前,产生的所有PE镜像的文件标准节对齐都是512字节。这在6.0开始,为了优化98下的加载速度,对齐将改为4KB左右。但是也为了兼容原因,98加载文件也必须支持旧的对齐方式(但是那么做将会牺牲效率),并且,如果你的目标机器是NT的话,你可以使用旧的512字节不会浪费你一丝效率。在代码中嵌入链接器开关的代码行如下:(第二行的意思,在.C的文件中嵌入一个命令到链接器的选项) // linker options can be embedded directly in .cpp code thus:
|