應用程序編寫完后,可以用兩種方法展開(deploy)它(“展開”( Deploying)的含義是指把應用程序分發給用戶)。可將應用程序分發給公眾,或者分發給某個公司內的用戶。不論用何種方法,都需要知道哪些選項是可用的。從根本上講,有兩種選擇:靜態鏈接或動態鏈接使用程序包,下面將討論這些選項,以便編程人員作出適當的選擇來展開其應用程序。下面先講講選項。
什么是程序包(What's a Package?)
在討論選項之前,先給出程序包的定義。
一個程序包(package)就是一段編譯過的代碼,駐留在BPL擴展名的文件中。
這個解釋可能還是讓人不好明白,下面來進一步講解。移去外表包裝,程序包實質上就是帶bpl擴展名的DLL(這樣講可能有點過,但在這里還是很貼切的)。Delphi中有兩種類型的程序包:運行階段(runtime)程序包和設計階段(design)程序包。下面分別講述這兩種類型的程序包,以便大家理解程序包是如何工作的。
1、 運行階段程序包(Runtime Packages)
運行階段程序包包含應用程序需要運行的代碼。雖然Delphi提供了眾多不同的程序包,但最主要的程序包是VCL70.BPL,這個程序包包含了全部的基礎VCL代碼。如果要在應用程序中使用程序包,就要裝入VCL70.BPL程序包並根據需要從中調用例程。如果應用程序是數據庫應用程序,同樣也要裝入VCLDB70.BPL,並按需要從中調用例程。除這里提到的兩個程序包外,還有其他的Delphi程序包。
除VCL程序包外,應用程序可能要用到其他程序包,當使用第三方組件或任何自己編寫的組件時,就會出現這種情況。必須查看第三方組件的文檔,搞清楚應用程序需要運行哪些程序包。下面先介紹設計階段程序包,然后回過頭來展開使用程序包的應用程序。
2、 設計階段程序包(Design Packages)
大多數的Delphi組件都包含一個運行階段程序包和一個設計階段程序包。運行階段程序包包含組件要運行的全部代碼;設計階段程序包包含組件在設計時要在窗體上運行的代碼,包括屬性編輯器和組件編輯器。
設計階段程序包有一個Requires列表,用以通知Delphi要運行哪些程序包。設計階段程序包總是要用運行時程序包中的代碼,還可能需要一個或多個VCL程序包中的代碼。一個程序包可以包含多個組件的代碼,運行階段程序包與設計階段程序包都是如此,沒有必要一個組件對應一個單獨的程序包。
由於設計階段程序包只包含在設計時顯示組件所需的代碼,所以它通常比相應的運行階段程序包要小很多。Delphi僅僅在設計時使用設計階段程序包,應用程序不使用設計階段程序包。
靜態鏈接與動態鏈接(Static Linking Versus Dynamic Linking)
上面,已經對程序包有了一些基本的了解,下面開始學靜態鏈接和動態鏈接。
1、 靜態鏈接(Static Linking)
當應用程序使用VCL靜態鏈接時,它不再需要使用程序包;應用程序要運行的全部代碼都直接鏈接到應用程序的可執行文件中,它是一個獨立的程序,不需要任何的支持文件(程序包或DLLs)。
Note
任何規則都有例外,靜態鏈接應用程序不需要任何DLLs支持的二個前提條件:
- 假定應用程序不是數據庫應用程序。Delphi數據庫應用程序運行時需要Borland Database Engine(BDE),BDE主要是由DLLs組成的集合,因此該應用程序是靜態鏈接,也是需要使用DLLs的。
- 假定應用程序不使用任何的ActiveX控件。實際上ActiveX控件是DLL的一種形式,因此,當應用程序使用ActiveX控件時,它就不再是一個獨立的應用程序。
Delphi提供了鏈接選項,可對其進行選擇。靜態鏈接是缺省選擇,與動態鏈接相比,靜態鏈接有兩條主要優點:
- 編程人員不需要操心附件文件的安裝,應用程序包含了全部要運行的代碼,不需要運行時程序庫的支持。
- 靜態鏈接的應用程序一般總比需要程序包的應用程序要小。在講動態鏈接的優點和缺點時還要談到這一點。
靜態鏈接有一個重要缺陷,但它只在使用到很多用戶定義的DLLs的應用給程序中顯露出來。這一缺陷是:在每個模塊(主應用程序本身)和每個DLL中,VCL和RTL代碼是重復的,這意味着代碼中有不必要的重復。
例如,假設每個模塊至少要200KB的VCL基本代碼和RTL代碼,並假定一個主應用程序要求10個支持它的DLLs(動態鏈接庫)。這意味着當實際只用200KB的代碼時,卻要使用2200KB的代碼(11個模塊x 200KB)。應用程序和DLLs都是靜態鏈接,在它們之間不能共享VCL和RTL代碼。
2、 動態鏈接(Dynamic Linking)
動態鏈接,是指應用程序在運行階段動態地裝入它要用的程序庫代碼。對於Delphi應用程序,這意味着任何需要的程序包都是在運行階段裝入。需要的程序包中肯定會包括一個或多個VCL程序包,並且可能還要用第三方程序包。
Note
應用程序裝入程序包是自動進行的,不必編寫代碼來裝入程序包,Delphi負責程序包裝入的工作。在靜態鏈接的基礎上選擇動態鏈接不需要對代碼做任何修改,只需要改變一下分發應用程序的方式。這一點在后面很快就會講到。
動態鏈接相對於靜態鏈接有一個主要優點:多個模塊可共享代碼。還記得前面舉的“一個應用程序與10個支持它的DLLs”的例子么?使用動態鏈接,應用程序和它所有的DLLs可共享來自VCL程序包中的全部代碼。每個模塊至少可以減少200KB,因為所有的基本代碼都包含在運行階段DLLs中。當大型軟件產品包含多個應用程序或許多DLLs時,這一優點就更加明顯。
動態鏈接也存在兩個問題。第一個問題是需要與應用程序一起傳送的程序包和DLLs可能非常大,光一個主要的VCL程序包VCL70.BPL就要1.3MB。除基本VCL程序包外,應用程序可能還需要用到其他的程序包,這意味應用程序至少需要1.3MB的DLLs才可以運行。第二個問題是動態鏈接更加難以捉摸、更麻煩,這個問題可以歸結成“版本問題”。為了講清楚這個問題可以打個比方。假定有兩個版本的Delphi,用Delphi7.02創建了一個應用程序,並選擇動態鏈接,這就要求傳送VCL程序包和RTL DLL,客戶在他的機器上安裝上這個應用程序后一切工作正常。與此同時,可用Delphi4.0創建一個應用程序,也采用動態鏈接。客戶購買了該應用程序並安裝它。這個安裝程序是家庭制作的,不那么正規,它會覆蓋原有的應用程序安裝的程序包和DLLs。由於用Delphi4.0創建的程序包版本比另一種的低,兩者不兼容,應用程序會突然退出運行。是否看出問題所在?
現實中,諸如Inprise的商用軟件公司是這樣解決這一個問題的:對一個軟件的不同版本,用不同的文件名來命名其程序包和DLLs,並將版本信息嵌入到程序包和DLLs中(一個好的安裝程序會自動檢查版本號,並且只安裝版本比系統中已存在的程序包版本高的程序包)。Borland公司的程序包不會出問題。
如果使用的組件出自一家不負責任的公司,那就很可能出問題。隨着Internet的迅速發展,組件的來源范圍非常廣,更要重視這個問題。在很多情況下,無法預料會出什么亂子,所以在購買便宜組件或使用免費組件時要特別小心。
3、 到底哪個好?(So Which Is Better?)
大家可能會問:應該用靜態鏈接還是動態鏈接?這個問題的答案取決於所編寫的應用程序的類型。一般來說,如果編寫小規模或中等規模的應用程序,應該用靜態鏈接;如果編寫大規模的應用給程序或用到很多DLLs的應用給程序,則應該用動態鏈接。
考察一個簡單例子可能會使這個問題更直觀些。前面我們創建了程序ScratchPad,使用靜態鏈接,該程序編譯后是427KB左右;如果使用動態鏈接,則EXE文件大小可降至22KB左右,但必須傳送1.3MB的程序包。在這種情況下,動態鏈接不是一個好的選擇。
在應用程序中使用運行階段程序包(Using Runtime Packages in Your Applications)
如果選擇使用動態鏈接,則只需修改工程選項中的一個設置。請按以下步驟操作:
(1)從主菜單上選【Project | Options】菜單項,彈出“Project Options”對話框;
(2)點擊“Project Options”對話框中的Packages頁面,並選中位於對於對話框底部的“Build with runtime package”選項;
(3)點擊OK關閉“Project Options”對話框;
(4)重建(Rebuilt)該程序;
這就是全部要做的事情。切記:使用動態鏈接,不需要對代碼作任何修改。
分發使用程序包的應用程序(Deploying Applications Using Packages)
要分發采用動態鏈接的應用程序,必須知道應用程序使用了哪些程序包。如果按照上一步的步驟,則可以肯定知道需要VCL70.BPL,可能還需要其他的VCL程序包,這取決於應用程序中使用的組件。
要照抄應用程序中用到的程序包,必須運行諸如TDUMP.EXE的工具並檢查EXE引用的入口;TDUMP在Delphi安裝目錄的Bin目錄下,要運行TDUMP,只需要打開命令提示符並轉到應用程序所在目錄,然后在命令行輸入以下命令:
tdump scratchPad.exe
TDUMP立即以滾屏方式顯示出信息,可按Pause鍵暫停以便查看顯示信息。當然滾屏可能太快,可將TDUMP的輸出定向到一個文本文件,這樣查看文本文件即可。例如:
tdump scratchPad.exe > dump.txt
然后可在Code Editor中打開dump.txt文件查看其中的內容。
在TDUMP生成的文件中能看到類似下面的內容:
Section: Import ImportLookUpTblRVA:00000000 Time Stamp: 00000000 Forwarder Chain: 00000000 (index of first forwarder reference) Imports from rtl70.bpl __fastcall System::initialization() __fastcall System::Finalization() __fastcall System::RegisterModule(System::TLibModule *) System::__linkproc__ __fastcall LStrAsg(void *, const void *) System::__linkproc__ __fastcall LStrArrayClr(void *, int) System::__linkproc__ __fastcall LStrClr(void *) System::__linkproc__ __fastcall Halt0() System::__linkproc__ __fastcall StartExe(System::PackageInfoTable *, System::TLibModule *) System::__linkproc__ __fastcall HandleFinally() __fastcall System::TObject::Dispatch(void *) __fastcall System::TObject::FreeInstance() __fastcall System::TObject::NewInstance(System::TMetaClass *) Imports from kernel32.dll GetModuleHandleA Imports from vcl70.bpl __fastcall Forms::initialization() __fastcall Forms::Finalization() __fastcall Forms::TApplication::MessageBox(const char *, const char *, int) __fastcall Forms::TApplication::Run() __fastcall Forms::TApplication::CreateForm(System::TMetaClass *, void *) __fastcall Forms::TApplication::Initialize() __fastcall Forms::TApplication::SetTitle(const System::AnsiString) __stdcall Forms::TCustomForm::QueryInterface(const _GUID&, void *) __fastcall Forms::TCustomForm::UpdateActions() __fastcall Forms::TCustomForm::ShowModal() __fastcall Forms::TCustomForm::SetFocus() __fastcall Forms::TCustomForm::CloseQuery()
從這里面找出所有帶.bpl擴展名的文件並記錄下它們的文件名。記錄下的文件名就是那些必須與應用程序一起分發的程序包。
Note
擁有一個好的安裝程序可以節省很多時間並省去許多麻煩,Delphi7專業版和企業版中帶有InstallShield Express打包程序,Wise Install打包程序也不錯。好的安裝程序能指示出應用程序所需的程序包,並自動將它們包括進去。不建議在任何環境下都自己編寫安裝程序,因為編寫安裝程序要考慮的問題太多,很容易因考慮不周而出問題。
大多數時候不需要在應用程序中使用運行階段程序包,但有時候卻又非使用程序包不可。