作者:蔡煥麟 (huanlin_tsai@yahoo.com)
日期:Feb-1-2001
目前坊間關於 COM+ 的中英文書籍可說是琳瑯滿目,相信很多人都已經知道 Windows2000 增加了一種叫做 Neutral Apartment 的執行緒模型,但是卻沒有人介紹如何以 Delphi 5 來撰寫這類元件,這篇文章會的目的就是希望能把這部分的學習心得與各位分享。閱讀這篇文章時,你應該具備以下知識或技能:
Windows NT4 支援的執行緒模型有 Single,Apartment(Single Threaded Apartment, STA)及 Free(Multi-threaded apartment, MTA),Delphi 5 也直接支援這些執行緒模型。而 Windows2000 加入了新的 Neutral Apartment(NA),這種新的執行緒模型可以讓你設計出來的元件真的享有 object pooling(物件集區)的好處--這麼說,難道我們目前所撰寫的元件都沒有用到 object pooling?
幾乎是的。首先,object pooling 在 MTS 2.0 中並沒有提供,也就是必須在 Windows2000 平台才有;其次,平常我們以 Delphi 5 或 VB6 所撰寫的元件絕大部分都是使用 STA 執行緒模型(VB6 只支援 STA),而 STA 無法使用 object pooling,只有 MTA 和 NA 才有支援 object pooling,但是 MTA 執行緒的元件在實作上比較複雜,所以在 Windows2000 平台上採用 NA 執行緒模型會是比較好的選擇。Delphi 5 雖然沒有直接支援 NA,但是透過繼承現有的類別再加上一些程式碼,就可以設計出具有 object pooling 功能的元件,稍後你就會看到,這項任務並不困難。
關於 Neutral Apartment,你可以看看微軟的 MSDN 網站怎麼說。
在動手之前,我想應該先解釋一下什麼是 object pooling,以及為什麼要使用它。Object pooling 的目的是希望在大量用戶端存取的情況下,縮短元件在建立及初始化所需要的時間。在分散式運算環境中,object pooling 可以有效的降低應用程式伺服器的負荷,並且加快應用程式伺服器對用戶端的回應時間,因為當物件被用完以後會被放入"物件池"裡面,等下次要使用時就不再重新建立,只是從物件池裡面取出來用,所以能夠省下建立物件的時間。但是請記住,object pooling 不是萬靈丹,它不會讓原本就跑得像烏龜的系統在效能上有戲劇性的進展,你在設計元件時就要先思考哪些元件需要,哪些元件不需要 object pooling。如果元件在建立及初始化時必須花費較多的系統資源及時間,你才能夠明顯的感受到 object pooling 的好處。
關於 Object pooling 如何增進效能的進一步資料,可以到微軟的 MSDN 網站看看。
有了基礎概念後,我們可以開始進入實作階段了。先啟動 Delphi,點選 File | New,然後在 New Items 對話盒中切換到 ActiveX 頁夾,點選 ActiveX Library,然後按一下 Ok 鈕。這時候你可以先存檔並且將專案命名為 D5Neutral.dpr。
再次點選 File | New,然後在 New Items 對話盒中切換到 Multi-tier 頁夾,點選 MTS Object,然後按一下 Ok 鈕。接下來會出現 New MTS object 對話盒,CoClassName(類別名稱)輸入 "NeutralObj",按一下 Ok 鈕,然後存檔並且將單元命名為 D5NeutralImpl.pas。
現在,我們要撰寫一個新的類別工廠(呃....類別工廠,簡單的說就是建立元件的元件,如果你想進一步了解的話,請閱讀文後所附的參考資料)。這個類別工廠主要的工作在於修改視窗的註冊資料庫(registry)以將元件的執行緒模型登錄成為 "Neutral"。做法是 New 一個 Unit,然後撰寫程式碼如下(這個程式碼片段的原始作者是 Max Alexeyev ):
unit NAObjFac; interface uses ActiveX, MtsObj, Mtx, ComObj, StdVcl; type TNeutralAutoObjectFactory = class(TAutoObjectFactory) private FNeutralApartment: Boolean; public constructor Create(ComServer: TComServerObject; AutoClass: TAutoClass; const ClassID: TGUID; Instancing: TClassInstancing; ThreadingModel: TThreadingModel = tmSingle; ANeutralApartment: Boolean = False); procedure UpdateRegistry(Register: Boolean); override; end; implementation uses ComServ; { TNeutralAutoObjectFactory } constructor TNeutralAutoObjectFactory.Create(ComServer: TComServerObject; AutoClass: TAutoClass; const ClassID: TGUID; Instancing: TClassInstancing; ThreadingModel: TThreadingModel; ANeutralApartment: Boolean); begin inherited Create(ComServer, AutoClass, ClassID, Instancing, ThreadingModel); FNeutralApartment := ANeutralApartment; end; procedure TNeutralAutoObjectFactory.UpdateRegistry(Register: Boolean); var sClassID, sServerKeyName: string; begin inherited UpdateRegistry(Register); if Register and FNeutralApartment then begin if Instancing = ciInternal then Exit; sClassID := GUIDToString(ClassID); sServerKeyName := 'CLSID\' + sClassID + '\' + ComServer.ServerKey; if (ThreadingModel <> tmSingle) and IsLibrary then CreateRegKey(sServerKeyName, 'ThreadingModel', 'Neutral'); end; end; end.
然後把 D5NeutralImpl.pas 的 initialization 區段中原本由 Delphi 幫你產生的程式碼:
TAutoObjectFactory.Create(ComServer, TNeutralObj, Class_NeutralObj, ciMultiInstance, tmApartment);
改成這樣:
TNeutralAutoObjectFactory.Create(ComServer, TNeutralObj, Class_NeutralObj, ciMultiInstance, tmApartment, True);
為了實驗看看 object pooling 確實有發揮作用,請在 TNeutralObj 裡面改寫 Initialize 方法,我在裡面加入了延遲約三秒鐘的程式碼:
procedure TNeutralObj.Initialize; procedure Delay(lMilliSeconds: longint); var lStart: longint; begin lStart := GetTickCount; while longint(GetTickCount) - lStart <= lMilliSeconds do Application.ProcessMessages; end; begin inherited; Delay(3000); end;
最後,加入一個簡單的 GetName 方法:
function TNeutralObj.GetName: string; begin Result := 'TNeutralObj.GetName'; SetComplete; end;
請記得 COM+ 元件的方法在結束之前必須呼叫 SetComplete 或 SetAbort 才能將元件所佔用的資源釋放掉。
將元件編譯連結,並且註冊,你可以利用 Delphi 的 IDE 提供的 Run | Install MTS Object 來註冊,或者使用〔元件服務〕管理工具來註冊元件。完了嗎?不,元件註冊完成後,只是具備了 object pooling 的功能,但是還沒啟用,你必須使用〔元件服務〕為元件啟用這項功能。做法是,在元件名稱上面點一下滑鼠右鍵,選內容,接著切換到〔啟動〕頁夾,將 "啟用物件集區" 打勾,"集區最小為" 輸入 2(也就是第一次建立時就在集區裡面建立 2 個物件), "啟用 Just In Time 啟動" 也必須打勾。參考下圖:
元件的部分到這裡就大功告成了,接下來你可以撰寫一個用戶端程式,建立並呼叫此元件的方法,以實際觀察第一次建立元件和之後建立元件所需的時間相差多少,在我的機器上執行的結果是,第一次呼叫元件的方法時需要 3 秒多,第二次以後就只需要 0.1 秒左右 。撰寫用戶端程式的這個部份就不贅述了,您可以下載完整的範例程式回去試試看。
下載範例程式(解開 zip 檔之後,請以 Delphi 開啟 BuildAll.bpg)
簡單與功能強大經常是相互衝突的,以往程式員為了避免撰寫複雜的 MTA 程式碼,而選擇執行效率比較差的 STA,現在由於 Neutral Apartment 的出現終於有了一個最好的選擇。希望透過本文的介紹及實作練習之後,各位也能很輕易的以 Delphi 5 設計出具備 object pooling 的 COM+ 元件,並且對於如何改善分散式應用程式的執行效率有更多的認識。
http://geocities.datacellar.net/huanlin_tsai/