Delphi中使用比較少的一些語法


本文是為了加強記憶而寫,這里寫的大多數內容都是在編程的日常工作中使用頻率不高的東西,但是又十分重要。

---Murphy

1,構造和析構函數:

a,構造函數:

一般基於TComponent組件的派生類,都應該使用overload關鍵字進行繼承,Delphi中的對象沒有什么復合的概念,在設計時,從簡便的角度出發

一般都設計為耦合性較強,但是使用簡單的派生類即可。構造函數不是必寫的,除非“復合”這樣的對象實現,當省略構造函數時,會由其父類來實現

新對象的建立。下面是幾個常用的寫法:

constructor TfmBaseScreen.Create(AOwner: TComponent);  //由於參數並沒有變化,所以這里在聲明的時候可以加override而不是overload
begin
  inherited; // Create(AOwner) ; //這里的Create默認是可以省略的。
  if AOwner is TPanel then
  Self.Width :=TPanel(AOwner).Width;
end;

//-------------------------

constructor TfmHuaPianScan.Create(AOwner: TComponent; AQuery: TADOQuery;
  AZDCode: string; AMvOutBillCode : string; MoveOutQuery : TADOQuery);   //這個聲明時需要增加overload說明
begin
  inherited Create(AOwner);
  FQuery := AQuery; 這里實現的僅僅是私有變量的初始化,其實更多的應用是派生類成員對象的初始化,並需要在析構函數中進行釋放。
  FZDCode := AZDCode;
  FMvOutBillCode := AMvOutBillCode;
  FMoveOutQuery := MoveOutQuery;
end;

//-------------------------

Constructor TDllLoader.Create(strDLLName : String);  //這是一個基於TObject的類,由於沒有任何成員,所以這里Create什么關鍵字都不需要帶。
Begin

   FhDLL := LoadLibrary(strDLLName);
   ASSERT(FhDLL <> 0);
End;

b,析構函數

析構函數的使用比構造函數要嚴格一些,一定要在任何時候都使用override,以保證父類內存空間可以正確的釋放。

下面是幾個析構函數的寫法:

Destructor TDllLoader.Destroy();   //雖然函數體內沒有inherited但是聲明時一定要加上override.

Begin

    If FhDLL <> 0 then 

    begin

        FreeLibrary(FhDLL); 

    End;

end

 

//-----------------------------------------

destructor TfmTest.Destroy;  //窗體類的析構,一定也要在聲明的時候增加override.
begin
    temp.Free;   //成員的釋放應該都安排在inherited之前。
    inherited;
end;

 c,窗體的創建和摧毀事件與構造析構的關系

當然,在窗體類的構造和析構當中,有很多成員管理方式,可以直接通過消息-->事件模式來實現,並不需要引用類及對象設計模式。

直接調用窗體的OnCreate和OnDestroy就可以完成很多事情。

我們先來看看窗體構造方法的一段代碼:

constructor TCustomForm.Create(AOwner: TComponent);
begin
    GlobalNameSpace.BeginWrite;
    try
      CreateNew(AOwner);
      if (ClassType <> TForm) and not (csDesigning in ComponentState) then
      begin
        Include(FFormState, fsCreating);
        try
          if not InitInheritedComponent(Self, TForm) then
            raise EResNotFound.CreateFmt(SResNotFound, [ClassName]);
        finally
           Exclude(FFormState, fsCreating);
        end;
        if OldCreateOrder then DoCreate;
      end;
    finally
         GlobalNameSpace.EndWrite;
    end;
end;

procedure TCustomForm.AfterConstruction;
begin
    if not OldCreateOrder then DoCreate;
    if fsActivated in FFormState then
    begin
       Activate;
       Exclude(FFormState, fsActivated);
    end;
end;

從以上的VCL源碼可以看到Create構造方法和FormCreate 事件執行順序是可以調配的,當OldCreateOrder為True時,是執行Create時調用OnCreate事件的;

而OldCreateOrder為False時,是在窗體AfterConstruction中調用OnCreate事件的。

