Symbian是一個廣泛運行於手機和行動裝置上的作業系統,由於手機和PC不同,它沒有統一的標準,其硬體設定及其所導致的使用者介面都差異巨大(譬如有的手機有觸筆而有的沒有),Symbian提供基本服務和一套比較通用的UI層(UIKON),取得Symbian授權的手機廠家一般都會針對自己具體型號的手機設計自己的使用者介面,比較著名的是Nokia的Series 60平台(用於Nokia 7650、Nokia N-Gage、Siemens SX1等)和Sony
Ericssion的UIQ平台(UIQ平台本身是Symbian的原生UI,不過現在Symbian把它賣給了Sony Ericssion)。前者針對單手操作的按鍵式手機,沒有觸控螢幕和手寫筆,一般有一個4方嚮導航鍵,後者運行於有較大螢幕,帶有觸控螢幕的手機。
一、模組分析
AVKON是Nokia在Symbian的UIKON UI層基礎上開發出來的專用於S60的UI層,和MFC有點相似之處。HelloWorld項目是基於此架構開發的。
首先,每個編譯出來的GUI應用程式(*.app)實際上是一種多態dll,不是一個通常意義上的類似於exe那樣的可執行模組。當執行一個應用程式時,OS把app檔案名稱作為參數傳遞給apprun.exe,由apprun.exe建立應用程式進程並把*.app載入到新建立的進程的地址空間,因此,每個app都匯出了一個函數NewApplication(),它是用序號匯出的。以Helloworld工程為例,察看目錄%SDKROOT%/6.1/Series60/Epoc32/BUILD/SYMBIAN/6.1/SERIES60/SERIES60EX/HELLOWORLD/GROUP/HELLOWORLD/WINS下面的HELLOWORLD.def檔案可以看出這一點。NewApplication的函數體在Helloworld.cpp檔案中,它所做的工作很簡單,僅僅是new一個CHelloWorldApplication對象然後返回其指標。在這個檔案中還有另一個全域函數E32Dll,此函數意義等同於Windows中dll編程的DllMain函數,是DLL的Entry
Point,在此可以進行初始化TLS(Thread Local Storage)的動作,每個Symbian程式都必須有這個函數。當應用程式準備啟動並執行時候,E32Dll會最先被架構所調用,架構會傳進來一個參數aReason,此參數有4種可能值:EDllProcessAttach, EDllThreadAttach, EDllThreadDetach or EDllProcessDetach,在這裡直接返回KErrNone,表示沒有錯誤。
接下來分析應用程式類CHelloWorldApplication。這個類按照官方文檔的說法就是“An Application that creates the new blank document and defines the application UID”,意思就是這個類是負責new出一個空白文檔,並定義應用程式的UID。這個類只有兩個成員函數:AppDllUid和CreateDocumentL,分別考察之。AppDllUid僅僅返回一個預先定義寫死的一個UID,CreateDocumentL函數用CHelloWorldApplication應用程式物件的this指標new出一個文檔對象出來CHelloWorldDocument(註:函數名後面加L表示此函數可以異常退出)。這個應用程式類並沒有提供建構函式,按照C++語言規範,編譯器將產生一個預設的建構函式,我想,可能是因為CHelloWorldApplication類沒有增加任何資料成員(僅僅多了兩個函數成員),所以無需自己提供建構函式來完成額外的構造動作吧。追尋到其祖先類CEikApplication,可以發現其建構函式是Protected的,這是採用兩步構造的典型特徵。通過將建構函式聲明為Protected,可以防止使用者繞過兩步構造來產生一個C類對象(這個小trick和MFC中Dynamic
Create是一樣的),迫使使用者必須通過靜態函數NewL或者NewLC來產生對象。
然後是文檔類CHelloWorldDocument,這個類的作用是“A Document object that represents the data model and is used to construct the App UI”,感覺有點類似於MFC中的文檔類,負責表示資料,建立UI。首先映入眼帘的是建構函式/解構函式。這一對函數在這裡是空函數,先不去管它。剩下的3個成員函數,NewL/NewLC和ConstructL用於完成兩步構造。所謂兩段構造,是Symbian裡面引入的一個概念,因為C++的建構函式和解構函式如果失敗,是不允許拋出異常的,如果直接把大量的初始化代碼放在建構函式裡的話,會使得建構函式失敗的幾率增加,一旦構造失敗,就很難對構造過程中半途而廢的“爛尾樓”進行收拾,Symbian提出的兩步構造就是把所有可能導致失敗的代碼全部移出建構函式,放在成員函數ConstructL()裡進行,在建構函式執行完畢後再調用該ConstructL完成後續構造動作,以保證建構函式絕對不會失敗,這一過程稱為“兩步構造”。與此相關,Symbian的一些類經常添加了一對函數:NewL和NewLC,來實現兩步構造,這兩個函數被聲明為static的,回憶C++的文法定義,static成員函數實際上是獨立於類之外的,它們可以在類的任何一個執行個體存在之前被調用,因此Symbian中往往用顯式調用這兩個函數的辦法來建立類的執行個體,我們先看一下代碼:
CHelloWorldDocument* CHelloWorldDocument::NewL(CEikApplication& aApp) { CHelloWorldDocument* self = NewLC(aApp); //構造過程成功,把指標從Cleanup Stack中彈出,因為已無保護必要 CleanupStack::Pop(self); return self; } CHelloWorldDocument* CHelloWorldDocument::NewLC(CEikApplication& aApp) { CHelloWorldDocument* self = new (ELeave) CHelloWorldDocument(aApp); //第一步構造 //self指向一個構造了一半的執行個體,將其推入Cleanup Stack,防止後續構造失敗 CleanupStack::PushL(self); self->ConstructL(); //第二步構造 return self; }
可以看出,NewL調用NewLC,NewLC先new出一個CHelloWorldDocument對象的執行個體(這是第一步構造,new已經被重載了,ELeave是它的參數,表示當new失敗的時候會異常退出),然後將指標推入Cleanup Stack,緊接著再調用ConstructL(這是第二步構造),NewL和NewLC的區別是:NewL在堆上建立執行個體,當出現記憶體不足錯誤時,就異常退出;NewLC在堆上建立執行個體並將其推入到Cleanup Stack中。如果記憶體不足就異常退出。(註:尾碼L就是Leave的意思,表示可能異常退出,C是Cleanup
Stack的意思,表示會將在堆上建立的對象推入Cleanup Stack)。
最後剩下一個函數CreateAppUiL,這個函數負責建立UI,向架構返回一個指向UI對象的指標,值得注意的是UI類的構造過程並不是兩步構造,它的建構函式在CreateAppUiL中被調用後,架構得到UI對象的指標,接著架構再主動調用UI類的ConstructL函數完成UI對象的全部構造工作,UI對象的所有權也轉交給架構,因此文檔類並不否則銷毀UI對象。
第3個類是UI類CHelloWorldAppUi,如前所述,UI對象不是基於兩步構造,沒有提供NewL和NewLC函數。官方協助中對它的描述是“An App Ui (Application User Interface) object that handles the commands generated from menu options”。可見它是專用於處理使用者命令輸入的。我們先分析ConstructL函數,它先調用基類的BaseConstructL()函數,完成一些諸如讀取資源一類的工作;接著,用兩步構造建立View類的執行個體(View類是負責在螢幕上進行顯示的,下面會有介紹),View類由UI類擁有,在CHelloWorldAppUi類中有一個指標成員iAppView指向所構造的View對象,View類的析構在UI類的解構函式中完成;最後調用AddToStackL(iAppView)把View對象加到Control
Stack上(當UI對象被銷毀的時候必須把View對象從Control Stack上拿下來,參見CHelloWorldAppUi類的解構函式),這樣做是為了確保應用程式能響應使用者的按鍵動作,只要View對象在Control Stack上,無論使用者什麼時候按下按鍵,OfferKeyEventL 函數就會被調用,這個函數有兩個參數分別表示按鍵事件的類型(Key down、Key press或者Key up)以及所按下的按鍵的編碼。接下來要講UI的主要任務——處理使用者命令輸入。這是在HandleCommandL成員函數中進行的。這個函數大體架構類似於Windows
SDK編程中的視窗函數——一個大的switch結構,整體結構還是很好理解的,基本上望文知義,幾個aCommand定義,比如EHelloWorldCommand1和EAknSoftkeyExit,是在Helloworld.rss(這是資源檔,有點類似於VC裡的res檔案)裡定義的,EHelloWorldCommand1指左軟鍵Option快顯功能表中的“Hello”功能表項目,EAknSoftkeyExit指“Hello”下面的“Exit”功能表項目,唯一是EEikCmdExit沒有找到定義所在,可能是架構預定義的。_LIT宏定義了一個TLitC類的執行個體message,並初始化為第二個參數給出的串,接著new出一個CAknInformationNote對象(這個對象的作用大概有點象MessageBox),顯示message中的字串。需要特別指出的是,在最後的Default分支中有一個Panic函數,Panic的字面意思是“驚慌,
恐慌”,我的理解,panic code大概就是error code的意思,這個函數大概就是一個返回一個error code,在Helloworld.pan檔案中定義了EHelloWorldBasicUi=1。
第4個類是View類,專門用於在螢幕上顯示資料的。這個類一共6個成員函數,建構函式和解構函式用於第一步構造,此處是空的,NewL和NewLC的作用如前所述,這幾個老面孔就不提了。我們分析的重點在Draw函數,它類似MFC的OnPaint函數,架構在需要重繪的時候會去調用它,程式員不應該手動調用它,因為在調用Draw之前必須先啟用(Activated)系統的GC(Graphics Context)——類似DC的一個概念。如果我們想重繪視窗怎麼辦呢?答案是使用DrawNow函數,這樣會激發架構去調用Draw,(當然如果非要手動直接調用Draw也是可以的,但是必須先ActivateGc,Draw完之後再DeactivateGc)。最後一點要注意的是,Draw函數是絕不允許異常退出(Leave)的,因為前面講過,Draw函數是程式員編寫的,在程式運行過程中,有可能被架構自動調用,如果Draw函數向架構拋出異常,架構不可能未蔔Crowdsourced Security Testing地知道發生了什麼異常,又如何處理錯誤呢?所以Draw函數必須使用TRAP宏來主動捕獲錯誤。
二、啟動順序
1.應用程式被啟動,架構載入應用程式dll(即產生的app檔案),調用其E32Dll函數,這個過程與Windows下載入Dll的過程極其類似。
2.架構調用應用程式dll匯出的NewApplication函數。此函數建立一個CHelloWorldApplication類的執行個體,向調用它的架構返回一個指向該應用程式物件的指標,接著架構用此指標完成應用程式的後續建立工作。
3.架構調用AppDllUid取得此程式的UID。UID的一個作用是用於檢查此程式是否已經有一個執行個體在運行中了,若是,則不再重複啟動一個新執行個體,而是切換到已經在啟動並執行應用程式。
4.架構調用CHelloWorldApplication 的成員函數CreateDocumentL,此函數建立一個Document類的執行個體並返回一個指向被建立的Document對象的指標。這樣架構就可以直接調用Document類的成員函數。在此,Document類的建立是基於Symbian OS的兩步建立,使用了Document類的靜態成員函數NewL。
5.架構再次調用AppDllUid,檢查是否需要從檔案系統中負載檔案。HelloWorld項目因為不需要對任何資訊進行持久化儲存,所以沒有相關聯的檔案。
6.架構調用Document對象的CreateAppUiL函數,建立一個UI對象並取得其指標(注意CreateAppUiL函數僅僅是new出了一個UI對象,也就是只調用了UI對象的建構函式,按照Symbian的兩步構造語意,UI對象尚未真正構造完畢)。
7.架構調用UI對象的ConstructL函數來完成UI對象的構造(在這裡,架構通過分別調用UI對象的建構函式和ConstructL函數來完成UI對象的構造,這樣可以使得架構可以在UI對象完全誕生之前完成任何必須的初始化工作),在ConstructL函數中,先調用了基類的BaseConstructL函數,完成諸如讀取和應用程式相關聯的資源等一類的工作;接著,調用View類的NewL函數建立View對象(使用兩步構造)。
8.架構調用View對象的Draw函數,此處的Draw函數會在顯示一個空白的螢幕。
9.當使用者選擇Option菜單的時候,架構就調用UI對象的HandleCommandL函數,並向此函數傳遞一個aCommand參數,以表明使用者選取的是哪個功能表項目。UI針對不同的功能表項目執行相應的動作,此處的動作是建立一個Information note並顯示之。