5.1 Win32应用程序:EXE

  Nico Bendlin的MiniDExe很好地演示了不使用任何Delphi例程来实现一个Win32应用程序的方法。对于一个可执行程序.EXE来说,只须满足如下条件,就可以在被Windows系统中执行:
  是一个以.EXE方式生成的格式正确的PE(Portable Executable)文件有一个正确的入口地址并记录在PE格式文件的头部
  编译器会按这样的规则生成文件模块,并将一段入口代码的地址记录在PE格式文件的头部。这段入口代码固定地调用System.pas中的例程_InitExe()。
  因此,可以进一步简化Nico Bendlin的MiniDExe:

//系统初始化单元
unit SysInit;

interface

var
    TlsIndex:Integer=-1;
    TlsLast:Byte;
const
    ptrToNi1:Pointer=nil;
implementation

end.

//系统内核单元
unit System;

interface

procedure _InitExe;

procedure HandleFinally;

procedure _Halt0;
const
    Kerne132=‘kernel32.d11;
    User32='user32.dll';
type
    TGUID=record
    D1:Longword;
    D2:Word;
    D3:Word;
    D4:array[0..7]of Byte;
end;

implementation

procedure ExitProcess (uExitCode:Longword);stdcal1;
external kerne132 name ExitProcess';
procedure _InitExe;
asm
end;

procedure HandleFinally;
asm
end;

procedure _Halt0;
begin
    ExitProcess(0);
end;

end.

//示例程序。目标文件为3584Bytes
program MiniDExe;
function ShowMessageBox(hwnd:Longword;1pText, 1pCaption:PChar;uType:Longword):Integer;stdcal1;external user32 name 'MessageBoxA';
const
    MB_ICONINFORMATION=$00000040;
begin
    ShowMessageBox (0,  Written in pure Delphi!', Hello World!', MB_ICONINFORMATION);
end.

  通过导入外部例程,上面的代码可以调用全部Win32API以及其他的动态链接库资源。可能存在的问题包括:

  •   由于没有处理在入口代码中传入的单元初始化表,因此单元文件的初始化和结束化节是无效的;
  •   由于没有内部例程的支持,因此一些兼容类型的赋值不能进行。例如长字符串与宽字符串的赋值;
  •   由于没有处理_HandleFinally(),因此try-finally-end 语法是无效的(try-except-end是由SysUtils.pas来处理的)。

 

5.1.2初始化例程InitExe()

//code from SysInit, pas
procedure _InitExe(Initrable: Pointer);
begin
    T1sIndex:=0;
    HInstance:=GetModuleHandle(nil);
    Module. Instance:=HInstance;
    Module. CodeInstance:=0;
    Module.DataInstance:=0;
    InitializeModule;
    _StartExe(InitTable,QModule);
end;

  位于Syslnitpas中的初始化例程_InitExe()其实只完成如下极少的功能:

  •   初始化系统全局变量T1sIndex和HInstance;
  •   初始化模块信息记录Module;
  •   调用InitializeModule()来初始化内部模块表;调用_StartExe()。

  其中模块信息记录Module是非常重要的一个系统内部变量,其类型定义如下:

PLibModule=~TLibModule;
TLibModule=record
Next:PLibModule;
Instance:Longword;//模块的实例句柄
CodeInstance:Longword;//模块的代码实例句柄
DataInstance:Longword;//模块的数据实例句柄
ResInstance:Longword;//模块的资源实例句柄
Reserved:Integer;
end;

 

5.1.3内部模块表管理例程

  Delphi提供了一组用于管理模块信息记录和内部模块表的例程:

procedure RegisterModule(LibModule:PLibModule);
procedure UnregisterModule(LibModule:PLibModule);
function FindHInstance(Address:Pointer):Longword;
function FindClassHInstance(ClassType:TClass):Longword;
function FindResourceHInstance(Instance:Longword):Longword;
function LoadResourceModule(ModuleName:PChar;Checkowner:Boolean=True):Longword;
procedure EnumModules(Func:TEnumModuleFunc;Data:Pointer);overload;
procedure EnumResourceModules(Func:TEnumModuleFunc;Data:Pointer);overload;
procedure EnumModules(Func:TEnumModuleFuncLW;Data:Pointer);overload;
procedure EnumResourceModules(Func:TEnumModuleFuncLW;Data:Pointer);overload;
procedure AddModuleUnloadProc(Proc:TModuleUnloadProc);overload;
procedure RemoveModuleUnloadProc(Proc:TModuleUnloadProc);overload;
procedure AddModuleUnloadProc(Proc:TModuleUnloadProcLW):overload;
procedure RemoveModuleUnloadproc (Proc: TModuleUnloadProcLW); overload;
//define in SysInit. pas
procedure InitializeModule;
procedure UninitializeModule;

  需要注意的是,这个内部模块表与操作系统的模块表并不一致。Delphi的内部模块表只记录当前模块加载的包,而操作系统的模块表记录当前进程加载的所有模块。
  初始情况下,.EXE的内部模块表只有一个记录,即当前模块((.EXE)的信息记录。
  SysInit单元的例程InitializeModule()只是简单地调用System单元中的RegisterModule()。例程RegisterModule()则负责把模块放在系统模块列表的头部。


5.1.4.EXE启动例程_StartExe()

  _InitExe()将由启动代码传入的单元初始化表,以及定义在SysInit.pas中的当前模块信息指针传给_StartExe(),从而真正地启动.EXE的初始化过程。

procedure _StartExe(InitTable:PackageInfo;Module:PLibModule);
begin
RaiseExceptionProc := GRaiseException;//初始化异常引发程序的指针
RTLUnwindProc := QRTLUnwind;//初始化异常展开程序的指针
InitContext.InitTable := InitTable;
Initcontext.Initcount := 0;//在InitUnits()中将使用Initcount对初始化过
//的单元进行计数
Initcontext.Module := Module;//.EXE的模块信息
MainInstance := Module.Instance;//模块句柄
IsLibrary := False;
InitUnits;
end;

  在_StartExe()中,主要处理初始化上下文(InitContext)。这个系统内部变量用于记录初始化和结束化中的一些重要信息。结构如下:

type
PInitContext=TInitContext;
TInitContext=record
Outercontext:PInitContext;{当前上下文的备份}
InitTable:PackageInfo;{单元初始化信息表}
InitCount:Integer;{InitTable的长度}
Module:PLibModule;{当前模块信息指针}
DLLSaveEBP:Pointer;{saved regs for DLLs}
DLLSaveEBX:Pointer;{saved regs for DLLs}
DLLSaveESI:Pointer;{ saved regs for DLLs}
DLLSaveEDI:Pointer;{saved regs for DLLsExitProcessTLS:procedure;}
ExitProcessTLS:procedure;{进程TLS退出例程}
DLLInitState:Byte;{0=package,1=DLL shutdown,2=DLLstartup}
end platform;

  该记录中各个域的使用情况如表5-1。

 

5.1.5应用程序的结束化控制

  在Delphi中,有四种情况可以导致一个应用程序(进程)的结束:

  • 程序代码执行完成,执行.DPR的结束语句“END.”正常退出;
  • 应用程序内部调用例程procedure halt();
  • 应用程序内部调用操作系统API;  
  • procedure ExitProcess();
  • 应用程序内部或者其他应用程序调用操作系统API:function TerminateProcess()。

  最后两种方法都是用操作系统API来使进程中止的,这种情况下,Delphi不做任何的处理。前两种方法最终都将调用内部例程_HaltO()。在这个例程中,与.EXE相关的代码有:

procedure Halt0;
var
    P:procedure;
begin
    //检查并执行退出过程
    if InitContext.DLLInitState=0 then //.exe module's DLLInitState=0
    while ExitProc <>nil do
    begin
        eP := ExitProc;
        ExitProc := nil;
        P;
    end;
    //检查并显示系统错误信息,WriteErrorMessage()例程将自动识别是否是控制台输出
    if ErrorAddr <>nil then
    begin
        MakeErrorMessage;
        WriteErrorMessage;
        ErrorAddr := nil;
    end;
    //检查模块的初始化上下文
    while True do
    begin
        //单元结束化
        FinalizeUnits;
        //执行与procedure UninitializeModule()例程相同的操作
        if(Initcontext.DLLInitState <=1)or (ExitCode <>0)then
        begin
            if InitContext.Module<>nil then
                with InitContext do
                begin
                    //从内部模块表中卸载,但不并表明模块从内存中卸载
                    UnregisterModule(Module);
                    //从内存中卸载模块载入的资源模块
                    if(Module.ResInstance <>Module.Instance)and(Module.ResInstance <>0)then
                        FreeLibrary(Module.ResInstance);
                end;
        end;
        //如果当前模块是,EXE,则试图执行ExitProcessProc(),然后退出进程
        if InitContext.OuterContext=nil then
        begin
            if Assigned(ExitProcessProc)then
                ExitProcessProc;
            ExitProcess(ExitCode);
        end;
        nitContext := InitContext.OuterContext^
    end;
end;

  在退出过程ExitProc的检查时,没有使用IF来检测“ExitProc<>Ni1”,而是采用了while循环,这使得可以在ExitProc中再次给ExitProc赋值,从而形成ExitProc的链表。这意味着类似下面的代码能被正常地执行:

var
    oldExitProc:Pointer;
procedure NewExitProc;
begin
    showmessage('test');
    BxitProc:=01dExitProc;//在退出过程中重设ExitProc
end;

procedure ReplaceExitProc;
begin
    OldExitProc:=ExitProc;
    ExitProc:=QNewExitProc;
end;

  应当使用SysUlils,pas中的例程AddExitProc()来安全地操作ExitProc。
  在System,pas单元的内部,还隐藏着一个模块退出过程列表。这个列表中的退出过程,是通过在procedure UnregisterModule()中调用例程NotifyModuleUnload()来执行的。可以使用例程AddModuleUnloadProc()和RemoveModuleUnloadProc()来维护模块的退出过程列表。
  值得注意的是HaltoO中还有一个ExitProcessProc的检查过程。在Delphi看来:ExitProc是表明当前可执行模块(.EXE)的退出过程;ModuleunloadProc是任意类型模块(.EXE、.DLL、.BPL)的退出过程;而ExitProcessProc则是当前进程的退出过程。对于.EXE来说,当前模块与当前进程有非常紧密的关系,但ExitProcessProc与ExitProc却完全无关。

  因此,只有.EXE会使用到ExitProc和ExitProcessProc过程。而ModuleUnloadProc是当前进程中所有模块都可以使用的。如果动态链接库和包不是使用“带运行期库”方式编译的,那么,ExitProc和ExitProcessProc对于这两种模块来说是无意义的。
  只有条件“InitContext.OuterContext=ni1”为真,才表明当前模块是.EXE模块。这种情况下,Halto()才执行操作系统API:ExitProcess()退出进程。
  HaltO()例程的结束化处理中也包括单元结束化。不过需要注意的是:系统是在模块结束化之前调用例程FinalizeUnits()。这意味着在ModuleUnloadProc和ExitProcessProc中无法使用某些类型的全局变量(例如对象),但是在ExitProc中,可以使用所有的东西。

 

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值