而OldCreateOrder屬性值默認就是False,也就是默認在AfterConstruction中調用。或者說,繼承窗口的時候,默認是先執行Create代碼段,再執行FormCreate代碼段。

 

 我們再來看看Destroy和FormDestroy的執行順序,如下是VCL源碼:

procedure TCustomForm.BeforeDestruction;
begin
      GlobalNameSpace.BeginWrite;
      Destroying;
      Screen.FSaveFocusedList.Remove(Self);
      RemoveFixupReferences(Self, '');
      if FOleForm <> nil then FOleForm.OnDestroy;
      if FormStyle <> fsMDIChild then Hide;
      if not OldCreateOrder then DoDestroy;
end;

destructor TCustomForm.Destroy;
begin
     if not (csDestroying in ComponentState) then GlobalNameSpace.BeginWrite;
     try
         if OldCreateOrder then DoDestroy;
         MergeMenu(False);
         if HandleAllocated then DestroyWindowHandle;
         Screen.RemoveForm(Self); 
         FCanvas.Free;
         FIcon.Free;
         FreeAndNil(FActionLists);
         inherited Destroy;
     finally
         GlobalNameSpace.EndWrite;
     end;
end;

同樣的析構方法跟FormDestroy事件的執行順序也是可以調配的,當OldCreateOrder為True時,系統是通過析構Destroy直接調用OnDestroy事件的;

而當OldCreateOrder為False時,系統是通過BeforeDestruction來調用OnDestroy事件的。

同Create的一樣,OldCreateOrder默認值是False,所以默認就是在BeforeDestruction中調用,也就是說,繼承窗體中,先執行OnDestroy事件代碼部分,再執行析構。

 

2,類方法及引用類

a,類方法

實現模式很簡單,只需要在定義過程或函數時,增加一個class關鍵字就可以了,它的地位跟C語言中的靜態方法的地位是一樣的。它所處理的信息都是與類相關,

不能引用對象成員。

下面是TObject中的一個標准的類方法,用來返回一個類名:

class function TObject.ClassName: ShortString;

b,類中類(引用類)

普通類的語法規則是:

類名=class(父類名)

    成員描述;

end;

而類中類的語法是:

類名=Class of 父類名

    成員描述;

end;      

類中類的父類似乎是其一個對象成員,它可以直接調用其父類的類方法,而類中類定義出的對象,其實是一個類。

我們可以在用類中類定義的對象中,使用構造和析構方法,構造方法可以調用對象的Create也可以調用其類中類的Create,

但是,析構函數不能使用類中類的類方法,只能使用其創建對象的析構。這種方法常用於將類作為參數的使用上,方便

在某一個方法之中,對其不明類(但明其祖類)的對象,進行類方法調用。

下面是一個標准使用的例子,這是Vcl中TApplication所帶的方法,雖然名稱是創建Form,其實對於任何基於TComponent的

對象都是可以創建的,其實現手法就是類中類:

procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
var
  Instance: TComponent;
begin
  // Set flag that TCustomForm constructor can read, so it knows if it's being
  // created as a main form or not (required when MainFormOnTaskbar is True)
  FCreatingMainForm := (FMainForm = nil) and InstanceClass.InheritsFrom(TForm);
  Instance := nil;
  try
{$IF DEFINED(CLR)}
    Instance := InstanceClass.Create(Self);
    Reference := Instance;
{$ELSE}
    Instance := TComponent(InstanceClass.NewInstance);  //這里是直接用的TComponent對象賦值一個派生類空間,面向對象是允許的
    TComponent(Reference) := Instance;  //這里的Reference是一個實參,這句用於指向創建的對象
    try
      Instance.Create(Self);   //NewInstance創建的空間只是分配一片空白區域,這里將其頭部區域格式化成TComponent
    except
      TComponent(Reference) := nil;
      raise;
    end;
{$IFEND}

    if (FMainForm = nil) and (Instance is TForm) then  //當系統中還沒有明確主窗口時,而創建的對象是窗體對象,則在這里將其自動設置為主窗體。
    begin
      TForm(Instance).HandleNeeded;
      FMainForm := TForm(Instance);
      if MainFormOnTaskBar then
        SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) or WS_EX_NOACTIVATE);
      ChangeAppWindow(Handle, not MainFormOnTaskBar, not MainFormOnTaskBar);
    end;
  finally
    if (FMainForm = nil) and (Instance is TForm) then
      TForm(Instance).FCreatingMainForm := False;
  end;
