第1章 最小化Delphi内核

  Delphi编译的,EXE最小到底能有多少个字节?

  很多人会用最简单的方式做一个“Hello World!”程序来给出答案。一一在Delphi7中,会是15K字节。如果使用对话框,则马上会变成382K字节。
  这里不去讨论为什么会有这种变化。因为我已经迫不及待地要向你介绍Nico Bendlin和他写的MiniDExe了。
  MiniDExe的纪录是3584字节!

1.1MiniDExe如何实现内核最小化

  Delphi的单元隐含地引用了Systempas单元,那么System.pas单元自身如何被编译成.DCU呢?查看$(SourceRTLImakefile 文件可以看到,System.pas是通过这样的命令行来被编译的:
  $(DCC) sys\system -y -s &(RTLBDBBUG) -n$(LTB)
  这里最重要的就是隐含的编译选项:“-y”。它仅用于编译System.pas等被系统保留名字的内核单,因此在DCC32的命令行帮助中是根本没有的。绝大多数资料中也没有介绍它。
  MiniDExe 就是通过重写、重编译 System.pas和Sys/nit.pas两个内核单元,全面摒弃了内核中的无关代码和多余变量、常量等,从而实现了最小化的。

1.1.1MiniDExe中的System.pas单元

 

unit System;

interface

procedure _InitExe;
procedure _HandleFinally;
procedure _halt0;

type
  TGUID = record
    D1: LongWord;
    D2: Word;
    D3: Word;
    D4: array[0..7] of Byte;
  end;

const
  Kernel32 = 'kernel32.dll';
  User32 = 'user32.dll';

var
  HKernel32: LongWord;
  HUser32: LongWord;

type
  TFNExitProcess = procedure(uExitCode: LongWord); stdcall;

var
  ExitCode: LongWord;
  ExitProcess: TFNExitProcess;

function LoadLibraryA(lpLibFileName: PAnsiChar): LongWord; stdcall;
function LoadLibraryW(lpLibFileName: PWideChar): LongWord; stdcall;
function LoadLibrary(lpLibFileName: PChar): LongWord; stdcall;
function GetProcAddress(hModule: LongWord; lpProcName: PChar): Pointer; stdcall;

implementation

function LoadLibraryA; external kernel32 name 'LoadLibraryA';
function LoadLibraryW; external kernel32 name 'LoadLibraryW';
function LoadLibrary; external kernel32 name 'LoadLibraryA';
function GetProcAddress; external kernel32 name 'GetProcAddress';

procedure _InitExe;
const
  PExitProcess: PChar = 'ExitProcess';
  PKernelModul: PChar = Kernel32;
  PUserModul: PChar = User32;
asm
  PUSH PExitProcess
  PUSH PUserModul
  CALL LoadLibrary
  MOV  HUser32, EAX
  PUSH PKernelModul
  CALL LoadLibrary
  MOV  HKernel32, EAX
  PUSH EAX
  CALL GetProcAddress 
  MOV  ExitProcess, EAX
end;

procedure _HandleFinally;
asm
end;

procedure _halt0;
asm
  PUSH ExitCode
  CALL ExitProcess
end;

end.

 

  其中_InitExe()是编译一个可执行文件时需要嵌入的初始化代码,_HaltO()则是可执行文件退出时的出口代码。显然,这是操作系统调入一个可执行文件必须的入口和出口代码。
  _HandleFinally()则是编译Sys/nit,pas时所必需的,它用于挂接异常处理过程。
  编译器还须要在System.pas中查找与COM接口相关的数据类型TCUID。这使得可以在不改写编译器的情况下与当前的COM版本保持一致。
  事实上,有了上述声明的部分,System.pas已经可以用于编译.EXE可执行文件了。但是,为了让它能够做一点点工作——显示一个对话框—Nico还必须继续为System.pas添上一些东西。
  接下来定义了全局变量HKerne132与HUser32,用于保存OS为当前进程映射的Kernel32.dll和User32.dl/模块的句柄。这两个变量在_InitExec()例程中填入实际的值。全局常量Kerne132和User32仅用于定义上述两个模块的名字。
  最后声明的是四个外部例程:

function LoadLibraryA(lpLibFileName: PAnsiChar): LongWord; stdcall;
function LoadLibraryW(lpLibFileName: PWideChar): LongWord; stdcall;
function LoadLibrary(lpLibFileName: PChar): LongWord; stdcall;
function GetProcAddress(hModule: LongWord; lpProcName: PChar): Pointer; stdcall;

  它们都是在Kerne132模块中由操作系统实现的。如果用户的执行程序根本无须调用Kerne132与User32这两个模块中的任何例程,则上面的这些声明全都是可以省略的。

1.1.2MiniDExe中的Syslnit.pas单元

unit SysInit;

interface

var
  TlsIndex: LongWord;

implementation

end.

  这个单元中只剩下T1sIndex这个全局变量了,它用于保存一个线程TLS时隙的索引值。如果模块不定义线程局部变量,则T1sIndex不是必需的一—尽管会导致编译器的错误警告。

1.1.3MiniDExe中的项目文件MiniDExe.dpr

{$A+,B-,C-,D-,E-,F-,G+,H+,I-,J-,K-,L-,M-,N+,O+,P+,Q-,R-,S-,T-,U-,V+,W-,X+,Y-,Z1}
{$MINSTACKSIZE $00004000}
{$MAXSTACKSIZE $00100000}
{$IMAGEBASE $00400000}
{$APPTYPE GUI}

program MiniDExe;

type
  TFNMessageBox = function(hWnd: LongWord; lpText, lpCaption: PChar;
    uType: LongWord): Integer; stdcall;

const
  MB_ICONINFORMATION = $00000040;

begin
  TFNMessageBox(GetProcAddress(HUser32, 'MessageBoxA'))
    (0, 'Written in pure Delphi!', 'Hello World!', MB_ICONINFORMATION);
end.

  这个项目异常简单,是一个简单的Win32API的声明和调用,这样做是为了避免在System.pas中加入Writeln()这样的输出例程代码。

1.2一些其他的内核优化?

  Delphi提供了全部的内核源码和VCL源码,这为一些个人或第三方组织优化它们提供了方便,其中最有名的两个组织是KOL和“High Performance Code(optimalcode.com)”。一些常见的第三方的内核优化代码有:

  • 内部例程优化:optimalcode.com的FastMath,Andrew N.Driazgov(andrewdr@newmail.ru)的QStrings,以及droopyeyes.com的FastString。
  • 内存管理器优化:在QStrings库中所包含的QMem,optimalcode.com发布的HPMM,以及用于替换ShareMem的单元 FastShareMem与ShareMemRep。
  • 异常管理器优化:KOL发布的异常处理单元err.pas。
  • 系统单元的整体优化:KOL发布的System units replacement for D4-D7和Assarbad's system unit replace。

  KOL目前还在维护着各个Delphi版本的内核单元替换,并且已经对SysUtils.pas、Classes.pas、Variants.pas和Math.pas等单元进行了优化。而optimalcode.com站点如今已经关闭了。
  也有一些非常优秀的、以个人名义维护的代码优化项目。例如Assarbad就发布过一个基于Delphi4优化的内核单元(但是更确切地说,Assarbad并不注重对System单元的优化,他所关注的其实只是简化)。由Dennis Christensen负责的项目Fast Code Project也非常有名,目前的网址是http://dennishomepage.gugs-cats.dk/FastCodeProject.htm,它甚至替代了optimalcode.com的位置。
  此外还有一些散见于网络的内核例程的优化代码。例如这样的一个例程:

//same of function Math. Sing()
function Sign(n: Integer): Integer;
//returns -1 for n<0
//            0 for n = 0
//            1 for n > 0
asm 
    or eax, eax 
    lahf 
    movzx eax, ah
    shr eax,6sub
    eax,1
    neg eax 
end;                

1.3为什么要研究最小化内核

  回到MiniDExe。

  起来没有必要把System.pas简化到如此之小—因为类似于MiniDExe中那样简化的内核儿乎付么也做不了。但是,如果要从源代码一级分析Delphi是如何将对象、组件库、接口等框架技术包裹在应用程序之上的,从一个最小化的内核开始分析,是最方使、最清晰不过的了。
  System.pas展示了Delphi如何将自己的应用挂在操作系统中。这一点也很重要。因为,以此为原点,将更易窥见Delphi是如何实现多线程、内存管理、文件系统以及其他各种数据结构的。
  自下面的章节开始,我将在这个内核之上,逐层包装代码,从而将Delphi内核的每个实现细节具体展现在您的面前。

 

转载于:https://www.cnblogs.com/YiShen/p/9874764.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值