乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      DELPHI下自定義包的作用,開發(fā)以及包文件的安裝配置位置

       【狗尾巴草】 2010-12-29
      本章要點:
      n        為何要用包
      n        為何不用包
      n        包的類型
      n        包文件
      n        使用運行期包
      n        把包安裝到Delphi IDE中
      n        創(chuàng)建包
      n        包的版本化
      n        包編譯器指示符
      n        包的命名約定
      n        使用運行期(插件)
      n        從包中導(dǎo)出函數(shù)
      n        獲取包的信息
       
      從Delph 3開始便引入了包的概念,它使我們能夠把應(yīng)用程序放入獨立的模塊,然后讓多個應(yīng)用程序共享這些模塊。包只是一種特殊的動態(tài)鏈接庫(DLLs),它包含了其他有關(guān)Delphi的詳細信息,它和DLL的不同之處在于使用方法。包主要用于共享獨立模塊(即Borland包庫,以.bpl為后綴的文件)中存儲組件集合。在開發(fā)Delphi應(yīng)用程序時,應(yīng)用程序通常是在運行期使用我們創(chuàng)建的包,而不是直接在編譯/鏈接期連接它們。因為這些單元的代碼都寄存在.bpl文件中,而不是exe或者.dll文件中,所以這些.exe或.d11文件可以非常小。
      包是VCL專有的,也就是說,用其他語言編寫的應(yīng)用程序不能使用Delphi創(chuàng)建的包(C++Builder是個例外)。提出包技術(shù)的原因是為了避開Delphi l和Delphi 2的限制。在以前的Delphi版本中,VCL向每個可執(zhí)行文件添加了最小150k~200k代碼。因此,即使把應(yīng)用程序的—小部分分離到一個DLL巾,這個DLL和應(yīng)用程序都還是包含冗余代碼。假如需要在一臺機器上提供一套應(yīng)用程序集,必然非常頭痛。包使我們得以減小應(yīng)用程序的大小,并為組件集合的發(fā)布提供便利途徑。
      1.1  為何要用包
      使用包的原因有多種。在接下來的小節(jié)中,我們將討論3個重要的原因:精簡代碼、分割應(yīng)用程序和組件容器。
      1.1.1  精簡代碼
      使用包的一個土要原因是減小應(yīng)用程序和DLL的大小。Delphi已經(jīng)提供了幾個個預(yù)定義的包,這些包把VCL分割成邏輯止的分組。事實上,我們能夠有選擇地編譯自己的應(yīng)用程序,如同有多個Delphi包存在一樣。
      1.1.2  發(fā)布更小的應(yīng)用程序——應(yīng)用程序分割
      人家已經(jīng)知道Internet上有很多可用的程序,也許是完整的應(yīng)用程序、可下載的演示版或者是對現(xiàn)有應(yīng)用程序的升級。對于用戶來說.假如在他們的本地系統(tǒng)上已經(jīng)存在某個應(yīng)用程序的一部分(比如預(yù)安裝),選擇下載更小的應(yīng)用程序版本將更有利。
      通過使用包把應(yīng)用程序進行分割,用戶就可以僅僅對應(yīng)用程序需要升級的那一部分進行升級。然而,注意必須考慮一些有關(guān)版本的問題。本章涉及到了這些版本問題。
      1.1.3  組件容器
      也許使用包最通常的原因之一是第三方組件的發(fā)朽。假如你是一個組件賣主,你必須知道如何創(chuàng)建包,因為諸如組件和屬性編輯器、向?qū)Ш蛯<夜ぞ叩仍O(shè)計期元素,都是中包提供的。
      包和DLL的對比
      使用DLL來為它們的服務(wù)器應(yīng)用程序存放管理窗體會導(dǎo)致DLL擁有自己的Forms.pas文件副本。這將會引起一個不可思議的錯誤,該錯誤與Windows的窗口句柄處理有關(guān)。Windows窗口句柄處理產(chǎn)生于DLL中——當(dāng)DLL被卸載時,窗口句柄卻不能被操作系統(tǒng)解除參照。下一個穿過隊列被發(fā)往頂層窗口的消息會導(dǎo)致應(yīng)用程序出錯,這樣操作系統(tǒng)就會因為應(yīng)用程序處于非法狀態(tài)而將它關(guān)閉。使用包代替DLL可以克服這個問題,因為包引用了主應(yīng)用程序的Forms.pas副本,因此消息隊列能夠成功地傳到應(yīng)用程序。
       
       
      1.2 為何不用包
      最好不要使用運行期包,除非其他應(yīng)用程序也需要使用這些包。因為,與把源代碼編譯為最終可執(zhí)行文件相比,包消耗的磁盤空間會更多。原因何在?假如創(chuàng)建一個使用包的應(yīng)用程序使得代碼尺寸包從200k減小到30k,看上去好像是節(jié)約了不小的空間。然而,當(dāng)需要發(fā)布包,甚至是Vcl60.dcp包時,大概需要2M空間。可以看出來這樣的“節(jié)約”并不是我們所期望的。我們的目的是,當(dāng)某代碼被多個應(yīng)用程序共享時,使用包來共享代碼。注意這就是在運行期使用包的惟一原出。對于一個組件作者來說,提供一個設(shè)計包,其中必須包含了希望用于Delphi IDE的組件。
      1.3  包的類型
      我們可以創(chuàng)建和使用的包有4種類型
      n        運行期包——運行期包包含—個應(yīng)用在運行期所需的代碼、組件等。一個依賴于某個特定運行期包的應(yīng)用程序,在沒有包的情況下就不能運行。
      n        設(shè)計包——設(shè)計包包含了在Delphi IDE中設(shè)計應(yīng)用程序所必需的組件、屬性/組件編輯器、專家工具等。這種類型的包只能用于Delphi,而且絕不要和應(yīng)用程序一起發(fā)布。
      n        運行期設(shè)計包——一個可同時在設(shè)計和運行期被激活的包,通常用在沒有具體的設(shè)計元素(比如屬性/組件編輯器和專家工具)時。通過創(chuàng)建這種包,可以簡化應(yīng)用程序的開發(fā)和配置。然而,假如這種包中含有設(shè)計元素,那么在運行期使用它將會帶來額外的設(shè)計支持負擔(dān)。如果有許多設(shè)計期元素的話,我們推薦創(chuàng)建設(shè)計包或運行期包,當(dāng)出現(xiàn)多個特定設(shè)計元素時,通過這些包來分割它們。
      n        非運行期、非設(shè)計包——這是種不太常見的包,只供其他包使用,不能直接供應(yīng)用程序引用,也不能用于設(shè)計環(huán)境。
      1.4 包文件
      表1.1根據(jù)包的文件擴展名列出并描述了特定包文件的類型。
      表 1.1 包文件
      文件擴展名
       文件類型
       描述
       
      .dbk
       包源文件
       當(dāng)調(diào)用包編譯器時創(chuàng)建.dpk文件。正如大家所想到的,這有點類似于Delphi項目創(chuàng)建一個.dpr文件
       
      .dcp
       運行期/設(shè)計期包標(biāo)識文件
       這是已編譯的包,它包含了包及其單元的標(biāo)識信息。另外,還有Delphi IDE所需的報頭信息
       
      .dcu
       編譯過的單元
       一個包含在某個包中的已編譯單元,包中的每一個單元都一個相應(yīng)的.dcu文件
       
      .bpl
       運行期/設(shè)計期
       這是運行期包或設(shè)計期包的庫類型的包文件,相當(dāng)于Windows中的DLL。對于一個運行期包,應(yīng)該和應(yīng)用程序(如果它們已經(jīng)被激活)一起發(fā)布這個文件。假如這個文件代表的是一個設(shè)計期包,應(yīng)該和運行期包一起發(fā)布給使用它們寫程序的程序員。注意如果所發(fā)布的不是源代碼,就必須發(fā)布相應(yīng)的.dcp文件
       
      1.5使用運行期包
          為了在Delphi應(yīng)用程序中使用運行期包,只要打開Project Options對話框上的Packages頁,選中其中的Build wlth Runtime Package核查框就行了。  下一次在選中這個選項之后再編譯應(yīng)用程序,這個應(yīng)用程序會被動態(tài)地連接到運行期包,而不是讓單元靜態(tài)的連入.exe或.d11文件。這樣將會得到一個苗條得多的應(yīng)用程序(當(dāng)然,要牢牢記住必須和應(yīng)用程序—起配置必需的包)。
      1.6  把包安裝到DeIphi IDE中
      有時,還必須把包安裝到DeIphi IDE中。例如在獲得個第三方組件集或Delphi插件時,就有必要這樣做。
      在這種情況下,必須首先把包文件放置到合適的位置。表1.2所示的就是通常放置包文件的地方。
       
       
      表1.2 包文件的存放位置
      包文件
       位置
       
      運行期包(*.bpl)
       運行期包應(yīng)該被放到Windows\system\目錄下(Windows95/98)或WinNT\System32\目錄下(Windows NT/2000)
       
      設(shè)計期包(*.bpl)
       因為從不同的賣主得到幾個包是可能的,所以應(yīng)該把設(shè)計期包放在公共的目錄下,以便于管理。比如,在\Delphi 6\目錄下創(chuàng)建\PKG目錄,可以把設(shè)計期包放在這里
       
      包標(biāo)志文件(*.dcp)
       可以把包標(biāo)識文件放在和設(shè)計期包文件(*.bpl)相同的地方
       
      編譯單元(*.dcu)
       假如正在發(fā)布沒有源代碼的包,必須同時發(fā)布已編譯的單元,我們推薦把DCU文件存放在類似\Delphi 6\Lib的目錄下,不要把自己的DCU文件和第三方賣主的DCU文件放在一起。比如,我們可以創(chuàng)建目錄\Delphi 6\3PrtyLib來存放第三方組件的*.dcu。必須要把這個目錄包括到搜索路徑中去。
       
      要安裝一個包,只要在Delphi 6菜單中選擇Component | Install Packages,調(diào)出Project Options對話框上的Packages頁。
      單擊Add按鈕,選擇某個.bpl文件。這樣做之后,這個文件將成為Project頁上被選定的文件。按下OK,這個新包就被安裝列Dclphi IDE中了。假如這個包中包含組件,我們在組件面板上會看到新的組件頁及新安裝的組件。
      1.7 創(chuàng)建包
            在創(chuàng)建包之前,需要就一些事情做出決策。首先,需要知道將要創(chuàng)建的包的類型(運行期包、設(shè)計期包等)。需要根據(jù)不同情況選樣包的類型,這一點我們馬上就會說明。第二,要知道給新創(chuàng)建的包取個什么樣的名寧,以及把這個也項目存放在什么地方,記住存放配置好的包的目錄也許并不是創(chuàng)建包的白錄。最后,需要知道這個包包含哪些單元以及它還需堅哪些其他的包。
      1.7.1  包編輯器
      最常見的創(chuàng)建包的方法是使用包編輯器,從Object Repository(對象倉庫)中選擇Packages圖標(biāo)就可以調(diào)出包編輯器(在 Delphi主菜單中選擇File | New | Other,可以找到Object Repository)。注意包編輯器包含兩個文件夾:Contains和Requires。
      1. Contains文件夾
      在Contains文件夾中,我們指定需要編譯的單元放入新的包中。在把單元放入一個包的Contains時,要遵循如下幾個規(guī)則:
      n        絕對不能把這個單元列于另一個包的contains子句中,也不能把它列于另一個包中的某個單元的uses子句中,這個包將和此單元應(yīng)該放入的包同時被載入。
      n        列于contains子句中的單元,不管直接地還是間接地(間接地就是指它們存在于其他單元的uses于句下,而這些單元列于某個包的contains子句中),都不能被列于此包的requires子句中。這是因為重編譯時,這些單元已經(jīng)和包綁定起來了。
      n        假如某個單元已經(jīng)于某個包的contains子句里,就不能再把這個單元列于同—個應(yīng)用程序中的另一個包的contains子句里。
      2. Requires文件夾
      在Requires文件夾中,可以指定新包所需的其他包。這類似于Delphi單元uses子句。在大多數(shù)情況下,我們創(chuàng)建的任何包中的requires子句下都會有VCL60——存放Delphi標(biāo)準VCL組件的包。例如,典型的方案是,首先把所有的組件都放入一個運行期包,然后創(chuàng)建一個設(shè)計期包,并把那個運行期包放在這個設(shè)計期包的requires子句下。關(guān)于把包放在另一個包Requires文件夾中,也有一些規(guī)則:
      n        避免循環(huán)引用——比如包Packagel不能放在自身的叫requires子句下,它也不能包含另一個requires子句下存有Package1包的包。
      n        引用鏈接不能向反方向引用先前已經(jīng)在鏈中引用過的包。
      包編輯器擁合一個工具條和一個環(huán)境敏感菜單。參考Delphi 6的聯(lián)機幫助,在“Package Editor”下面列出了這些按鈕的用途,這里就不再重復(fù)了。
      1.7.2 包設(shè)計方案
      以前曾經(jīng)說過,必須知道想創(chuàng)建的包是什么類型。在這一節(jié),將給出4種可能的情形,根據(jù)這些情形來選擇使用設(shè)計期包或運行期包。
      方案1:使用組件的設(shè)計和運行期包
      組件的設(shè)計和運行期包是針對這樣一種情形,你是一個組件作者并且適于以下任意一種情:
      n        希望Delphi程序員能夠正確編譯/連接我們的組件到他們的應(yīng)用程序,或者能夠單獨地和他們的應(yīng)用程序一起發(fā)布。
      n        有一個組件包,并且不想強迫我們的用戶把設(shè)計特性(組件/屬性編輯器等)編譯到他們的應(yīng)制程序代碼中去。
      對于這種情形,我們既要創(chuàng)建設(shè)計期包,也要創(chuàng)建運行期包,圖1.1說明了這種方案。正如圖中所示,設(shè)計期包(ddgDT60.dpk)同時封裝了設(shè)計特性(屬性和組件編輯器)和運行期包(ddgRT60.dpk)。運行期包(ddgRT60.dpk)所包括的僅僅只有組件。把運行包列表到設(shè)計期包的requires區(qū)域,這個方案就算完成了,如圖1.1所示。
      圖1.1 設(shè)計期包包含了設(shè)計元素和運行期包
      在編譯包之前,還必須為每一個包設(shè)置合適的使用選項??梢栽赑ackage Options對話框中做這項工作(在包編輯器中單擊右鍵彈出快捷菜單,選擇Options選項即可打開對話框)。對于運行期包DdgRT60.dpk,應(yīng)該把使用選項設(shè)置為Runtime only 。這樣就可以保證這個包不會作為一個設(shè)計期包安裝到IDE(見本草后面的補充“組件安全”)。對于設(shè)計期包DdgRT60.dpk,應(yīng)該選擇Design time only作為使用選項。這能夠確保用戶把包安裝到IDE,卻不會把它們作為運行期包使用。
      把運行期包加入設(shè)計期包還不能使包含在此運行包中的組件能用于Delphi的IDE,必須向IDE注冊這些組件。正如大家所知道的,無論何時創(chuàng)建一個組件,Delphi都會自動地在組件單元中插入一個Register()過程,接著再調(diào)用RegisterComponents()過程。當(dāng)我們安裝組件時,實際上向Delphi IDE注冊這個組件的是RegisterComponents()過程。在使用包時,推薦的方法是把Register ()過程從組件單元轉(zhuǎn)移到一個單獨的注冊單元,這個注冊單元調(diào)用RegisterComponents()來注冊所有的組件。這不僅使我們能夠更加容易的管理組件注冊,考慮到還不能在Delphi IDE中使用這些組件,這樣做也能夠防止別人非法的安裝和使用我們的運行期包。
      作為例子,本書中的組件都被存放在運行期包DdgRT60.dpk中。有關(guān)這些組件的屬性編輯器、組件編輯器和注冊單元(DdgReg.pas)都存放在設(shè)計包DdgDT60.dpk中。DdgDT60.dpk還把DdgRT60.dpk放在了它的requires子句下。程序1.1所示的是注冊單元。
      程序1.1 Delphi6開發(fā)向?qū)е邪慕M件注冊單元
      Unit DDGReg
       
      Interface
       
      Procedure Register;
       
      Implementation
       
      uses
         Classes, ExptIntf, DsgnIntf, TrayIcon, AppBars, ABExpt, Worthless, RunBtn, PwDlg, Planets, LbTab, HalfMin, DDGClock, ExMemo, MenView, Marquee, PlanetPE, RunBtnPE, CompEdit, DefProp, Wavez, WavezEd, LnchPad, LPadPE, Cards, ButtonEdit, Planet, DrwPnel;
       
      Procedure Register;
      begin
             //Register the components
        RegisterComponents('DDG',[TddgTrayNotifyIcon, TddDigitalClock, TddgHalfMinute, TddgButtonEdit, TddgExtendedMemo,          TddgTabListbox, TddgRunButton, TddgLaunchPadm, TddgMenView, TddgMarquee, TddgWaveFile, TddgCard, TddgPasswordDialog, TddgPlanet, TddgPlanets, TddgWorthLess, TddgDrawPanel, TComponentEditorSample, TDefinePropTest]);
        //Register any property editor
        RegisterPropertyEditor(TypeInfo(TRunButton),TddgLaunchPad,'',TRunButtonProperty);
        RegisterpropertyEditor(TypeInfo(TWaveFileString), TddgWaveFile, 'WaveName',TWaveFileStringProperty);
        RegisterpropertyEditor(TddgWaveFile, TWaveEditor);
        RegisterpropertyEditor(TComponentEdtiorSample, TSampleEditor);
        RegisterpropertyEditor(TypeInfo(TPlanetName), TddgPlanet, 'PlanetName', TPlanetNameProperty);
        RegisterpropertyEditor(TypeInfo(TCommandLine), TddgRunButton,'', TCommandLineProperty);
        RegisterCustomModule(TAppBar, TCustomModule);
        RegisterLibraryExpert(TAappBarExpert.Create);
      end;
      end.
       
       
      件安全
      即使某個人只有我們的運行期包,也能注冊我們的組件。他可以調(diào)用他自己的注冊單元來注冊我們的組件。然后他把這個單元添加到一個單獨的包中,此包的requires子句下有我們的運行期包。在他把這個新包安裝到Delphi IDE后,組件將出現(xiàn)在組件面板中。然而,他不可能用我們的組件來編譯任何應(yīng)用程序,因為在組件單元中沒有必需的*.dcu文件
       
      包的發(fā)布
      假如向組件作者發(fā)布包時沒有附上源代碼,就必須同時發(fā)布已編譯的DdgDT60.bpl和DdgRT60.bpl包、*.dcp文件以及編譯組件所需的任何已編譯的單元(*.dcu文件)。使用我們組件的程序員,假如想要激活他們的應(yīng)用程序的運行期包的話,就必須和他們的應(yīng)用程序一起發(fā)布DdgRT60.bpl包,以及其他可能使用的運行期包。
      方案2:只用組件的設(shè)計期包
      我們想發(fā)布不打算在運行期包中發(fā)布的組件時,只用組件的設(shè)計期包。這時,應(yīng)該把組件、組件編輯器、屬性編輯器、組件注冊單元等都包括在一個包文件中。
      包的發(fā)布
      假如向組件作名發(fā)布包時沒有附上源代碼,就必須同時發(fā)布己編譯的包DdgDT6.bp1、DdgDT6.dcp文件和編譯組件所需的任何已編譯的單元(*.dcu文件),使用我們所編組件的程序員必須在他們的應(yīng)用程序中編譯我們的組件,但他們不能把我們的組件作為運行期包發(fā)布。
      方案3:只用(沒有組件)IDE增強工縣的設(shè)計特性
      共用(沒有組件)IDE增強工具的設(shè)計特性是在我們想要為Delphi IDE提供諸如專家之類的增強工具時。對于這種情況,首先在注冊單元中注冊專家到IDE這種情況下的發(fā)布也比較簡單,只要發(fā)布已編譯的*.bpl文件就行了。
      方案4:應(yīng)用程序分割
      應(yīng)用程序分割是這樣一種情形,我們想分割我們的應(yīng)用程序成為幾個邏輯上的塊,每一個塊都可以單獨發(fā)布。我們要這樣做是出于以下幾個別由:
      n        這種方案易于維護。
      n        當(dāng)用戶需要這個應(yīng)用程序時,  可以只購買所需的功能。然后假如用戶需要增加功能,只要下載必需的包就行了,這比下載整個應(yīng)用程序小得多。
      n        能夠更容易地提供部分程序的修正(補丁),而不要求用戶獲取一個應(yīng)用程序的全新版本。
      采用這種方案時,只需提供應(yīng)用程序所需的*.bpl文件即可。這種方案與前一種方案類似,只不過沒有為Delphi IDE提供包,而是為自己編寫的應(yīng)用程序提供包。分割應(yīng)用程序時,必須注意包的版本化所帶來的問題,詳情參見下一節(jié)的描述。
      1.8 包的版本化
      包的版本化不太容易理解。在很大程度亡,包的版本化和單元版本化的方式是相同的。也就是說,為應(yīng)用程序提供的任何包必須使用和編譯應(yīng)用程序相同的版本的Delphi來編譯。因此不能把在Delphi 6中編寫的包用于在Delphi 5中編寫的應(yīng)用程序里。Borland程序員把包的版本看作是代碼基礎(chǔ)(code base),所以Delphi 6中編寫的包有6.0版的代碼基礎(chǔ)。這個概念會影響包文件命名約定。
      1.9 包編譯器指示符
      有—些特別的編譯器指示符可以插入包的源代碼。在這些指示符中,有的是特定用于正在被打包的單元,其他的用于包文件。表1.3和表1.4列出了這些指示符及其說明。
      表1.3 被打包的單元的編譯器指示符
      指示符
       含義
       
      {$G} or {IMPORTEDDATA OFF}
       如果不想使單元被打包,而是想直接和應(yīng)用程序相連接,就要使用這個指示符。把它和{$WEAKPACKAGEUNIT}比較一下,{$WEAKPACKAGEUNIT}允許一個包中包括單元,它的代碼是靜態(tài)的和應(yīng)用程序相連接
       
      {$DENYPACKAGEUNIT}
       含義同{$G}
       
      {$WEAKPACKAGEUNIT}
       參見“關(guān)于{$WEAKPACKAGEUNIT}的更多信息”小節(jié)
       
      表1.4 Package.dpk文件的編譯器指示符
      指示符
       含義
       
      {$DESIGNONLY ON}
       把包作為設(shè)計期包編譯
       
      {$RUNONLY ON}
       把包作為運行期包編譯
       
      {$IMPLICITBUILD OFF}
       防止包被重編譯。當(dāng)不經(jīng)常改變包時使用這個選項
       
       
      關(guān)于{$WEAKPACKAGEUNIT}的更多信息
      弱包(weak package)的概念比較簡單,它主要指包正在引用并不存在的庫(DLL)。比如,Vcl60調(diào)用Windows操作系統(tǒng)的核心Winn32 API。DLL中有很多這樣的調(diào)用,而并不是每臺計算機中部有這些DLL。這些調(diào)用由包含{$WEAKPACKAGEUNIT}指示符的單元顯露。通過包括這個指示符,保持這個單元的源代碼在包中,但要放在DCP文件而不是BPL文件里(把DCP看作DCU,BPL看作DLL)。因此,任何對這些弱打包單元的引用都會靜態(tài)的鏈接到應(yīng)用程序,而不是動態(tài)地通過這個包引用。
      我們很少使用{$WEAKPACKAGEUNIT}指示符。Delphi程序員創(chuàng)建它來處理一些特殊的情況,雖然這不是必需的。假如有兩個組件,每一個都在一個單獨的包個,并且正在引用某個DLL的同一接口祖先,這時就會存在問題。當(dāng)某個應(yīng)用程序要同時使用這兩個組件時,就會導(dǎo)致那個DLL的實例被載入,這樣將會引發(fā)有關(guān)初始比和全局變量引用的巨大災(zāi)難。解決的方法是把這個接口單元提供給某個標(biāo)準的Delphi包,比如Vcl60.bpl。然而,這又會導(dǎo)致其他有關(guān)專門的DLL問題,這些DLL也許并不存在,比如PENWIN.DLL。假如Vcl60.bpl包含有一個不存在的DLL的接口單元,這將導(dǎo)致Vcl60.bpl和Delphi不能使用。Delphi程序員通過讓Vcl60把接口單元包含進一個單一的包中來解決這個問題。但只有和Delphi IDE一起使用Vcl60.bpl時,是使接口單元被靜態(tài)的鏈接,而不是動態(tài)載入。
      一般情況下當(dāng)然沒有必要使用這個指示符,除非出現(xiàn)前面的類似情形,或者想確保包中包含某個特定的單元,而且又是靜態(tài)的鏈接到正在運行的應(yīng)用程序中。對于后者,這樣做的一個原因是為了優(yōu)化。注意弱打包的任何單元在它們的初始化部分和結(jié)束部分部不能有全局變量或代碼。在和包一起發(fā)布時,也必須要發(fā)布弱打包單元的*.dcu文件。
      1.10 包的命名約定
      以前我們說過包的版本化問題將合影響包的命名。對于怎么命名包沒有一套規(guī)則,但是我們建議使用某種把代碼基礎(chǔ)融入包的名稱中的命名習(xí)慣。舉個例子,本書中的組件都包含在一個運行期包中,它的名字包含了6個Delphi限定詞(DdgRT6.dpk)。設(shè)計期包(DdgDT6.dpk)也是這樣。上一個版在的包是用DdgRT5.dpk。這種命名習(xí)慣能夠防止用戶產(chǎn)生混淆,比如他們正在應(yīng)用哪個版本的包、哪個版本的Delphi編譯器適合它們。注意,包名稱要以3個字符的作者/公司標(biāo)識符開頭,接著用RT指示出這是一個運行期包,或者用DT表示它是一個設(shè)計期包。最后就是所適用的Delphi的版本號。
      1.11 使用運行期(插件)包的可擴展應(yīng)用程序
      插件包使得我們可以把應(yīng)用程序隔成不同的模塊.并且可以獨立于主程序發(fā)布這些模塊。這種方式是很有好處的,它允許我們擴展應(yīng)用程序的功能,卻不用重編譯/重設(shè)計整個應(yīng)用程序。然而,這就要求有—個精心的體系設(shè)計計劃。盡管深入研究這種設(shè)計問題已經(jīng)超出水書的范圍,但我們還是要做些討論,以使大家知道如何利用這個強大的功能。
       
      生成插件窗體
       
      這個應(yīng)用程序被分割為3個邏輯上的塊:主程序(ChildTest.exe)、TchildForm包(AIChildFrm6.bpl)和具體的TChildForM的派生類,每一個都存放在它日己的包里。
      AIChildFrm6.bpl包包含基類ChildForm。其他的包包含TchildForm類的派生類或具體的(concrete)TchildForm類。我們分別將把這些包稱為基類包和實體包。
      主應(yīng)用程序使用抽象包〔AIChildFrm6.bpl〕,每一個實體包也使用抽象包。這樣,為了工常上作,這個主應(yīng)用程序必須和包括了AIChildFm6.dcp包的運行期包一起編譯。同樣,每—個實體包都必須要求有AIChildFrm6.dcp包。我們不準備列出TChildForm的源代碼,也不列出對每一個TchildForm派生單元的實體派生類的源代碼。它們必須包括類似下面的初姑化(initialization)部分和結(jié)束(finalization)部分:
      Initialization
        RegisterClass(TCF2Form);
      Finalization
        UnRegisterClass(TCF2Form);
       
       
      當(dāng)主程序載入自身的包時,要使TChildForm的派生類可用于主程序的流系統(tǒng),就必須調(diào)用RegisterClass()。這類似于RegisterComponents()怎樣使得組件能夠可用于Delphi IDE中。當(dāng)包被載入時,必須調(diào)用UnRegisterlass()來刪除注冊類。然而,要注意RegisterClass()僅僅只是使類可用于主程序,主程序卻仍然不知道類名。主程序要怎樣才能創(chuàng)建一個類名未知的類實例呢?我們的意圖難道不就是使得這些窗體可用在主程序中,而不必辛苦的編與它們的類名并放入主程序的源代碼嗎?程序1.2所示的就是主程序的主窗體的源代碼,其中我們要強調(diào)的是如何用插件包實現(xiàn)插件窗體。
      程序1.2 使用插件包的主程序的主窗體
      unit MainFrm;
       
      interface
       
      uses
        Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
        StdCtrls, ExtCtrls, ChildFrm, Menus;
       
      const
        { Child form registration location in the Windows Registry. }
        cAddInIniFile  = 'AddIn.ini';
        cCFRegSection  = 'ChildForms';  // Module initialization data section
       
        FMainCaption   = 'Delphi 6 Developer''s Guide Child Form Demo';
       
      type
       
        TChildFormClass = class of TChildForm;
       
        TMainForm = class(TForm)
          pnlMain: TPanel;
          Splitter1: TSplitter;
          pnlParent: TPanel;
          mmMain: TMainMenu;
          mmiFile: TMenuItem;
          mmiExit: TMenuItem;
          mmiHelp: TMenuItem;
          mmiForms: TMenuItem;
          procedure mmiExitClick(Sender: TObject);
          procedure FormCreate(Sender: TObject);
          procedure FormDestroy(Sender: TObject);
        private
          // reference to the child form.
          FChildForm: TChildForm;
          // a list of available child forms used to build a menu.
          FChildFormList: TStringList;
          // Index to the Close Form menu which shifts position.
          FCloseFormIndex: Integer;
          // Handle to the currently loaded package.
          FCurrentModuleHandle: HModule;
          // method to create menus for available child forms.
          procedure CreateChildFormMenus;
          // Handler to load a child form and its package.
          procedure LoadChildFormOnClick(Sender: TObject);
          // Handler to unload a child form and its package.
          procedure CloseFormOnClick(Sender: TObject);
          // Method to retrieve the classname for a TChildForm descendant
          function GetChildFormClassName(const AModuleName: String): String;
        public
          { Public declarations }
        end;
       
      var
        MainForm: TMainForm;
       
      implementation
      uses IniFiles;
       
      {$R *.DFM}
       
      function RemoveExt(const AFileName: String): String;
      { Helper function to removes the extension from a file name. }
      begin
        if Pos('.', AFileName) <> 0 then
          Result := Copy(AFileName, 1, Pos('.', AFileName)-1)
        else
          Result := AFileName;
      end;
       
      procedure TMainForm.mmiExitClick(Sender: TObject);
      begin
        Close;
      end;
       
      procedure TMainForm.FormCreate(Sender: TObject);
      begin
        FChildFormList := TStringList.Create;
        CreateChildFormMenus;
      end;
       
      procedure TMainForm.FormDestroy(Sender: TObject);
      begin
        FChildFormList.Free;
        // Unload any loaded child forms.
        if FCurrentModuleHandle <> 0 then
          CloseFormOnClick(nil);
      end;
       
      procedure TMainForm.CreateChildFormMenus;
      { All available child forms are registered in the Windows Registry.
        Here, we use this information to creates menu items for loading each of the
        child forms. }
      var
        IniFile: TIniFile;
        MenuItem: TMenuItem;
        i: integer;
      begin
        inherited;
       
        { Retrieve a list of all child forms and build a menu based on the
          entries in the registry. }
        IniFile := TIniFile.Create(ExtractFilePath(Application.ExeName)+cAddInIniFile);
        try
          IniFile.ReadSectionValues(cCFRegSection, FChildFormList);
        finally
          IniFile.Free;
        end;
       
        { Add Menu items for each module. NOTE THE mmMain.AutoHotKeys property must
          bet set to maAutomatic }
       
        for i := 0 to FChildFormList.Count - 1 do
        begin
          MenuItem := TMenuItem.Create(mmMain);
          MenuItem.Caption := FChildFormList.Names[i];
          MenuItem.OnClick := LoadChildFormOnClick;
          mmiForms.Add(MenuItem);
        end;
       
        // Create Separator
        MenuItem := TMenuItem.Create(mmMain);
        MenuItem.Caption := '-';
        mmiForms.Add(MenuItem);
       
        // Create Close Module menu item
        MenuItem := TMenuItem.Create(mmMain);
        MenuItem.Caption := '&Close Form';
        MenuItem.OnClick := CloseFormOnClick;
        MenuItem.Enabled := False;
        mmiForms.Add(MenuItem);
       
        { Save a reference to the index of the menu item required to
          close a child form. This will be referred to in another method. }
        FCloseFormIndex := MenuItem.MenuIndex;
      end;
       
      procedure TMainForm.LoadChildFormOnClick(Sender: TObject);
      var
        ChildFormClassName: String;
        ChildFormClass: TChildFormClass;
        ChildFormName: String;
        ChildFormPackage: String;
      begin
       
        // The menu caption represents the module name.
        ChildFormName := (Sender as TMenuItem).Caption;
        // Get the actual Package file name.
        ChildFormPackage := FChildFormList.Values[ChildFormName];
       
        // Unload any previously loaded packages.
        if FCurrentModuleHandle <> 0 then
          CloseFormOnClick(nil);
       
        try
          // Load the specified package
          FCurrentModuleHandle := LoadPackage(ChildFormPackage);
       
          // Return the classname that needs to be created
          ChildFormClassName := GetChildFormClassName(ChildFormPackage);
       
          { Create an instance of the class using the FindClass() procedure. Note,
            this requires that the class already be registered with the streaming
            system using RegisterClass().  This is done in the child form
            initialization section for each child form package. }
          ChildFormClass := TChildFormClass(FindClass(ChildFormClassName));
          FChildForm := ChildFormClass.Create(self, pnlParent);
          Caption := FChildForm.GetCaption;
       
          { Merge child form menus with the main menu }
          if FChildForm.GetMainMenu <> nil then
            mmMain.Merge(FChildForm.GetMainMenu);
       
          FChildForm.Show;
       
          mmiForms[FCloseFormIndex].Enabled := True;
        except
          on E: Exception do
          begin
            CloseFormOnClick(nil);
            raise;
          end;
        end;
      end;
       
      function TMainForm.GetChildFormClassName(const AModuleName: String): String;
      { The Actual class name of the TChildForm implementation resides in the
        registry. This method retrieves that class name. }
      var
        IniFile: TIniFile;
      begin
        IniFile := TIniFile.Create(ExtractFilePath(Application.ExeName)+cAddInIniFile);
        try
          Result := IniFile.ReadString(RemoveExt(AModuleName), 'ClassName',
            EmptyStr);
        finally
          IniFile.Free;
        end;
      end;
       
      procedure TMainForm.CloseFormOnClick(Sender: TObject);
      begin
        if FCurrentModuleHandle <> 0 then
        begin
          if FChildForm <> nil then
          begin
            FChildForm.Free;
            FChildForm := nil;
          end;
       
          // Unregister any classes provided by the module
          UnRegisterModuleClasses(FCurrentModuleHandle);
          // Unload the child form package
          UnloadPackage(FCurrentModuleHandle);
       
          FCurrentModuleHandle := 0;
          mmiForms[FCloseFormIndex].Enabled := False;
          Caption := FMainCaption;
        end;
      end;
       
      end.
       
      這個應(yīng)用程序的邏輯其實是很簡單的。它使用系統(tǒng)注冊來確定哪一個包是可用的;當(dāng)為正在載入的包構(gòu)建菜單時,確定菜單的標(biāo)題和包中窗體的類名。
      大多數(shù)丁作都在LoadChildFormOnClick()事件處理程序中完成。在確定包的文件名以后,這個方法使用LoadPackage()載入這個包。LoadPackage()函數(shù)基本上和DLL中的LoadLibrary()相向。LoadChildFormOnClick()然后又為被載入的包個的窗體確定類名。
      為了創(chuàng)建一個類,需要一個類似Tbutton或Tform1的類引用。然而,這個應(yīng)用主程序卻沒有實體TchildForms的類名,這就是為什么要從系統(tǒng)注冊表中找回類名的原因。主應(yīng)用程序可以向FindClass()函數(shù)傳遞類名,以返回一個關(guān)于特定的類的類引用,這個類已經(jīng)用流系統(tǒng)注冊。記住,當(dāng)包被載入時,我們是在實體窗體單元的初始化部分做這個工作。接著用下面兩行代碼來創(chuàng)建類:
      ChildFormClass := TchildFormClass(FindClass(ChildFormClassName));
      FchildForm := ChildFormClass.Create(self, pnlParent);
       
       
      說明:類引用不過是內(nèi)存中的某個區(qū)域,其中包含了相關(guān)類的信息,這和類的類型定義是一回事情。當(dāng)用VCL流系統(tǒng)或RegisterClass()函數(shù)注冊這個類時,類引用就會進入內(nèi)存,F(xiàn)indClass()函數(shù)查找內(nèi)存區(qū)域,定位某個指定類名的類,并返回一個指向那個位置的指針,這不同于類實例。類實例是創(chuàng)建于調(diào)用構(gòu)造函數(shù)時。
       
      變量ChildFormClass是對TchildForm預(yù)聲明的類引用,并且能夠多態(tài)地訪問TchildForm派生類的類引用。
      CloseFormOnClick()事件處理程序只用來關(guān)閉子窗體并卸載它的包。剩下的基本上是一些設(shè)置性代碼,用來創(chuàng)建包菜單以及從從INI件中讀信息。
      使用這個技術(shù),我們就能創(chuàng)建高擴展性和松散耦合的應(yīng)用程序框架。
      1.12 從包中導(dǎo)出函數(shù)
      考慮到包不過是增強的DLL,因此可以像在DLL中那樣,從包中導(dǎo)出函數(shù)和過程。在這一節(jié)我們將向大家介紹這種使用包的方式。
       
      從包函數(shù)中運行窗體
       
      程序1.3足一個包含在包的內(nèi)部的單元。
      程序1.3 有兩個導(dǎo)出函數(shù)的包單元
      unit FunkFrm;
       
      interface
       
      uses
        Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
        Dialogs, StdCtrls;
       
      type
       
        TFunkForm = class(TForm)
          Label1: TLabel;
          Button1: TButton;
        private
          { Private declarations }
        public
          { Public declarations }
        end;
       
      // Declare the package functions using the StdCall calling convention
      procedure FunkForm; stdcall;
      function AddEm(Op1, Op2: Integer): Integer; stdcall;
       
       
      // Export the functions.
      exports
        FunkForm,
        AddEm;
       
      implementation
       
      {$R *.dfm}
       
      procedure FunkForm;
      var
        FunkForm: TFunkForm;
      begin
        FunkForm := TFunkForm.Create(Application);
        try
          FunkForm.ShowModal;
        finally
          FunkForm.Free;
        end;
      end;
       
      function AddEm(Op1, Op2: Integer): Integer;
      begin
        Result := Op1+Op2;
      end;
       
      end.
       
      很明顯,過程FunkForm()簡單地把在這個單元中聲明的窗體作為一個模塊窗體顯示。函數(shù)AdEm()獲取兩個操作數(shù)作為參數(shù)并返問它們的和。注意這個函數(shù)是通過StdCall調(diào)用協(xié)定在此單元的接口部分聲明的。
      程序1.4是演示如何從包中調(diào)用一個函數(shù)的應(yīng)用程序。
      程序1.4 應(yīng)用程序演示
      unit MainFrm;
       
      interface
       
      uses
        Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
        Dialogs, StdCtrls, Mask;
       
      const
        cFunkForm = 'FunkForm';
        cAddEm    = 'AddEm';
       
      type
        TForm1 = class(TForm)
          btnPkgForm: TButton;
          meOp1: TMaskEdit;
          meOp2: TMaskEdit;
          btnAdd: TButton;
          lblPlus: TLabel;
          lblEquals: TLabel;
          lblResult: TLabel;
          procedure btnAddClick(Sender: TObject);
          procedure btnPkgFormClick(Sender: TObject);
        private
          { Private declarations }
        public
          { Public declarations }
        end;
       
        // Defined the method signatures
        TAddEmProc = function(Op1, Op2: Integer): integer; stdcall;
        TFunkFormProc = procedure; stdcall;
       
      var
        Form1: TForm1;
       
      implementation
       
      {$R *.dfm}
       
      procedure TForm1.btnAddClick(Sender: TObject);
      var
        PackageModule: THandle;
        AddEmProc: TAddEmProc;
        Rslt: Integer;
        Op1, Op2: integer;
      begin
        PackageModule := LoadPackage('ddgPackFunk.bpl');
        try
       
          @AddEmProc := GetProcAddress(PackageModule, PChar(cAddEm));
          if not (@AddEmProc = nil) then
          begin
            Op1 := StrToInt(meOp1.Text);
            Op2 := StrToInt(meOp2.Text);
       
            Rslt := AddEmProc(Op1, Op2);
            lblResult.Caption := IntToStr(Rslt);
          end;
       
        finally
          UnloadPackage(PackageModule);
        end;
      end;
       
      procedure TForm1.btnPkgFormClick(Sender: TObject);
      var
        PackageModule: THandle;
        FunkFormProc: TFunkFormProc;
      begin
        PackageModule := LoadPackage('ddgPackFunk.bpl');
        try
          @FunkFormProc := GetProcAddress(PackageModule, PChar(cFunkForm));
          if not (@FunkFormProc = nil) then
            FunkFormProc;
        finally
          UnloadPackage(PackageModule);
        end;
      end;
       
      end.
       
       
      首先要注意的是必須聲明兩個過程類型——TAddEmProc和TFunkFormProc,而且必須是當(dāng)它們在包中出現(xiàn)時聲明。
      首先我們討論了btnPkgFormClick()事件處理程序,它的代碼看上去似曾相識。不同的是我們沒有調(diào)用LoadLibrary(),而是使用了LoadPackage(),其實LoadPackage()的出現(xiàn)就終結(jié)了LoadLibrary()的使用。接下來,我們使用GetProcAddress()函數(shù)來接受對這個過程的引用。CFunkForm常量的名稱和包中的函數(shù)名稱相同。
      我們可以發(fā)現(xiàn),從包中導(dǎo)出函數(shù)和過程的方法幾乎和從動態(tài)鏈接庫中導(dǎo)出的方法完全相同。
      1.13 獲取包的信息
      可以查詢一個包以獲得有關(guān)信息,比如它包含多少個單元、需要另外哪些包等??梢允褂脙蓚€函數(shù)來做這件工作:EnumModules()和GetPackageInfo()。
      這兩個函數(shù)都需要回調(diào)函數(shù)(callback functions),程序1.5演示了這些函數(shù)的使用。
      程序1.5 包信息演示
      unit MainFrm;
       
      interface
       
      uses
        Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
        Dialogs, StdCtrls, ComCtrls, DBXpress, DB, SqlExpr, DBTables;
       
      type
        TForm1 = class(TForm)
          Button1: TButton;
          TreeView1: TTreeView;
          Table1: TTable;
          SQLConnection1: TSQLConnection;
          procedure Button1Click(Sender: TObject);
        private
          { Private declarations }
        public
          { Public declarations }
        end;
       
      var
        Form1: TForm1;
       
      implementation
       
      {$R *.dfm}
       
      type
        TNodeHolder = class
          ContainsNode: TTreeNode;
          RequiresNode: TTreeNode;
        end;
       
      procedure RealizeLength(var S: string);
      begin
        SetLength(S, StrLen(PChar(S)));
      end;
       
      procedure PackageInfoProc(const Name: string; NameType:
        TNameType; Flags: Byte; Param: Pointer);
      var
        NodeHolder: TNodeHolder;
        TempStr: String;
      begin
        with Form1.TreeView1.Items do
        begin
       
          TempStr := EmptyStr;
       
          if (Flags and ufMainUnit) <> 0 then
            TempStr := 'Main unit'
          else if (Flags and ufPackageUnit) <> 0 then
            TempStr := 'Package unit' else
          if (Flags and ufWeakUnit) <> 0 then
            TempStr := 'Weak unit';
       
          if TempStr <> EmptyStr then
            TempStr := Format(' (%s)', [TempStr]);
       
          NodeHolder := TNodeHolder(Param);
          case NameType of
            ntContainsUnit: AddChild(NodeHolder.ContainsNode,
              Format('%s %s', [Name,TempStr]));
            ntRequiresPackage: AddChild(NodeHolder.RequiresNode, Name);
          end; // case
        end;
      end;
       
      function EnumModuleProc(HInstance: integer; Data: Pointer): Boolean;
      var
        ModFileName: String;
        ModNode: TTreeNode;
        ContainsNode: TTreeNode;
        RequiresNode: TTreeNode;
        ModDesc: String;
        Flags: Integer;
        NodeHolder: TNodeHolder;
       
      begin
        with Form1.TreeView1 do
        begin
          SetLength(ModFileName, 255);
          GetModuleFileName(HInstance, PChar(ModFileName), 255);
          RealizeLength(ModFileName);
          ModNode := Items.Add(nil, ModFileName);
       
          ModDesc := GetPackageDescription(PChar(ModFileName));
          ContainsNode := Items.AddChild(ModNode, 'Contains');
          RequiresNode := Items.Addchild(ModNode, 'Requires');
       
       
          if ModDesc <> EmptyStr then
          begin
       
            NodeHolder := TNodeHolder.Create;
            try
              NodeHolder.ContainsNode := ContainsNode;
              NodeHolder.RequiresNode := RequiresNode;
       
              GetPackageInfo(HInstance, NodeHolder, Flags, PackageInfoProc);
            finally
              NodeHolder.Free;
            end;
       
       
            Items.AddChild(ModNode, ModDesc);
       
            if Flags and pfDesignOnly = pfDesignOnly then
              Items.AddChild(ModNode, 'Design-time package');
            if Flags and pfRunOnly = pfRunOnly then
              Items.AddChild (ModNode, 'Run-time package');
       
          end;
       
        end;
       
        Result := True;
      end;
       
      procedure TForm1.Button1Click(Sender: TObject);
      begin
        EnumModules(EnumModuleProc, nil);
      end;
       
      end.
       
      首先調(diào)用EnumModules(),它枚舉可執(zhí)行文件和在此可執(zhí)行文件個的任何包。傳遞給EnumModuls()的回調(diào)函數(shù)是EnumModuleProc(),這個函數(shù)把有關(guān)這個應(yīng)用程序中的每一個包的信息都填充到—個TTreeView組件中。大多數(shù)代碼都是針對TTreeView組件的設(shè)置代碼。函數(shù)GetPackDescription()返回包含在包子源文件中的描述性字符串。調(diào)用GetPackageInfo()來傳遞回調(diào)函數(shù)PackageInfoProc()。
      在PackageInfoProc()中,我們能夠處理包信息表中的信息。包中的每個單元和這個包所需要的每個包都要調(diào)用這個函數(shù)。這里,我們通過檢查Flaqs參數(shù)和NameType參數(shù)的值,再一次把這些(包信息表中的)信息填充TTreeView組件。對于其他信息,聯(lián)機幫助的“TpackageInfoProc”中都有解釋。
      1.14 小結(jié)
      包是Delphi/VCL體系的一個關(guān)鍵組成部分。通過學(xué)習(xí)如何使用包(不僅僅只是作為組件容器),就能夠開發(fā)出設(shè)計優(yōu)雅、領(lǐng)域?qū)挿旱捏w系結(jié)構(gòu)。
       
      本文來自CSDN博客,轉(zhuǎn)載請標(biāo)明出處:http://blog.csdn.net/michelsn/archive/2008/05/16/2450777.aspx

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多