end;

類方法中的Self,指的是類本身,而不是對象,所以沒辦法象普通Self那樣使用。

    一個類必須創建具有其對象狀態的成員,才是有意義的類。在我上一家公司中,見到有人賣弄高深的把全局方法都建立在一個類中,用類實現。

這樣的類,既沒有其多樣的對象,在方法實現時,甚至一點與類相關的東西都沒有,還不如建立一個公共方法單元。

    一個標准的類,也不能僅僅是對一堆封裝成員的Get和Set操作,應該在成員的邏輯處理中,簡化其應有的方法,又要保留必要的變化細節。

否則的話,還不如定義一個結構體。

 

3,構造自己的異常,及合理應用

EFileOpenFailed = class(Exception)

    public procedure Warning(); virtual; abstract;  //這里是純虛函數的定義,在使用時,還必須再定義一層子異常類。

end;

所有異常類,都應該以E開頭,並且從Exception繼承。並且,在觸發異常時,應該從繼承樹的枝節點寫起,

根節點的異常觸發應該寫到最后。異常的調用,可以在except包含段中以

[on 異常實例:異常類定義 do]的模式進行調用。再或者,可以用[raise 異常類名.Create(構造參數列表);]來直接拋出異常 

    try

         SimulateError(Button)    

    except

         on E : EFileOpenFailed do

            E.Warning();

         on E : Exception do

            ShowMessage(E.Message);    

    end;

 

4,起源於萬物之王的消息機制

Delphi跟Windows消息是一脈相承的,或者說是擴展消息。Windows消息到了Delphi被重新轉換成固定的結構體(Record),

然后,由Delphi的對象進行分發處理,再由消息函數進行接收運行其對應功能。

對於Delphi,可以說所有的對象都具有消息接收功能,這個需要追溯到對象的最初定義TObject:

  TObject = class
  public
    constructor Create;             //構造函數
    procedure Free;                  //用於釋放對象
    class function InitInstance(Instance: Pointer): TObject;  //使用固定地址,格式化一片實例數據,並返回其對象指針。但是這個並不能分配內存,只操作已分配的內存
    procedure CleanupInstance; //清空對象本身所指向的區域內存數據,但是並不釋放內存
    function ClassType: TClass; inline;  //返回類類型,這里的inline相當於注釋,表示該功能向下兼容,並且可以減少編譯文件的大小。
    class function ClassName: string;  //返回類名
    class function ClassNameIs(const Name: string): Boolean; //用類名判斷類
    class function ClassParent: TClass; //返回父類,如果強制返回一個基於TObject的父類,則會報錯。
    class function ClassInfo: Pointer; inline; //返回類信息的指針
    class function InstanceSize: Longint; inline; //返回基於當前類的實例使用的空間大小
    class function InheritsFrom(AClass: TClass): Boolean; //判斷是否繼承於特定類
    class function MethodAddress(const Name: ShortString): Pointer; overload; //根據類所含方法的名稱,返回對應指針。
    class function MethodAddress(const Name: string): Pointer; overload; //同上
    class function MethodName(Address: Pointer): string; //根據方法的地址指針,返回方法名
    function FieldAddress(const Name: ShortString): Pointer; overload; //根據屬性的名稱,返回屬性的地址。
    function FieldAddress(const Name: string): Pointer; overload; //同上
    function GetInterface(const IID: TGUID; out Obj): Boolean; //獲得一個接口地址,返回執行成功與否
    class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry; //直接返回一個接口地址
    class function GetInterfaceTable: PInterfaceTable;  //獲取接口表地址
    class function UnitName: string; //類定義所在的單元文件名
    function Equals(Obj: TObject): Boolean; virtual;  //對象比較
    function GetHashCode: Integer; virtual; //得到哈希代碼
    function ToString: string; virtual;  //等同ClassName,可多態改動
    function SafeCallException(ExceptObject: TObject;
      ExceptAddr: Pointer): HResult; virtual;  //以安全模式調用異常
    procedure AfterConstruction; virtual;  //構造后處理
    procedure BeforeDestruction; virtual; //析構前預處理
    procedure Dispatch(var Message); virtual; //消息分發
    procedure DefaultHandler(var Message); virtual; //默認接收方法,可以接收所有編號的消息。
    class function NewInstance: TObject; virtual;  //開辟一個新對象空間,其地址是由系統自動分配的
    procedure FreeInstance; virtual;  //釋放實例空間
    destructor Destroy; virtual;  //析構
  end;       

 消息使用的三個步驟,

