[ 2005-6-8 13:00:08 | By: 抉擇不悔 ]
|
{$IFDEF WIN32} -- 這可不是批註喔!
對於Delphi來說﹐左右大括號之間的內容是批註﹐然而「{$」 (左括號後緊接着貨幣符號)對於Compiler(編譯器)而言並不是批註﹐ 而是寫給Compiler看的特別指示。 應用時機與場合 Delphi中有許許多多的Compiler Directives(編譯器指令)﹐ 這些編譯指令對於我們的程序發展有何影響呢? 它們又能幫我們什麼忙呢? Compiler Directive 對程序開發的影響與幫助, 可以從以下幾個方向來討論: Ø 協助除錯 Ø 版本分類 Ø 程序的重用與管理 Ø 設定統一的執行環境 協助除錯 穩健熟練的程序設計師經常會在開發應用系統的過程中﹐特別加入 一些除錯程序或者回饋驗算的程序﹐這些除錯程序對於軟件質量的 提升有極其正面的功能。然而開發完成的正式版本中如果不需要這 些額外的程序的話﹐要想在一堆程序中找出哪些是除錯用的程序並加以 刪除或設定爲批註﹐不僅累人﹐而且容易出錯﹐況且日後維護時這些除錯程序還用得着。 此時如果能夠應用像是$IFDEF的Compiler Directives ﹐就可以輕易的 指示Delphi要/不要將某一段程序編進執行文件中。 同時﹐Compiler本身也提供了一些錯誤檢查的開關﹐ 可以預先對程序中可能的問題提醒程序設計師注意﹐同樣有助於撰寫正確的程序。 版本分類 除了上述的除錯版本/正式版本的分類之外﹐對於像是「試用版」「普及版」 「專業版」的版本分類﹐也可以經由Compiler Directive的使用﹐爲最後的 產品設定不同的使用權限。其它諸如「中文版」「日文版」「國際標準版」 等全球版本管理方面﹐同樣也可以視需要指示Delphi特別連結哪些資源檔或 者是採用哪些適當的程序。以上的兩則例子中﹐各版本間只需共享同一份程序代碼即可。 Delphi 1.0 與 Delphi 2.0有許多不同之處﹐組件資源文件(.DCR)即是其中 一例﹐兩者的檔案格式並不兼容﹐在您讀過本文之後﹐相信可以寫出這樣的程序﹐ 指示Delphi在不同的版本採用適當的資源文件以利於組件的安裝。 {$IFDEF WIN32} {$R XXX32.DCR} {$ELSE} {$R XXXX16.DCR} {$EDNIF} 程序的重用與管理 經過前文的討論後﹐相信你已經不難看出Compiler Directives在程序管理上的應用價值。 對於原始程序的重用與管理﹐也是Compiler Directives 使得上力的地方. 舉例來說: Pascal-Style字符串是Delphi 1.0與 Delphi 2.0之間的明顯差異﹐除了原先的短字符串之外﹐ Delphi 2.0之後還多了更爲方便使用的長字符串﹐同時﹐系統也額外提供了像是 Trim() 這樣的字符串處理函式。假如您有一個字符串處理單元必須要同時應用於Delphi 1.0 與 2.0的項目時﹐編譯指示器可以幫你的忙。 此外﹐透過像是{$I xxxx} 這樣的 Compiler Directives﹐我們也可以適當的含入某些 程序, 同樣有助於切割組合我們的程序或編譯設定。 設定一致的執行環境 項目小組的成員間﹐必須有共同的環境設定﹐我很難預料一個小組成員間彼此有不同的 {$B}{$H}{$X}設定﹐最後子系統在併入主程序時會發生什麼事。 此外, 當您寫好一個組件或單元需要交予第三者使用時, 使用編譯指示器也可以保證元 件使用者與您有相同的編譯環境。 使用Compiler Directives 指令語法 Compiler Directives從外表看起來與批註頗爲類似, 與批註不同的是: Compiler Directives的語法格式都是以「{$」開始, 不空格緊接一個名 稱(或一個字母)表明給Compiler的特別指示, 再加上其它的開關或參數內容, 最後以右大括號作爲指令的結束, 例如: {$B+} {$R-} {$R MyCursor.res} 同時, 就如同Pascal的變量名稱與保留字一樣, Compiler Directives也是 不區分大小寫的。 從指令的語法格式來說Compiler Directives﹐可以進一步分類成以下三種格式: Ø 開關指令(Switch directives) 這類指令都是單一字母以不空格的方式連接「+」或「-」符號; 或者是開關名 稱以一個空格後連接「ON」或「OFF」來表示作用/關閉某一個編譯指示開關。例如: {$A+} {$ALIGN ON} 開關型的編譯指令不一定要分行寫, 它們可以組合在同一個編譯指示的批註符號之間, 但必須以逗號連接, 而且中間不可以有空格, 例如: {$B+,H+,T-,J+} 光標停留在程序編輯器的任一位置時按下Ctrl+O O, 完整的Compiler Directives 將會全部列於Unit的最上方。 Ø 參數指令(Parameter directives) 有些Compiler Directives需要在編譯名稱後面連接自定的參數(文件名稱或指定的記 憶體大小), 例如: {$R MyCursor.res}, 即在指示Delphi在編譯連結時, 含入「MyCursor.res」這個資源檔。 Ø 條件指令(Conditional directives) 指示Compiler在編譯的過程中, 按我們設定的條件, 選擇性的採用/排除不同區域的 程序代碼。 以下是一個條件編譯的例子, 第一與第三列是寫給Compiler看的,指示 Compiler在 __DEBUG這個條件名稱完成定義的情況才編譯ShowMessage()這列程序; 反之, 如果 __DEBUG 當時沒有定義的話, 這段程序幾乎與批註無異, Compiler對 它將視而不見。 {$IFDEF __DEBUG} ShowMessage(IntToStr(i)); {$ENDIF} 如何從IDE改變Compiler directives設定 從Delphi的IDE程序整合發展環境, 我們很方便的就可以修改各個compiler directives的 設定, 方法是: 從Delphi IDE主選單: Project/Options/Compiler, 直接核選/取消各個CheckBox。 值得注意的是, 改變一個項目的Compiler directives並不會影響其它的項目, 換言之, 各個項目都保有自己一套編譯指示。 假如您希望其它的項目也採用相同一套的Compiler directives, 在上述Project Options 對話盒的左下方有一個「Default」選項, 選取這個CheckBox之後, 雖然對於既有的項目 沒有作用, 但未來新的項目都將可以採用這組設定作爲默認值。 將Compiler directives寫入程序 透過Delphi的整合環境設定Compiler directives的確十分簡便, 但是許多情況下我們 仍然需要將Compiler directive直接加到程序中。至少有兩個原因支持我們這麼作: Ø 局部控制編譯條件 在Project/Options/Compiler中所作的設定, 影響所及是整個項目, 如果某一段程序 要特別使用不同的編譯設定, 就必須直接將編譯指示加到程序中。 下列這段取自Online Help的程序範例, 即應用了{$I}編譯指令局部控制在發生I/O錯誤 時不要舉發例外訊息, 這樣, 我們就可以編譯出一支在這段程序區域中不會產生I/O例外 訊息的檔案偵測函數。 function FileExists(FileName: string): Boolean; var F: file; begin {$I-} AssignFile(F, FileName); FileMode := 0; ( Set file access to read only } Reset(F); CloseFile(F); {$I+} FileExists := (IOResult = 0) and (FileName <> ''); end; { FileExists } Ø 程序的可移植性 我們都可能會用到其它公司或個人創作的unit或component, 也可能分享程序給其它人, 換句話說, 單元或程序可能會在不同的機器上編譯, 直接將Compiler directives加入 程序, 不僅可以免去程序使用前需要特別更改IDE的麻煩, 更重要的是解決了各個單元 間要求不同編譯環境的歧異。 注意事項 Compiler directives的作用與影響範圍 如同變量的可見範圍與生命週期, 在我們使用 Compiler Directives 時也必須注意各個 Compiler Directives 的作用範圍. Compiler Directives的作用範圍可分爲以下兩種: Ø 全域的 全域的Compiler Directives, 影響所及是整個項目; 我們稍早前提到經由Delphi IDE改變Compiler directives的方式就屬於全域的設定。 Ø 區域的 而區域的Compiler Directives 影響所及只從Compiler Directives 改變的那一行開始, 直到該程序單元(Unit)的結束或另一個相同的Compiler Directives 爲止, 對其他的程序單元並沒有影響。 也就是說, 如果在unit中特別加入Compiler directives, Compiler會優先採用區域 的設定, 然後纔是屬於項目層級的全域設定。 值得一提的是, 在程序中直接加入Compiler directives的最大作用範圍也只限於當時 那個單元而已, 對其他單元並沒有任何影響, 即使是以uses參考也是一樣。也就是說, 我們可以透過uses參考其它unit公開的變量與函式, 但是各個unit的編譯指令並不會互 相參考。 這項獨立的性質, 使得unit之間編譯環境的設定與關係變得十分簡潔, 例如Delphi 2.0的VCL都是在{$H+}的情況下編譯的, 因此, VCL中的字符串都是以長字符串的型態編譯 而成的, 有了這項編譯指令獨立的特性, 不論我們Prject中的設定爲何, 這些在VCL中 定義過的字符串都是長字符串。我們的Project也不會因爲uses了VCL中的unit而改變了自己 的設定。 因此, 在我們移交程序到網絡上時, 大可以放心的在程序中加入必要的Compiler directives, 別擔心, 即使別的unit以uses參考了我們的程序, 也不影響它自己原來 的設定。 如果我們自行以{$DEFINE _DEBUGVERSION}($DEFINE在稍後的個別指令介紹中將有說明) 定義了一個條件符號, 這個新的條件符號也是區域的, 換句話說, 它只從定義的那一 個單元的那一列之後才成立, 當然, 也只對目前這個單元有效. 由於自訂的條件符號只有區域的作用, 如果有好幾個程序單元都需要參考到某一個條 件符號, 怎麼辦呢? 嗯! 在每一個程序單元開頭處中都加上編譯指示是最直接的方式, 可是略嫌麻煩, 特別是編譯指示有變時, 要一一修正各個單元的設定內容, 很容易因爲 疏忽而出錯。 比較簡易可行的作法是從Delphi IDE整合發展環境的主選單-Project / Options / Directories/Conditional 的 Conditionals 中填入條件名稱。這樣, 相對於項目的 各個unit而言, 就有了一個全域的條件符號。 或者, 您也可以參考本文對於{$I}這個Compiler Directive的說明。 我在那裏指出了 另一個彈性的解決方式。 修改過編譯指令後, 建議Build All過一次程序 請試一試這個程序: procedure TForm1.Button1Click(Sender: TObject); begin // ifopt是用來偵測某一個編譯開關的作用狀態 {$ifopt H+} ShowMessage('H+'); {$else} ShowMessage('H-'); {$endif} end; 在我們執行上述程序時, 在Delphi預設的是$H+時, ShowMessage()會在畫面上會顯示 「H+」, 執行過後, 讓程序與form的內容與位置保留不變, 單純的從主選單: Project/Options/Compiler, 將Huge Strings的核對方塊清除($H-), 然後按下F9執行, 咦! 怎麼還是看到「H+」?! 那是因爲Delphi只會在unit內容經過異動後纔會重新將.PAS編譯成.DCU, 在我們的例子中, 程序並沒有變動, .DCU當然也沒有重新產生, 最後.EXE的這個部分 自然也是沒什麼變化。 所以, 要解決這個問題, 只要以Delphi IDE主選單Project/Build All指示Delphi 重新編譯全部的程序即可。因此, 如果您從Delphi IDE修改過Compiler Directives後, 記得要Build All喔! 不應該用來作爲程序執行流程控制 在程序中, 我們可以使用if敘述, 根據執行當時的情況控制程序執行時的流程, 但我們不可以用{$IFDEF}來作同樣的事, 爲什麼? 從上述的說明, 相信您不難發現, Compiler directives會對最後.EXE的內容發生直接的影響, 應用像是{$IFDEF}指示 Compiler的結果, 幾乎可以視同授權Compiler在編譯的那個時候自動選用/捨棄程序 到.DCU, .EXE中, 換句話說, 在程序編譯完成時, 會執行到那一段程序已成定局了, 我們自然不能用它來作程序流程的控制。 條件編譯的巢套最多可以16層 在使用{$IFDEF}…{$ENDIF}條件編譯我們的程序時, 一個{$IFDEF}中可以再包含另一 個{$IFDEF}, 但深度最多隻能16層, 雖然是個限制, 但以正常的情形來說, 這應該已 經足夠了。 有些Compiler directives不應寫在Unit中 對於像是{$MINSTACKSIZE}{$MAXSTACKSIZE}管理堆棧大小, 或者像是{$APPTYE} 指示程序編譯成圖形/文字模式的Compiler directives, 只能寫在.DPR中, 寫在Unit 中是沒有效果的。 建議事項 確定您瞭解指令的影響 由於編譯指令的影響是如此直接與深遠, 在修改與應用某一個Compiler directive時, 請確定您已經瞭解其含意與影響。 打開全部的偵錯開關 Delphi有關偵錯的Compiler directives如下: Ø $HINTS ON Ø $D+ Ø $L+ Ø $Q+ Ø $R+ Ø $WARNINGS ON 各指令的用法您可以參閱本章稍後對個別指令的說明, 全部打開這些開關吧! 這樣不僅讓您可以使用Delphi IDE的除錯器, 對於程序編譯與執行過程中的問題, Delphi也會適時的反應, 有助於寫作正確的程序。 此處有一個迷思有待澄清—「加入Dubug信息會不會讓執行文件變大變慢啊?」, 不一定。 對於們像是$D+, $L+, $HINTS ON這些開關, 打開後, Delphi在編譯時的確會額外加入 一些除錯信息, 使得.DCU的檔案變大, 對於.EXE的檔案大小並沒有影響; 同時, 程序的執行速度也沒有改變, 還可以應用IDE的除錯器trace我們的程序, 值得應用。 對於像是$Q, $R等Compiler directive, 的確會影響執行文件的大小與速度, 然而這並 不動搖我們在研發期間使用它們的決定, 請想想看, 值得爲這一點點的速度放棄程序 的正確性嗎? 當然, 程序開發完成後, 正式出貨的版本, 可以關閉這兩個開關。 如果您寫好了一個組件, 而且只預備提供.DCU, 由於沒有.PAS可供Delphi IDE的Debugger 追蹤程序, 除錯開關反而應該在組件脫手前關閉並重新編譯.DCU, 否則會引起使用者那 邊找不到檔案的例外訊息。 善用{$I} {$I FileName}是一個非常有用的Compiler directive.應用這個指令, 我們可以彈性的 管理Compiler directive的設定。 條件名稱請加入前導符 不知道您有沒有這個疑問 -- 如果用{$DEFINE}定義的條件名稱與變量名稱相同時會發 生什麼事? procedure TForm1.Button1Click(Sender: TObject); var TEST: integer; begin {$DEFINE TEST} {$IFDEF TEST} ShowMessage('Test'); {$ENDIF} end; 以上的程序編譯與執行都沒有問題, 但條件名稱與變量名稱重複畢意容易讓人混淆, 因此, 假如能適當的爲編譯條件名稱之前加上諸如底線(_TEST), 程序會比較容易閱讀。 設定一致的編譯環境 在您瞭解了Compiler Directives之後, 請立即開始着手修改您IDE中有關編譯指示 的各個開關並且設爲Default, 這樣, 日後您的項目乃至整個研發小組都將擁有 共同一致的編譯環境, 對於寫出來的程序會以何種方式編譯連結都瞭然於胸, 直接有助於子系統順利併入主系統中。 個別指令說明 有了之前對於Compiler directives的觀念之後, 接下來的這一節我將一一 介紹幾個常用的Compiler Directive的用法與注意事項, 您可以從這一 節中學到更多有關Compiler directives的知識與使用細節。 {$A+} 字段對齊 在{$A+}(默認值)的情形下, 如果沒有使用 packed 修飾詞宣告的 record 型態, 其字段會以CPU可以有效存取的方式向 1. 2. 4 等邊界對齊, 以獲取最佳的存取速度。以下列的程序示例來說: {$A+} type MyRecord = record ByteField: byte; IntegerField: integer; end; … procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage(IntToStr(SizeOf(MyRecord))); end; ShowMessage在{$A+}時顯示的結果是:「8」; 倘若是{$A-}, 那所得的結果是「5」, 按理說, Byte應該只要一個byte就足夠了, 但是考慮到硬件的執行特性, 經過對齊後的record會有比較好的執行速度。 有關這個Compiler Directive要注意的事項是: 不管{$A}的開關是ON或OFF, 使用packed修飾過的記錄宣告, 是一定不會對齊的. 例如: MyRecord = packed record // 不會對齊的記錄宣告方式 {$APPTYPE GDI} 應用程序型態 一般的情形下, Delphi會以{$APPTYPE GUI}的方式產生一個圖形的使用者接口程序, 如果您需要產生一個文字屏幕模式的程序, 那可以經由: Ø 在.DPR中加入{$APPTYPE CONSOLE} Ø 從主選單: Project/Options/Linker/EXE and DLL Options, 核取 「Generate Console Application」Check Box。 其它有關這個Compiler Directive的注意事項有: Ø $APPTYPE不能應用在DLL的項目或單一的程序單元(Unit), 它只對.EXE有意義。 而且只有寫在.DPR中才有作用。 Ø 我們可以應用System程序單元中的IsConsole函數在程序執行時偵測應用程序 的類型。 Ø 參閱Object Pascal手冊第十三章可以知道更多有關Console Mode Application的信息。 {$B-} 布爾評估 請看以下的程序: if (Length(sCheckedDateString) <> 8) or EmptyStr(sCheckedDateString) or (sCheckedDateString = ' . . ') or (sCheckedDateString = ' / / ') then begin Result := True; Exit; end; 假如sCheckedDateString的字符串內容是「85/12/241」(長度9)的話, 以上的if述句, 其實在第一個邏輯判斷時就已經知道結果了, 即使不看後來的邏輯運算結果也知道 整個式子會是真值。 假如您希望對整個邏輯表達式進行完整的評估 -- 儘管結果已知, 後來的邏輯運算 也不影響整個的結果時仍要全部評估過, 請將這個Compiler directives設爲{$B+}, 反之, 請設爲{$B-}, 系統的默認值是{$B-}。 {$D+} 除錯信息 當程序以{$D+}(默認值)編譯時, 我們可以用Delphi整合發展境境的Debugger設定 斷點, 也可以使用Trace Into或Trace Info追蹤程序的執行過程, 值得注意的是, 以{$D+}編譯的程序, 執行的速度並不會受到影響, 只不過編譯過的DCU的檔案長度會 加大, 但EXE檔的大小不變。 {$DEFINE條件名稱} 定義條件名稱 隨着您對Compiler Directives的瞭解與應用程度的加深, 您會發現這是一個非常實 用的編譯指示。 經常, 我們會因爲除錯需要﹑區別不同版本等緣故, 希望選擇性的採用或排除某一 段程序, 這個時候, 我們就可以先以$DEFINE定義好一個條件名稱(Conditional name), 然後配合{$IFDEF條件名稱}…{$ELSE}…{$ENDIF}指示編譯器按指定的條件名稱之有無 來選擇需要編譯的程序。以下列的程序片斷來說: {$DEFINE _ProVersion} … procedure TForm1.Button1Click(Sender: TObject); begin {$IFDEF _Proversion} frmPrint.ShowModal; // A {$ELSE} ShowMessage('很抱歉, 試用版不提供打印功能'); {$ENDIF} end; 編譯器將會選擇編譯上述A的那列程序, 日後, 如果我們需要編譯「簡易版」 的程序版本時, 只要: Ø 將{$DEFINE _ProVersion}那列整個刪掉。 Ø 或者, 將{$DEFINE _ProVersion}改成{-$DEFINE _ProVersion}, 讓它變成普通的批註 Ø 或者, 在{$DEFINE _ProVersion}的下一列加上{$UNDEF _ProVersion}, 解除_ProVersion這個條件名稱的定義。 這樣, 由於_ProVersion這個條件名稱未定義的緣故, Compiler就只會選擇 {$ELSE}下的那段程序, 重新編譯一次, 不需費太多力氣, 很容易的就可以製作出 「簡易版」了, 省去了要同時維護兩份程序的麻煩。 使用$DEFINE時的其它注意事項如下: Ø 以{$DEFINE}定義的條件名稱都是區域的。換句話說, 它的作用範圍只在 當時所在的單元纔有效, 即使定義在unit的interface, 由其它的unit以uses參考也沒有效, 仍然只有在目前的unit有作用。 Ø 此外, 它的作用範圍是從定義起, 到unit結尾或者以{$UNDEF}解除爲止。 Ø 如果程序單元中已經用{$DEFINE}定義了一個條件名稱, 而且也沒有用 {$UNDEF}解除定義, 重新{$DEFINE}一個同樣名稱並沒有作用, 換句話說, 它們是同一個. Ø 假如需要一個全域的條件名稱, 您可以:主選單: Project / Options / Directories/ Conditional 的 Conditionals 中填入條件名稱。 Ø 以下的標準條件名稱, 是Delphi 2.0已經預先預備好的, 我們可以直接引用, 同時, 它們都是全域的, 任何Unit都可以參照得到。 Ø VER90: Delphi Object Pascal的版本編號。90表示9.0版, 日後若出現9.5 版時, 也會有VER95的定義。 Ø WIN32: 指出目前是在Win32(95, NT)作業環境 Ø CUP386: 採用386(含)以上的CPU時, 系統會提供本條件名稱。 Ø CONSOLE: 此符號會於應用程序是在屏幕模式下編譯時才定義。 {$DESCRIPTION 描述內容} 應用{$DESCRIPTION}可以指定加入一段文字到.EXE或.DLL表頭的模塊描述進入點 (module description entry)中﹐通常我們會用這個Compiler Directive加入應 用程序的名稱與版本編號到.EXE中。例如: {$DESCRIPTION Dchat Version 1.0} {$X+} 擴充語法 這是爲了與之前的Pascal版本前向兼容的編譯指令, 雖然設定這個開關型的指令仍有 作用, 但筆者建議您大可保留系統的默認值{$X+}, 在{$X+}下: Ø 不需要非得準備一個變量接受函數的傳回值, 換句話說, 函數的傳回值可以 捨棄, 此時, 就可以像是呼叫程序一樣, 很方便的呼叫函數。 Ø 支持Pchar型態與零基的字符數組作爲C語言以Null結尾的字符串。 {$HINTS OFF} 提示訊息 打關{$HINTS}開關後, Compiler會提示程序設計師注意以下的情況: Ø 變量定義了卻沒有使用 Ø 程序流程中不會執行的for或while循環 Ø 只有存入沒有取用的指定敘述。意思是說, 指定數據到某一個變量之後, 卻沒有任何的程序參考取用這個變量值。 {$HINTS ON} procedure MyTest; const _False = False; var I, J: integer; begin if _False then for I := 1 to 3 do ; J := 3; end; {$HINTS OFF} 由於程序簡單, 在兩個$HINTS中間的程序, 我們不難看出: Ø for循環不會執行到, I變量也因此不曾用過 Ø J := 3寫了等於白寫 但在程序越寫越長而日趨復雓時, 藉由{$HINTS ON}的協助, 比較容易察覺出程序的毛病。 {$IFDEF} {$IFNDEF} 請參閱{$DEFINE}的說明, 在此補充說明{$IFNDEF}, 以下列程序來說, 即在指示Compiler 在_Test未定義時, 條件編譯ShowMessage()那列程序: {$IFNDEF _TEST} ShowMessage('_TEST not define'); {$ENDIF} 換言之, {$IFNDEF}相當於{$IFDEF}的{$ELSE}部分。 {$IFOPT 開關} 到底{$B}是開着或關着呢? 如果我們想要指示Compiler按照某一個編譯開關當時的狀態作 我們指定的事, 應該該怎麼做呢? 這時, {$IFOPT}就派得上用場了。例如: {$R+} {$Q-} // 特別指定爲Q- {$IFOPT R+} // 如果 Range Check 是開啓的話 ShowMessage('程序是在 Range Check 開啓狀態下編譯的'); // 這個 Q+ 也會在 IFOPT R+ 成立時才通知 Compiler {$Q+} {$ENDIF} {$IFOPT Q+} ShowMessage('Q 也變成開啓狀態了'); {$ENDIF} ShowMessage() 與 {$Q+}會在$R+ 的情形下才編譯, 因此, 雖然我們事前特別指示爲{ $Q-}, 第二個的ShowMessage()在程序執行時也可以看到「Q 也變成開啓狀態了」。 {$IMAGEBASE檔案基礎地址} 這個Compiler directive用來指示.EXE或.DLL加載時的預設地址。例如: {$IMAGEBASE $00400000}。如果指定加載的地址空間之前已經有其它模塊佔用了, Windows會爲.EXE重 新配置一個新的加載地址。對於.DLL來說, 如果可以成功配置到我們寫在{$IMAGEBASE} 的地址, 由於不需要重新配置內存地址, 不僅加載的速度較快, 如果有其它程序也參 照到這個DLL的話, 也可以減少加載時間與內存的消耗。 使用這個Compiler directive時需要注意的事項有: Ø 指定的敘述必須是一個大於$00010000的32位整數數值, 同時, 較低位置的 16個位必須是零。 Ø DLL的建議地址範圍從$40000000到$7FFFFFFF, 該範圍的地址可以同時適用於 Windows 95與Windows NT。 {$I文件名稱} 含入檔案 以Delphi IDE修改Compiler directives的確相當方便, 但往往我們仍然需要將Compiler directives直接加入程序中, 可是當我們這樣作之後不用多久, 就會發現要一一重新 修改各個單元中的這些Compiler directives時, 實在是既無聊而又容易出錯的工作。 這時候, 假如您一開始就採用{$I文件名稱}, 整件事就會變得很簡單。怎麼做呢? 讓我用一個例子告訴您 -- Ø 先用一般的文書編輯器建好一個MySet.inc的普通文本文件, 內容爲: {$H+} {$DEFINE _Proversion} Ø 在我們的程序中, 加入一列{$I MySet.inc}, 例如: unit Unit1; {$I MySet.inc} interface … implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); begin {$IFDEF _ProVersion} ShowMessage('專業版'); {$Else} ShowMessage('只有專業版纔有此功能'); {$ENDIF} end; … 這是子程序的觀念嘛! 沒錯, 就是這麼簡單而已, 以後如果有任何變化, 修改MySet.INC, 然後Project/Buile All即可, 實在是夠簡單的了。 基本動作會了之後, 讓我告訴你多一點有關{$I文件名稱}的事。 Ø 一旦應用了{$I文件名稱}, 幾乎等於Compiler在編譯時, 讓Compiler將這個檔 案的內容貼進我們的程序中的那個位置。 Ø 如果沒有註明擴展名, Delphi預設這個檔案是.PAS。 Ø 如果在項目的目錄中找不到這個檔案的話, Delphi會陸續搜尋Tools/Options /Library中的Library Path中的目錄。 另外, 當您寫作了一個DLL, 使用者在使用其中的函數前必須宣告過, 如果能夠一併提 供這些函數的宣告文件, 使用者只要一行{$I xxx} 即可, 是不是很方便呢? {$I+} EInOutError檢查 在{$I+}(系統默認值)狀態編譯的程序, 一旦發生I/O錯誤時, 將會舉發一個EInOutError 的例外, 假如我們在特定的情況下不希望出現這個例外的訊息時(例如前文提到的偵測檔 案是否存在函數), 可以將這個Compiler directive設爲{$I-}, 此時, 程序執行時是否發 生過錯誤,程序設定師必須自行檢查IOResult這個公用變量的值, 如果是零, 表示沒有錯誤, 非零的錯誤代碼含意請詳查Online help。 {$L文件名稱} 連結目標文件 如果您有一個.OBJ文件要併入Delphi的程序時, 可以在程序中加入: {$L OTHER.OBJ} 這樣, 就可以使用OTHER.OBJ中的程序了, 值得注意的是, 函數或程序在呼叫前, 仍然必須用external宣告過, 表明這些模塊是來自「外部」的函式。 舉例來說, 筆者有一份由Keypro廠商提供的.OBJ檔, 在使用時, 相關的程序如下: … {$L hasptpw.obj} {$F+} procedure hasp (Service, SeedCode, LptNum, Pass1, Pass2 : word; var p1,p2,p3,p4 : word); external; {$F-} … 經過{$L hasptpw.obj}宣告之後, 程序的其它部分就可以直接呼叫原先位於 hasptpw.obj 中的hsap這個程序了。 {$L+} 區域符號信息 在{$L+}時, Delphi會額外加入一些區域符號信息, 這使得我們可以應用Delphi IDE中的 View/Call Stack, View/Watch在程序執行時檢視變量內容與函式呼叫的關係。 應用這個Compiler directive的注意事項有: Ø {$D-}時, {$L+}不會有作用。 Ø 使用{$L+}, 只會加大.DCU的檔案大小, 對.EXE的大小與執行速度並沒有影響。 {$H+} 長字符串宣告 Delphi 2.0之後, 字符串多了一個更爲好用的長字符串, 不僅沒有數據長度255的限制, 與C語言慣用的Null-terminated string兼容性也大爲提高。 使用{$H}時的注意事項有: Ø {$H+}的編譯情形下, 以string定義的字符串變量都是長字符串, 請注意, 字符串是否爲長字符串是在字符串定義時決定的, 例如: procedure TForm1.Button1Click(Sender: TObject); {$H-} var s: string; begin {$H+} s := '測試一下長字符串'; Windows.MessageBox(0, pchar(s), '訊息', 64); end; 由於var前{$H-}的緣故, 雖然在begin後我們立即設定爲{$H+}, 但s仍然是一個短字符串, 所以, 自然不能像是長字符串一樣, 以pchar強制型別轉換後當作Null-terminated字符串使 用。 Ø 承上, 不管程序是{$H+}或{$H-}, 只要字符串是以長字符串方式定義的, 即使 begin..end;中改成{$H-}, 該字符串的操作仍然具有長字符串的特性。 因此, 由於VCL中的字符串都是長字符串, 即使我們的程序是{$H-}, 仍然可以拿它們當長 字符串來使用。 Ø 不論{$H}的狀態如何, 以AnsiString定義的一定是長字符串; 以string[n]或 ShortString定義的一定是短字符串。 {$M 16386, 1048576} 內存配置大小 要改變唯迭(Stack)內存配置大小時, 我們可以有以下兩種選擇: Ø 使用{$MINSTACKSIZE數字}, {$MAXSTACKSIZE數字}, 分別指定最小.最大的Stack 大小. Ø 或者使用{$M min, max}, 同時指定最小與最大的值。 使用這些Compiler directive時的注意事項有: Ø 寫在.DPR中才有效果。 Ø 堆棧的最小數字必須介於1024至21474835647之間。 Ø 堆棧的最大數字必須介於$MINSTACKSIZE至21474835647之間。 Ø 當內存不足而無法滿足最小的堆棧大小時, Windows會在啓動這程序時 提出錯誤報告。 Ø 當程序要求的內存超過$MINSTACKSIZE的大小時, 將舉發EStackOverflow例外。 {$Z1} 最小列舉大小 這個Compiler directive將影響儲存列舉型態時最小所需的byte數值。如果宣告列舉型 態時, 數值不大於256, 而且也在系統預設的{$Z1}時, 這個列舉型態只佔用一個byte儲 存的。{$Z2}時, 以兩個byte儲存, {$Z4}時, 以四個byte儲存。因爲C語言通常以WORD或 DWORD儲存列舉型態, 如果您的程序需要與C、C++溝通時,{$Z2}{$Z4}就很管用了 {$Z+}, 與{$Z-}分別對應到{$Z1}和{$Z4}。 {$P+} 開放字符串參數 在程序與函數宣告時, 其中的字符串自變量, 在{$P+}時表示是Open string; {$P-}時, 只是一般的字符串變量而已。這個Compiler directive只在{$H-}時有作用。 {$O+} 最佳化開關 建議您維持{$O+}的系統默認值。開啓這個Compiler directive, Delphi會自動進行 最佳化處理, 程序可以因此跑得快一些, 您可以放心的打開這個編譯開關, Delphi 不會進行不安全的最佳化而使您的程序執行時發生錯誤。 {$Q-} 滿溢檢查, {$R-} 範圍檢查 {$Q}與{$R}是一組搭配使用的Compiler directive, 它們將檢查數值或數組的 操作是否在安全的邊界中, {$Q}會檢查整數運算(如+, -, Abs, Sqr, Pred, Succ等), 而{$R}則檢查字符串與數組的存取是否超出合理邊界範圍等問題。 使用這兩個Compiler directives會因爲這些檢查動作而降低程序執行的速度, 通常我們會在除錯時開啓這兩個編譯開關。 {$U-} Pentium CPU浮點運算安全檢查 還記得早期Pentium CPU浮點運算不正確的事吧? 這批CPU應該回收得差不多了, 但如果您仍然不確定程序會不會意外的遇到漏網之魚或黑心牌經銷商的話, 請將這個 Compiler directives設爲{$U+}。 根據Borland手冊的說明, 如果CPU是沒有暇疵的, 設定{$U+}對於執行速度只有輕微 的影響; 但如果是問題CPU, 浮點的除法速度會因此慢上三倍, 是否要打開這個開關, 您心中應該已有取捨。 {$R文件名稱} 資源檔 在您還沒有開始學習Compiler directives之前, 這個指令就已經出現在您的程序中 了,每次開出一個新的form時, Delphi自動在Implement開頭部分中加入{$R *.DFM}, 在Project/Source中看到的.DPR程序中也有{$R *.RES}, 這些是什麼意思呢? 意思是說, 在編譯連結時, 含入與項目主檔名同名的.RES, 以及與form unit檔案同名的.DFM等資源檔。 如果您需要在程序中使用額外的資源(例如: 自訂鼠標指針), 請注意不要自行以Resouse WorkShop或Image Editor等資源編輯器更改這些與Project或Form同名的資源檔, 改變這些同名的檔案不僅無效, 可能還有不可預期的錯誤。因些, 您應該在另外一個資源檔中存放這些資源, 並於{$R}中寫明檔案的名稱將其連結進來, 例如: {$R MyCursor.res} {$T-} @指針型態檢查 應用@操作數可以取得變量的地址, 在{$T-}時, 以@取得是一個無型別的指標(Pointer)。 反過來說, 在{$T+}時, 是有型別的指標, 假定I是一個integer的變量, @I所得到的即是 相當於^Integer(Pointer of Integer)的指標。 {$WARNINGS ON} 編譯器警告 這個Compiler directive與{$HINTS}的作用類似, 同樣會對程序的可能問題提出警告。 不同的是, 在{$WARNINGS ON}時, Compiler會對未初始化的變量、沒有傳回值的函數、 建構抽象對象等情況提出警告。 {$J-} 型態常數只讀 從前筆者曾經對以下的程序產生過疑惑: {$J+} procedure TForm1.Button1Click(Sender: TObject); const VarConst: integer = 4; begin VarConst := 5; ShowMessage(IntToStr(VarConst)); end; const不是常數嗎? 爲什麼可以改呢? 在先前的Pascal版本中, 以 const VarName: DataType = const value; 定義的具型態常數的確是可以改的, 假如您希望常數就是常數, 它不應該允許修改, 請將這個Compiler directive設爲{$J-} 不論是{$J+}或{$J-}, 以const VarName = const value; 定義的常數( 沒有加上型別宣告), 是一個真正的常數, 其它的程序不可以改變其內容。 其實{$J+}時還有一個妙用, 那就是宣告出類似C語言static的變量, 換句話說, 產生了一個與Application相同生命週期的變量。在這種情形下, 變量只在第一 次使用時纔會建立, 函數或程序結束時, 該變量也不會消滅, 下一次再呼叫到這個函數 或程序時, 我們仍然可以參考到上次執行結束時的值。讓我們試一下這個例子: {$J+} procedure TForm1.Button1Click(Sender: TObject); const i: integer = 0; begin ShowMessage(IntToStr(i)); Inc(i); ShowMessage(IntToStr(i)); end; 第一次執行時, 我們分別會看到「0」「1」, 再點一次這個按鈕時, 看到的將是「1」「2」。 |