BCB中採用的類庫是VCL,其編程框架是事件驅動的,類似於VB。我在開發過程中發現,如果不對BCB的事件驅動鏈進行分析,寫的程序會帶有很多的錯誤,健壯性很成問題,而且調試很麻煩。
我發現程序中的很多錯誤都來源於C++的指針操作。在程序中用new的方法創建了一個對象,然後delete這個對象之後,如果此時還有其它指針指向這個對象,訪問此對象信息的代碼必定會引發異常。這在C++中是常識性的問題。但這個問題在BCB這類事件驅動的開發環境中就複雜化了。由於事件模型其實是對Windows消息循環機制的一個封裝,而Windows中一個消息可能會引發一連串的其他消息,所以,事件之間也是相互引發的,形成一個事件驅動的鏈條。
在BCB這類RAD的開發環境中,窗體(Form)是最核心的組件,窗體的事件模型也是程序中最需要分析的。
我在BCB5中設計了幾種典型情況,針對BCB提供的事件模型,繪出了BCB中的事件驅動鏈。由於WM_PAINT消息的反覆激發,所以我有意識地屏蔽掉了對這一消息的響應事件OnPaint。我發現大多數錯誤就發生成窗體的生成與銷燬過程中,其主要的原因就是程序員選錯了事件,將代碼放錯了地方。所以,我重點分析了工程中窗體的創建和銷燬過程中的事件驅動鏈。
先對BCB的工程文件(BPR)進行分析:
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
WinMain是BCB工程的入口點,Initialize()一句完成程序的初始化工作,CreateForm()一句完成窗體的創建工作(在此引發一系列的事件),Run()一句則進入了消息循環,此時事件主要是用戶操作引發的(當然也有定時器等操作系統引發的事件)。
Catch()一句保證應用程序不會引起操作系統的崩潰。但這也是無數的BCB和Delphi程序會引發“XXX內存讀寫錯誤”信息的根源所在。正是爲了提高BCB程序的健壯性,我才進行了這個實驗,其中比較枯燥,不想看記錄的人可以直接去看結論部分。
實驗記錄:
一、由BCB自動創建的Form的事件驅動鏈
實驗一. 工程中只有一個主窗體Form1
當窗體中只有一個主窗體(Form1)時,程序運行事件的發生次序如下:
創建
Form1構造函數àOnCreate() //到此窗體創建完畢,以下進入窗體的顯示過程
àOnShow()àOnActivate()àOnCanResize()àOnConstrainedResize()àOnResize()
窗體顯示完畢,以下進入消息循環,等待用戶輸入.
關閉
關閉主窗體Form1時:
OnCloseQuery()àOnClose() //到此退出消息循環
àOnHide()àOnDestory()à~Form1()析構函數
顯然,一個窗體要先隱藏,再銷燬,最後再調用析構函數
另外:OnShow()à……àOnResize()這一事件鏈在各種情況中始終不變,下面的記錄中我就以……代表這些不變的事件鏈。
實驗二:工程中有兩個窗體,Form1爲主窗體,Form2爲普通窗體
當工程中有兩個窗體,Form1爲主窗體,Form2爲普通窗體,事件鏈如下:
創建
Form1構造函數à OnCreate() //到此窗體Form1創建完畢,開始創建Form2
à Form2構造函數
//以下進入Form1的顯示過程
àOnShow()à……àOnResize()
除非在代碼中指定,缺省情況下Form2只處於加載狀態,不會顯示,Form2中的OnCreate()等事件代碼不會運行。
關閉
關閉時,Form2處於隱藏或顯示狀態均一樣:
Form1的OnCloseQuery()àOnClose() //到此退出工程消息循環,以下銷燬Form2
àForm2析構函數àForm1的 OnHide()àForm1的OnDestory()à~Form1()析構函數
值得注意的是,這時,Form2的OnDestory()沒有被執行,對應地,當它創建時,Form2的OnCreate()也沒有執行!
實驗三:工程中有兩個窗體,Form1爲主窗體,Form2繼承自Form1
在BCB中,可以很方便地繼承某個窗體,這是BCB的一個非常好用的功能。
創建
Form1構造函數à OnCreate() //到此窗體Form1創建完畢,開始創建Form2
àForm2的OnCreate()àForm1的構造函數àForm2的構造函數 //以下進入Form1的顯示過程,同單窗體工程一樣àOnShow()à……àOnResize()
值得注意的是:Form2的OnCreate()函數居然先於Form2的構造函數運行!
關閉
1.Form2爲隱藏狀態,
Form1的OnCloseQuery()àOnClose() //到此退出工程消息循環,以下銷燬Form2
àForm2析構函數àForm1的析構函數àForm2的OnDestory()àForm1的 OnHide()àForm1的OnDestory()àForm1的析構函數
注意:Form1的析構函數被執行了兩次!
2.Form2爲顯示狀態
Form1的OnCloseQuery()àOnClose() //到此退出工程消息循環,以下銷燬Form2
àForm2的OnHide()àForm2的析構函數à Form1的析構函數à Form2的OnDestory()àForm1的 OnHide()àForm1的OnDestory()àForm1的析構函數
注意:Form1的析構函數被執行了兩次!
兩種情況對比,可以發現只多了一個Form2的隱藏過程.