登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

BCB-DG's Blog

...

 
 
 

日志

 
 

使用VC創建最小EXE  

2008-11-25 13:55:32|  分类: Delphi |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

使用VC++的编译器创建最小的镜像文件(DLL/EXE)[译]

一、序

本文通过描述一些方法来告诉你如何打造一个最小的镜像文件(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

Introduction
This document describes some methods on how to minimize the size of images - be they dll's or exe's. The methods include elimination of the c-runtime stubs, and compiler & linker settings. The compiler and linker I concentrate on is MSVC 6 - most of the tips apply also to MSVC 5. While many of the concepts presented here apply to other development enviroments the actual command line parameters and #pragmas will obviously be different - check your enviroments documentation.

Doing without the C-Runtime
The c-runtime is a library of functions that do stuff for you the programmer. These functions are platform independent, and act as an abstraction layer between your program and the operating system. This has some drawbacks:

Bugs. While most c-runtimes are well tested, each layer you add to an application introduces the possibility of more bugs
It takes up space. You application must either include the code for the c-runtime functions it uses, or use a c0runtime in a shared dll.
The abstraction layer is not necessairly simpler than the OS - merely platform independent. Many tasks can be accomplished in less lines of code using raw OS API calls than using the c-runtime functions
Using the c-runtime also hides from you many of the capabilities of the OS that have the potential to make your program simpler, and drastically increase performance.
If, like me, you find these to be unacceptable tradeoffs, there are a couple of things you must do to eliminate the c-runtime completely from your application

Stop using c-runtime functions. Some functions you can use (the string, and memory manipulation functions) as they are available in intrinsic form, or the OS API provides direct equilavents. More work might be required to replace other runtime calls, especially where the OS does not provide an equilavent service.
Implement a couple of runtime functions that the C++ compiler assumes exists. new, delete and _purecall may be necessary for c++ modules. You will need to provide the application with an entrypoint.
Some compiler switches may need to be changed to make the generated object files link properly in a no-crt enviroment.
Change the linker settings to prevent the linker from including the runtime libraries
NB: Note that the c-runtime startup code is responsibile for initializing all global scope objects. Do not use global scope objects that require initialization in a no-runtime application as they will be in an undefined state (or - hopefully - prevent the application from linking successfully).

--------------------------------------------------------------------------------

Functions you can use
The following functions are available in intrinsic form by the compiler. Beware though that the instrinsic forms of these may not be as optimised as the library form.

memcmp
memcpy
memset
strcmp
strcpy
strlen
strcat
strset

--------------------------------------------------------------------------------

Required Functions
The c++ compiler absolutely requires that you implement __purecall, new and delete. If C++ exception ahndling is enabled more are required that I have no clue how to write. Either don't use C++ exceptions, or find the .obj files that implement these functions and link them into your project.

A simple implementation of these functions:

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;
}

In addition to the C++ functions listed above you will need to provide your application with a new entry point. Typically an application starts at a functions called main, WinMain, or DllMain. These functions are actually called from the c-runtimes real entry point. Here are the prototypes and real names of an applications entry point:

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
);

Application Termination
Normally, when the code path leaves the main( or WinMain() functions, the application exits. This is because the default implementation of the above functions calls ExitProcess. If you do not call ExitProcess then your application will not close untill all threads have exited cleanly.

Example Implementation
As a debugging aid it is usefull to implement the entrypoint function in a manner similar to the c-runtimes.

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.
}

--------------------------------------------------------------------------------

Compiler Switches
The following table describes the compiler switches that should be set to ensure successful compilation using MSVC++ 6

Switch Action Description
/GX delete This switch enables C++ exception handling which requires a number of functions related to unwinding the stack.
/GZ delete This switch enables some advanced debugging features. With these features enabled the linker will look for a function called _chkstk.
/Oi add Add this switch to ensure that intrinsic functions are enabled.
/Zl add Usually the compiler embeds a "defaultlib" refrence to the c-runtime in the .obj file. This switch (do not confuse with the /ZI switch) ensures that defaultlib entries are not written to the generated OBJs.

--------------------------------------------------------------------------------

Linker Switches
The following switches are optional if the compiler is set correctly. However, if just one badly compiled obj file is in the project then the c-runtimes standard entry point may be invoked. If you suspect this is the case you might want to set one, or both of these switches.

Switch Action Description
/nodefaultlib add This switch is not really necessary if you compile with /Zl, as there should be no default libraries to ignore. If however you use a 3rd party library, or any old obj files that do include a defaultlib entry, then the linker will silently ignore your custom entry point, unless you use the following switch
/entry:function add Use this if you wish to use a non-standard name for your entry point. This is a good idea if you are linking to a 3rd party library or object code that contains defaultlib directives, as the linker, if given half a chance, will use the runtime libraries entry point if it can find it.
/opt:nowin98 add The MSVC 6 linker defaults to a 4Kb section padding in PE files as an optimizaton to speed load times on Windows 98. Very small projects will benefit with a file size saving of about 16Kb.

--------------------------------------------------------------------------------

More MSVC 6 Linker settings
Microsofts linkers prior to 6.00 produced PE iamges with file level section alignments of 512 bytes. Link 6.00 aligns sections in a file on 4Kb boundries to optimise loading of the image under 98. For compatability reasons 98 must load files with the old alignment (but will do so with a performance hit), and, if you are targetting NT you can use the old 512 byte padding without any performance deficit at all. The linker switch you must add to the link line is: (the second line details how the command might be embedded as a linker option in a .C file.

// linker options can be embedded directly in .cpp code thus:
#if defined(_MSC_VER) && _MSC_VER >= 1200
#pragma comment(linker, "/OPT:NOWIN98" )
#endif

  评论这张
 
阅读(1105)| 评论(0)

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018