<1>,給具有消息結構體的消息變量賦值。

<2>,構造一個具有處理消息方法的類。處理消息方法的書寫格式:方法名(var 消息參數); message 消息號;

<3>,用一個消息處理類的實例,用Dispatch或者DefaultHandler方法對消息進行分發和處理。這里的分發是指分發到類內部過程。

例程:

  TMyMsg = record  //消息定義,這里是一種最簡單的消息結構體。
    Msg : Cardinal;
    MsgText : ShortString;
  end;

  TMsgAccepter = class  //接收消息類定義,這里省略了消息方法闡述部分
  private
    procedure AcceptMsg2000(var msg : TMyMsg); message 2000;  //這里定義觸發方式及觸發消息號
    procedure AcceptMsg2002(var msg : TMyMsg); message 2002;
  public procedure
    DefaultHandler(var Message); override;  //這里用於接收其他編號的消息
  end;

  //調用過程

var
  MsgAccept : TMsgAccepter;
  Msg :TMyMsg;
begin
  MsgAccept := TMsgAccepter.Create;
  try
    Msg.Msg:=2000;
    Msg.MsgText:='消息測試文本';
    MsgAccept.Dispatch(Msg);   //TObject自帶的方法
  finally
    MsgAccept.Free;
  end;
end;

VCL的事件定義都是如此,先在VCL控件中定義出接收特定消息的方法,再由事件屬性(這里的屬性是一個指針),去指定相關方法,

並在事件(TNotifyEvent)子類中形成用戶事件區。

注意:以WM_開頭的消息,是Windows定義的消息;以CM_開頭的消息,是VCL庫自定義的消息。

 

 5,行標的定義Label.

面向對象的編程中並不建議使用行表和goto跳轉語句,所以在通常的Delphi代碼中很少見,但是並不是說Delphi不具備goto跳轉,eg.

var
 a,b: Integer;
label
 X,Y;
begin
 if a > b then
  goto X
 else
  goto Y;
X:
 WriteLn('a>b');
Y:
 WriteLn('b>a');
end;

 

6,一個幾乎脫離VCL的程序框架:

program Console;
{$APPTYPE CONSOLE} //這里表示支持對用戶控制台的操作
//uses SysUtils;
var
  str:string;
begin
  writeln('您好,這是一個示范程序,請輸入一行文字:');
  readln(str);
  writeln('您輸入的是:',str);
  Writeln(TObject.classname);  //這行代碼僅僅表示,可以訪問VCL中的System.pas
  if TObject.ClassNameIs('TObject') then
    Writeln('OK');
  readln;
end.

 

7,回調函數與方法指針:

  回調函數是引用C語言中的叫法,在Delphi中,既可以是函數,又可以是過程。

