1. 程式人生 > >4.3 單元初始化與結束化

4.3 單元初始化與結束化

4.3.1單元初始化與結束化的內部例程

  Delphi會初始化每一個單元,單元結束化則是初始化的逆過程。它們對應於System.,pas中的兩個內部例程:

procedure InitUnits;
procedure FinalizeUnits;

  編譯器會採用深度優先遍歷演算法掃描專案的所有單元,然後生成一個初始化表。編譯器保證每個單元在初始化表中不會重複,從而使得每一個單元只會被初始化一次。

  從執行流程的角度上來說,等到例程InitUnits()被執行時,初始化表已經在記憶體中準備好了——這些是編譯器和作業系統的模組初始化程式所做的工作。

  初始化表的結構如下:

packageUnitEntry =packed record
    Init, FInit: Pointer;
end;

UnitEntryTable = array [0..9999999] of PackageUnitEntry;
PUnitEntryTable = ^UnitEntryTable;
PackageInforable = packed record
    Unitcount: Integer;{ number of entries in UnitInfo array; always>0}
    UnitInfo: PUnitEntryTable;
end
; PackageInfo = ^PackageInforable;

  變數InitContext的域InitTab1e指向初始化表。
  真正的初始化過程非常簡單,例程InitUnits()核心部分的實現程式碼如下:

Drocedure InitUnits;
var
    Count,I:Integer;
    Table:PUnitEntryTable;
    P:Pointer;
//...
    while I < count do//0..Unitcount-1,列舉全部的單元初始化記錄
    begin
        P:=Table[I].Init;
        Inc(I);
        Initcontext.Initcount:
=I;//記錄初始化過的單元數量 if Assigned(P)then TPrOc(P)();//把初始化程式碼入口作為一個無引數過程呼叫 end; //... end;

  Table^[I].Init即是單元檔案initialization節的程式碼在記憶體中的入口地址。同樣的,Table^[I].FInit即是finalization節的程式碼入口地址。在結束化例程FinalizeUnits()中,直接使用在初始化過程中記錄的InitContext.InitCount值,列舉並呼叫Table^[I].FInit。
  如果InitContext.InitCount=0,則呼叫procedure FinalizeUnits()時不會有任何實際的操作發生。

  單元初始化與結束化分別在模組調入和解除安裝時發生。如果單元沒有initialization節和finalization節,那麼雖然在初始化表中有相應的單元記錄,但它的單元入口地址為Nil。


4.3.2其他初始化例程

  在單元初始化的關鍵字initialization或begin之中,在使用者程式碼被執行到之前,編譯器通常會填入一些呼叫初始化例程的程式碼。在這些例程包括:

procedure _InitResStrings;
procedure _InitResStringImports;
procedure _Init Imports;
procedure _InitwideStrings;

  其中,在單元中使用了寬字串型別的常量或有初值變數的情況下,編譯器會在初始化程式碼之後填入程式碼來呼叫InitwideStrings()例程。它的作用是將放在CODE節中的寬字串複製到堆,並將它在堆中的地址賦給原始碼中宣告它的變數或者常量。由於寬字串與長字串的儲存方式和行為是一致的,因此可以參見第6章中“資料節與程式碼節(CODE)”。寬字串沒有使用引用計數機制,因此它不能像長字串那樣在第一次賦值引用時才檢測字串的有效性並決定是否賦值。所以使用寬字串型別的常量或有初值變數時,總是要導致單元初始化時呼叫例程_InitwideStrings(),而使用長字串時卻不必。
  其他三個例程的相關細節,請參見第10章中“資源字串”。