一、序
本文通过描述一些方法来告诉你如何打造一个最小的镜像文件(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运行时环境下中使用需要初始化的全局对象,否则应用程序可能会认为它们是没有初始化的(例如全局对象的构造函数)。
三、一些可继续使用的函数
下列以编译器内在形式存在的函数是可用的。注意尽管这些函数是以内在形式存在的,也可能不是像那些库函数一样是最优化的。这意味着您可编写更高效的替代方案。
l memcmp
l memcpy
l memset
l strcmp
l strcpy
l strlen
l strcat
l strset
四、必需的函数
毫无疑问,C++编译器需要您实现__purecall、new和delete。如果您开启了C++异常处理可能需要更多。我不会教你怎么写那些代码,您只能从两个方案中选其一:
1) 不要使用C++异常处理;
2) 找到那些已实现异常处理的*.obj目标文件,然后链接到您的工程中。
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
__purecall、new和delete简单实现如下:
void* __cdecl operator new ( unsigned int cb )
{
return HeapAlloc( GetProcessHeap(), 0, cb );
}
void __cdecl operator delete ( void* pv )
{
if ( pv )
HeapFree( GetProcessHead(), 0, pv );
}
extern "C" int _cdecl _purecall( void )
{
return 0;
}
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
另外,在添加上面的函数之后,将要为您的应用程序提供一个新的入口点。一个应用程序典型的启动是由操作系统调用函数main/WinMian/DllMain。实际上,那些函数是由C运行时的入口点调用的。下面就是C运行时入口点函数的原型及名称:(它们内部的实现过程和MASM32的入口点代码几乎一样,学过MASM32的一定会知道如何实现下面的函数代码^_^)
EXTERN_C int WINAPI mainCRTStartup();
EXTERN_C int WINAPI WinMainCRTStartup();
EXTERN_C BOOL WINAPI _DllMainCRTStartup(
HINSTANCE hInstDll, // handle to the DLL module
DWORD fdwReason, // reason for calling function
LPVOID lpvReserved, // reserved
);
五、应用程序的结束
通常,在我们的代码执行到离开main(或是WinMain)函数时,应用程序将结束。导致这个原因的是上面的函数缺省实现了:在我们的主函数执行完时调用了操作系统的API函数ExitProcess(MASM32的FANS们会心一笑)。当然如果您坚决不调用ExitProcess也行,那么您的应用程序这时将不会根据您的指示而结束,它会一直等到——当它所有的线程完全地关闭时才会善罢甘休结束。
六、一个实例
自己手工实现一个如C运行时入口点代码一样的入口点函数是非常有用的,例如调试器。如下:
EXTERN_C int WINAPI WinMainCRTStartup()
{
HINSTANCE hInstance = GetModuleHandle(NULL);
LPSTR lpszCmdLine = GetCommandLine();
int r = WinMain(hInstance,NULL,lpszCmdLine,SW_SHOWDEFAULT);
ExitProcess(r);
return r; // this will never be reached.
}
七、编译器开关
下面的那张表格描述了MSVC++6编译器应该设置的确保成功编译的开关:
开关 |
动作 |
说明 |
/GX |
删除 |
这个开关激活了需要涉及到那些需要展开堆栈操作的函数的C++异常处理。 |
/GZ |
删除 |
这个开关激活了一些高级的C运行时调试特性。当这个特性激活后,链接器将会搜索_chkstk的调用。 |
/Oi (第一个是大写的字母o而不是罗马数字O) |
添加 |
添加这个开关可确保编译器内在形式的函数激活。 |
/Zl(大写的Z和小写的L) |
添加 |
通常编译器会嵌入一个“defaultlib”来引用.obj文件内的C运行时,这个开关确保deafultlib不会写入到产生的目标文件(*.obj)内。 |
八、链接器开关
如果编译器已正确配置的话,下面的开关是可选的。但是,如果刚好工程中有一个obj文件漏网的话,C运行时的入口点代码可能会被调用。当然,如查你不放心的话,那么,下面的一个或多个开关你可能需要设置:
开关 |
动作 |
说明 |
/nodefaultlib |
添加 |
如果您在编译器开关中已使用了/Zl,这个开关可不需设。正如编译器的开关说明一样,这个开关的意思是忽略缺省库。如果你使用的第三方函数库或是一些旧的obj文件中仍然包含了一个defaultlib,除非你使用下面的开关,否则链接器将会忽略掉你定义的入口点。 |
/entry:function |
添加 |
如果你希望使用一个非标准的入口点函数名称,那么这个开关你就要使用。如果你需要链接一些第三方的函数库或是目标代码中包含了defaultlib的指示的话,这将是一个不错的想法!否则若给了一半的机会与链接器,只要它能找到它,将会使用C运行时的函数库入口点。 |
/opt:nowin98 |
添加 |
在windows98的平台下,MSVC6链接器将会缺省的为PE文件分配4KB左右的节对齐方式用来优化加载速度的时间。如果激活这个开关,将会对非常小的工程受益,控制PE的大小约在16KB左右。 |
九、更多的MSVC6链接器设置
微软的链接器早在6.0版本之前,产生的所有PE镜像的文件标准节对齐都是512字节。这在6.0开始,为了优化98下的加载速度,对齐将改为4KB左右。但是也为了兼容原因,98加载文件也必须支持旧的对齐方式(但是那么做将会牺牲效率),并且,如果你的目标机器是NT的话,你可以使用旧的512字节不会浪费你一丝效率。在代码中嵌入链接器开关的代码行如下:(第二行的意思,在.C的文件中嵌入一个命令到链接器的选项)
// linker options can be embedded directly in .cpp code thus:
#if defined(_MSC_VER) && _MSC_VER >= 1200
#pragma comment(linker, "/OPT:NOWIN98" )
#endif
原文链接:http://www.mvps.org/user32/nocrt.html
From: http://blog.csdn.net/baofeng/archive/2007/03/11/1526351.aspx