a,簡單的函數指針

 <1>定義語法:

   type

      過程類名=procedure ([參數表列]); //注意,這里procedure后沒有過程名

      函數類名=function([參數表列]) : 返回類型;

 end;

   <2>用以上定義的方法類可以定義出一個方法指針變量,並且這個變量可以作為其他方法的參數來使用.

    回調函數的形參實現,以下是闡述部分事例:

       procedure 回調函數名(..., A : 過程類名,...);

       begin

           ...A([參數表列])..  //這里實現了對過程A的使用,使用時不需要考慮A的闡述部分,因為它還沒有真正的實體,但是參數表列要跟<1>中定義的一致。

       end;

    <3>主調函數的使用.

       主調函數中,需要定義出一個具有實體的方法,這個方法作為參數使用,替換<2>中的A成為實參。

       這個作為實參出現的方法不需要額外的說明其類型,但是必須要與<1>中的參數表列及返回類型一致。

       eg.

   function 作為實參的方法([參數表列]) : 返回值;

       begin

           ......  //注意,這一部分才是真正的實現了<1>中方法的實體

       end;

       procedure 一個主調的方法;

       begin

           回調函數名(..., 作為實參的方法,...);  //注意,這里的<2>中的A已經被換成了上面的實體方法。 

       end;

 

    整個回調函數的實現,是分三步的,前兩步分別是定義〖方法指針〗及〖回調函數與方法指針的使用關系〗,第三步

才是真正實現〖方法指針的實體及回調函數的使用〗。

 

b,與類和對象相關的方法指針:

    語法定義跟簡單方法指針有點象,不過結尾多了 of object:

       過程類名=procedure ([參數表列]) of object;

       函數類名=function([參數表列]) : 返回類型 of object;

    這類的指針往往是指向一個對象方法,甚至類方法(作為類方法指針,前面要加class)來用。

  其指針函數在實際實現時,其實是隱藏了一個self參數的,可以針對對象或者類來進行操作。並且,以of object定義的

指針,不可以與簡單方法指針轉換和賦值。

  這也就是我們為什么在窗體中這么寫代碼會報錯:

      button1.onclick=buttonclick; //這里buttonclick雖然單獨執行與button1.onclick可能是一樣的,但是它僅僅是個本地過程,而onclick是個對象事件。

      我們再來看看事件的標准定義:

      TNotifyEvent = procedure(Sender: TObject) of object;    //這句話其實就是定義了一個方法指針,而OnClick就是這種方法實例。

c,跨工程接口調用的關鍵字_cdecl, _stdcall,_fastcall,_cdeclspec

     這節內容實際是與方法指針沒有直接關系的,但是一般使用方法指針時又極易涉及到這些關鍵字,所以放在一起講了。

     這個下划線是C語言約定的標准,對於不同的語言其使用的方式也不一樣。

     cdecl 是C Declare 即C語言標准接口。

     stdcall 是 standard Call ,這個接口標准被Win API 所采用,也是最為通用的調用標准,例如delphi也用它。

     fastcall 在C++Builder當中使用廣泛。

     cdeclspc 用的比較少,它的直接意思是一個dll引用方法。

     所有的關鍵字與其定義接口的關鍵字必須一致,其實際意義是這些方法及方法參數的壓棧方法,細節我在這里不詳述。

     下面是一個標准的Delphi外部方法接口的定義:

      {要注意,加了關鍵字的方法名都是區分大小寫的。這里的MTASDK就是外部方法名。}

     function mta001_Exit(): Integer; stdcall; external MTASDK name '_mta001_Exit';

   

 最后再說明一點,假如定義了一個方法指針變量A,那么@A並不代表A的指針,而且是一個標准類型4字節的Pointer指針。

只有再多加一個@才能代表其方法地址,即:@@A

 

8,數組,集合與類型別名的使用。

  a,數組定義:

  var

         數組變量 : array [數組起始下標..終止下標] of 元素類型;

  允許定義復雜點的數組,多維數組及數組的數組。

        數組變量 : array [起始下標1..終止下標1,起始下標2..終止下標2] of 元素類型;  //這是一個二維數組.

        數組變量 : array [起始下標..終止下標] of array [起始下標..終止下標] of 元素類型。 //這是一個數組的數組。

   

       數組允許在定義變量時省略下標,然后在使用時,再用SetLength來設定數組的長度(即開辟內存),這樣定義的數組成為動態數組。

