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