而數組名不帶下標時,僅僅等同於一個指針。動態數組的默認起始下標是從0開始的整數。

     數組可以通過Low(數組名)和High(數組名)來返回數組的上下標。

      b,集合定義:

      type

         集合名=set of [元素表列]; //元素表列可以是多個元素用逗號隔開,也可以用上下標加..表示,還可以是枚舉類型名。

  TToolButton = (tbAdd, tbDel, tbEdit, tbPost, tbCancel);  //這是一個枚舉
  TToolBtnSet = set of TToolButton;                                 //這是一個集合

      這個也可以在定義變量的時候直接定義集合:

      var

           集合變量 : 集合類型=set of [元素表列];

     常用的集合運算有in ,並且可以用+/-[元素表列]來增刪集合中的元素。

  注意:集合的類型實例數是有限制的,必須在255以內。例如:集合類型=set of Char; 這樣的定義是合法的,因為Char的實例數剛好255,

              而:集合類型=set of string;   或者:集合類型=set of integer; 這樣的定義是會報系統錯誤的。 所以,一般集合引用枚舉類型來定義很常見。

      c,類型別名:

      類型別名 : type 原類型名;

      注意,這樣定義的變量,在計算和賦值是是完全兼容的,但是在out型參數使用時,不匹配是會報錯的。

      類型別名的定義,有利於代碼的優化和變更。

     

9,多線程的使用

  一個程序就是一個進程,而一個進程中是擁有一個主線程的,我們經常需要設計各種過程的並行執行,這就需要用到線程設計。Delphi中使用線程的方式一般有三種:

標准的TThread類創建線程對象模式;直接使用WinApi調用模式;偽線程Application.ProcessMessages模式。

      而在線程使用中,最關鍵的處理核心就在於資源共享,資源使用中,一般是通過資源引用標記來使得其安全釋放的,不幸的是,這些資源標記大多時候是需要我們自己來建立和標記的。而且VCL中,可供用戶操作的繼承於TControl類都不是線程安全的,並且部分非用戶界面的VCL類對象也不是線程安全的(例如TList),VCL中有很都針對多線程設計的

安全類都以Thread前綴來標明(例如TThreadList)。所以,我們盡量不要在進程的excute事件中處理VCL對象,如果非要這么做不可,可以仿照Objective-C中的對象引用計數機制。

      好了,現在就針對線程的三種模式一一說明:

  a,標准的TThread類(這里有我一篇完整的例子分享一個多線程實現[冒泡][選擇][二分法]排序的例子)

    TThread類,因為含有一個純虛方法excute,所以它是一個抽象類,不允許直接建立對象來使用。我們使用它時,必須建立一個非抽象的派生類,再進行使用。

   TThread在Delphi中是可以作為一個單元獨立定義的,我們可以從〖新建〗--->〖Delphi Files〗--->〖Thread Object〗來建立一個獨立的線程單元。

         TThread類的聲名如下:(這里只揀主要屬性和方法進行說明)

 TThread = class
  private
     ...
  protected
    procedure DoTerminate; virtual;                                    //線程結束時執行
    procedure Execute; virtual; abstract;                 //線程的主實現過程。執行完畢自動設置Terminated為True,並調用事件OnTerminate
    procedure Synchronize(Method: TThreadMethod);                              //將線程中的一個對象過程,置於程序中的主線程中同步執行,一般被excute中調用。
    property ReturnValue: Integer read FReturnValue write FReturnValue; //可作為函數ThreadProc的返回值
    property Terminated: Boolean read FTerminated;                               //線程結束標記,觸發OnTerminate后自動置True
  public
    constructor Create(CreateSuspended: Boolean);                              //構造方法,參數如果為False,則在創建線程后自動執行Excute;如果為True,則掛起
    destructor Destroy; override;
    procedure AfterConstruction; override;
    procedure Resume;                                                                      //喚醒掛起的線程
    procedure Suspend;                                                                     //掛起線程
    procedure Terminate;                                                                   //中斷線程
    function WaitFor: LongWord;
    property FatalException: TObject read FFatalException;
    property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate;  //線程在結束時是否需要自動釋放
    property Handle: THandle read FHandle;                                       //線程句柄
{$IFDEF MSWINDOWS}
    property Priority: TThreadPriority read GetPriority write SetPriority;  //是一個枚舉類型,標記着該線程的優先級
{$ENDIF}
    ...
    property Suspended: Boolean read FSuspended write SetSuspended; //線程是不是掛起狀態
{$IFDEF MSWINDOWS}
    property ThreadID: THandle read FThreadID;                              //線程ID,注意:一個線程是可以有多個句柄的,但是線程ID是唯一的。
{$ENDIF}
    ...

 //線程的結束事件。默認調用DoTerminate過程,也可以自定義一個事件方法然后用OnTerminate進行指向。
    property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;  
  end;

 

  假如我們已經派生了一個事件類TMyThread,並實化了Excute方法,那么我們就可以這么產生一個線程

  var

       MyThread1 : TMyThread;

....

      MyThread1 := TMyThread.Create(True);               //這個線程是掛起的,如果參數是False,其實MyThread1變量都可以省了。

      MyThread1.OnTerminate := 回調函數;

      MyThread1.FreeOnTerminate  := True;                //其實下面這兩句也可以在類TMyThread聲明中,用構造函數直接設置死。

      MyThread1.Resume; 

  這樣就可以完成運行一個線程了,只不過對於那些非線程安全的對象處理,我們需要在TMyThread的類聲明中,用一個對象方法進行處理。

例如在TMyThread中,有這樣一個方法:

  protected

          procedure OperatorVCL;

      ...

      我們可以在Excute實化時這么處理:

      procedure TMyThread.Excute;

      begin

            ...

            Synchronize(OperatorVCL);    //回調處理

      end;

      我們可以這么理解TThread的設計,當一個程序中出現一個任何一個附屬線程的時候,程序會自動建立一個0X0的隱藏窗口,用來接收線程的Synchronize方法。並且,所有的附加線程,都是共用這個隱藏窗口的,並且在傳遞Synchronize方法時,將其對應的線程Self作為一個IParam傳給主線程。

  b,跳過Thread類,直接用Win Api模式來實現一個進程:

      //定義一個線程句柄

      var

          hThread: THandle;     

      //定義一個線程的主方法

     function MainThreadMethod( AParam : integer ) : boolean; stdcall; //這里是過程,函數都可以,參數返回值也可以隨便定義,但是stdcall關鍵字不能少。

     begin

        ...

     end;

     //直接創建線程,並喚醒或掛起

     var
         ID: DWORD;

     ...

     hThread := CreateThread(nil, 0, @MainThreadMethod, nil, CREATE_SUSPENDED, ID);

     ResumeThread(hThread);

     SuspendThread(hThread);

     ...

     這個事例中用CreateThread,ResumeThread,SuspendThread三個方法直接用API函數完成了對一個線程的使用。

     當然,還有一系列的線程方法,例如:TerminateThread,ExitThread,GetCurrentThread: THandle;GetCurrentThreadId: DWORD; 等等。

     其實,用TThread類實現多線程時,也調用CreateThread和ExitThread方法,不過Delphi重新定義了一個線程方法:BeginThread(非對象或類方法哦),

這個方法將CreateThread系列方法打包簡化了,並且在Thread的構造函數中默認調用了BeginThread方法。

     注意:API系列的Thread方法是在Windows單元定義的;BeginThread方法是在System單元定義的;而TThread的類定義,是在Classes單元定義的。

     c,偽線程操作Application.ProcessMessages

     這個方法一般是用在循環當中,其作用是防止在一個事件中執行循環的時候,把主線程完全占有,這時候窗體操作UI處於死鎖狀態,即不能停止,也不能點擊或拖動。

  而增加了Application.ProcessMessages,就可以讓程序執行到這里響應一個Windows的標准消息,這種〖分步〗執行的效果也跟多線程類似。

      我們可以在系統響應Application.ProcessMessages的消息的時候,配合Sleep語句,並改變一個控件內容,從而達到控制循環條件的效果。

  但是有一點一定要注意:Application.ProcessMessages 一定不能在OnTimer事件中使用,否則會造成消息迭代,主線程阻塞。雖然OnTimer的機制跟線程非常

相似,但是其實它是基於主線程實現的,任務在這里阻塞一樣會影響到主線程的運行。

     

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM