DELPHI的编译指令 {$IFDEF WIN32} -- 这可不是批注喔! 应用时机与场合 Delphi中有许许多多的Compiler Directives(编译器指令)﹐这些编译指令对于我们的程序发展有何影响呢? 它们又能帮我们什么忙呢? Compiler Directive 对程序开发的影响与助益, 可以从以下几个方向来讨论: 协助除错 稳健熟练的程序设计师经常会在开发应用系统的过程中﹐特别加入一些除错程序或者回馈验算的程序﹐这些除错程序对于软件品质的提升有极其正面的功能。然而开发完成的正式版本中如果不需要这些额外的程序的话﹐要想在一堆程序中找出哪些是除错用的程序并加以删除或设定为批注﹐不仅累人﹐而且容易出错﹐况且日后维护时这些除错程序还用得着。 此时如果能够应用像是$IFDEF的Compiler Directives ﹐就可以轻易的指示Delphi要/不要将某一段程序编进执行文件中。 版本分类 除了上述的除错版本/正式版本的分类之外﹐对于像是「试用版」「普及版」「专业版」的版本分类﹐也可以经由Compiler Directive的使用﹐为最后的产品设定不同的使用权限。其它诸如「中文版」「日文版」「国际标准版」等全球版本管理方面﹐同样也可以视需要指示Delphi特别连结哪些资源档或者是采用哪些适当的程序。以上的两则例子中﹐各版本间只需共享同一份程序代码即可。 Delphi 1.0 与 Delphi 2.0有许多不同之处﹐组件资源文件(.DCR)即是其中一例﹐两者的档案格式并不兼容﹐在您读过本文之后﹐相信可以写出这样的程序﹐指示Delphi在不同的版本采用适当的资源文件以利于组件的安装。 {$IFDEF WIN32} 程序的重用与管理 经过前文的讨论后﹐相信你已经不难看出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的特别指示, 再加上其它的开关或参数内容, 最后以右大括号作为指令的结束, 例如: 同时, 就如同Pascal的变量名称与保留字一样, Compiler Directives也是不区分大小写的。 开关指令(Switch directives) {$A+} 开关型的编译指令不一定要分行写, 它们可以组合在同一个编译指示的批注符号之间, 但必须以逗号连接, 而且中间不可以有空格, 例如: 光标停留在程序编辑器的任一位置时按下Ctrl+O O, 完整的Compiler Directives将会全部列于Unit的最上方。 参数指令(Parameter directives) 条件指令(Conditional directives) 以下是一个条件编译的例子, 第一与第三列是写给Compiler看的,指示 Compiler在 __DEBUG这个条件名称完成定义的情况才编译ShowMessage()这列程序;反之, 如果 __DEBUG 当时没有定义的话, 这段程序几乎与批注无异, Compiler对它将视而不见。 如何从IDE改变Compiler directives设定 从Delphi的IDE程序整合发展环境, 我们很方便的就可以修改各个compiler directives的设定, 方法是: 从Delphi IDE主选单: Project/Options/Compiler, 直接核选/取消各个CheckBox。值得注意的是, 改变一个项目的Compiler directives并不会影响其它的项目, 换言之, 各个项目都保有自己一套编译指示。 假如您希望其它的项目也采用相同一套的Compiler directives, 在上述ProjectOptions对话盒的左下方有一个「Default」选项, 选取这个CheckBox之后, 虽然对于既有的项目没有作用, 但未来新的项目都将可以采用这组设定作为默认值。 透过Delphi的整合环境设定Compiler directives的确十分简便, 但是许多情况下我们仍然需要将Compiler directive直接加到程序中。至少有两个原因支持我们这么作: 局部控制编译条件 下列这段取自Online Help的程序范例, 即应用了{$I}编译指令局部控制在发生I/O错误时不要举发例外讯息, 这样, 我们就可以编译出一支在这段程序区域中不会产生I/O例外讯息的档案侦测函数。 function FileExists(FileName: string): Boolean; 程序的可移植性 我们都可能会用到其它公司或个人创作的unit或component, 也可能分享程序给其它人, 换句话说, 单元或程序可能会在不同的机器上编译, 直接将Compiler directives加入程序, 不仅可以免去程序使用前需要特别更改IDE的麻烦, 更重要的是解决了各个单元间要求不同编译环境的歧异。 注意事项 Compiler directives的作用与影响范围 如同变量的可见范围与生命周期, 在我们使用 Compiler Directives 时也必须注意各个Compiler Directives 的作用范围. 全域的 区域的 值得一提的是, 在程序中直接加入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); 在我们执行上述程序时, 在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喔! 不应该用来作为程序执行流程控制 条件编译的巢套最多可以16层 有些Compiler directives不应写在Unit中 建议事项 打开全部的侦错开关 $HINTS ON 此处有一个迷思有待澄清-「加入Dubug信息会不会让执行文件变大变慢啊?」, 不一定。 对于们像是$D+, $L+, $HINTS ON这些开关, 打开后, Delphi在编译时的确会额外加入一些除错信息, 使得.DCU的档案变大, 对于.EXE的档案大小并没有影响; 同时, 程序的执行速度也没有改变, 还可以应用IDE的除错器trace我们的程序, 值得应用。 对于像是$Q, $R等Compiler directive, 的确会影响执行文件的大小与速度, 然而这并不动摇我们在研发期间使用它们的决定, 请想想看, 值得为这一点点的速度放弃程序的正确性吗? 当然, 程序开发完成后, 正式出货的版本, 可以关闭这两个开关。 如果您写好了一个组件, 而且只预备提供.DCU, 由于没有.PAS可供Delphi IDE的Debugger追踪程序, 除错开关反而应该在组件脱手前关闭并重新编译.DCU, 否则会引起使用者那边找不到档案的例外讯息。 善用{$I} 条件名称请加入前导符 procedure TForm1.Button1Click(Sender: TObject); 以上的程序编译与执行都没有问题, 但条件名称与变量名称重复毕意容易让人混淆, 因此, 假如能适当的为编译条件名称之前加上诸如底线(_TEST), 程序会比较容易阅读。 设定一致的编译环境 个别指令说明 {$A+} 字段对齐 {$A+} ShowMessage在{$A+}时显示的结果是:「8」; 倘若是{$A-}, 那所得的结果是「5」, 按理说, Byte应该只要一个byte就足够了, 但是考虑到硬件的执行特性, 经过对齐后的record会有比较好的执行速度。 有关这个Compiler Directive要注意的事项是: 不管{$A}的开关是ON或OFF, 使用packed修饰过的记录宣告, 是一定不会对齐的. 例如: MyRecord = packed record // 不会对齐的记录宣告方式 {$APPTYPE GDI} 应用程序型态 在.DPR中加入{$APPTYPE CONSOLE} $APPTYPE不能应用在DLL的项目或单一的程序单元(Unit), 它只对.EXE有意义。而且只有写在.DPR中才有作用。 请看以下的程序: if (Length(sCheckedDateString) <> 8) 假如sCheckedDateString的字符串内容是「85/12/241」(长度9)的话, 以上的if述句, 其实在第一个逻辑判断时就已经知道结果了, 即使不看后来的逻辑运算结果也知道整个式子会是真值。 假如您希望对整个逻辑表达式进行完整的评估 -- 尽管结果已知, 后来的逻辑运算也不影响整个的结果时仍要全部评估过, 请将这个Compiler directives设为{$B+}, 反之, 请设为{$B-}, 系统的默认值是{$B-}。 {$D+} 除错信息 {$DEFINE条件名称} 定义条件名称 经常, 我们会因为除错需要﹑区别不同版本等缘故, 希望选择性的采用或排除某一段程序, 这个时候, 我们就可以先以$DEFINE定义好一个条件名称(Conditional name), 然后配合{$IFDEF条件名称}#{$ELSE}#{$ENDIF}指示编译器按指定的 条件名称之有无来选择需要编译的程序。 以下列的程序片断来说: {$DEFINE _ProVersion} 编译器将会选择编译上述A的那列程序, 日后, 如果我们需要编译「简易版」的程序版本时, 只要: 将{$DEFINE _ProVersion}那列整个删掉。 使用$DEFINE时的其它注意事项如下: 以{$DEFINE}定义的条件名称都是区域的。换句话说, 它的作用范围只在当时所 在的单元才有效, 即使定义在unit的interface, 由其它的unit以uses参考也没有 效, 仍然只有在目前的unit有作用。 应用{$DESCRIPTION}可以指定加入一段文字到.EXE或.DLL表头的模块描述进入点 (module description entry)中﹐通常我们会用这个Compiler Directive加入应 用程序的名称与版本编号到.EXE中。 例如: {$DESCRIPTION Dchat Version 1.0} {$X+} 扩充语法 这是为了与之前的Pascal版本前向兼容的编译指令, 虽然设定这个开关型的指令仍有作用, 但笔者建议您大可保留系统的默认值{$X+}, 在{$X+}下: 不需要非得准备一个变量接受函数的传回值, 换句话说, 函数的传回值可以舍 弃, 此时, 就可以像是呼叫程序一样, 很方便的呼叫函数。 打关{$HINTS}开关后, Compiler会提示程序设计师注意以下的情况: 变量定义了却没有使用 由于程序简单, 在两个$HINTS中间的程序, 我们不难看出: for循环不会执行到, I变量也因此不曾用过 J := 3写了等于白写 但在程序越写越长而日趋复雓时, 藉由{$HINTS ON}的协助, 比较容易察觉出程序 的毛病。 {$IFDEF} {$IFNDEF} 请参阅{$DEFINE}的说明, 在此补充说明{$IFNDEF}, 以下列程序来说, 即在指示 Compiler在_Test未定义时, 条件编译ShowMessage()那列程序: {$IFNDEF _TEST} 换言之, {$IFNDEF}相当于{$IFDEF}的{$ELSE}部分。 {$IFOPT 开关} 到底{$B}是开着或关着呢? 如果我们想要指示Compiler按照某一个编译开关当时的状态作我们指定的事, 应该该怎么做呢? 这时, {$IFOPT}就派得上用场了。例如: {$R+} 这个Compiler directive用来指示.EXE或.DLL加载时的预设地址。例如: {$IMAGEBASE $00400000}。如果指定加载的地址空间之前已经有其它模块占用了, Windows会为.EXE重新配置一个新的加载地址。对于.DLL来说, 如果可以成功配置到我们写在{$IMAGEBASE}的地址, 由于不需要重新配置内存地址, 不仅加载的速度 较快, 如果有其它程序也参照到这个DLL的话, 也可以减少加载时间与内存的消 耗。 使用这个Compiler directive时需要注意的事项有: 指定的叙述必须是一个大于$00010000的32位整数数值, 同时, 较低位置的16个位必须是零。 以Delphi IDE修改Compiler directives的确相当方便, 但往往我们仍然需要将Compiler directives直接加入程序中, 可是当我们这样作之后不用多久, 就会发 现要一一重新修改各个单元中的这些Compiler directives时, 实在是既无聊而又 容易出错的工作。这时候, 假如您一开始就采用{$I文件名称}, 整件事就会变得很简单。怎么做呢? 让我用一个例子告诉您 -- 先用一般的文书编辑器建好一个MySet.inc的普通文本文件, 内容为: 在我们的程序中, 加入一列{$I MySet.inc}, 例如: interface implementation 基本动作会了之后, 让我告诉你多一点有关{$I文件名称}的事。 一旦应用了{$I文件名称}, 几乎等于Compiler在编译时, 让Compiler将这个档 案的内容贴进我们的程序中的那个位置。 {$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} 经过{$L hasptpw.obj}宣告之后, 程序的其它部分就可以直接呼叫原先位于 hasptpw.obj中的hsap这个程序了。 {$L+} 区域符号信息 在{$L+}时, Delphi会额外加入一些区域符号信息, 这使得我们可以应用Delphi IDE中的View/Call Stack, View/Watch在程序执行时检视变量内容与函式呼叫的 关系。 应用这个Compiler directive的注意事项有: {$D-}时, {$L+}不会有作用。 Delphi 2.0之后, 字符串多了一个更为好用的长字符串, 不仅没有数据长度255的限制与C语言惯用的Null-terminated string兼容性也大为提高。 使用{$H}时的注意事项有: {$H+}的编译情形下, 以string定义的字符串变量都是长字符串, 请注意, 字符串是否为长字符串是在字符串定义时决定的, 例如: 由于var前{$H-}的缘故, 虽然在begin后我们立即设定为{$H+}, 但s仍然是一个短 字符串, 所以, 自然不能像是长字符串一样, 以pchar强制型别转换后当作Null-terminated字符串使用。 承上, 不管程序是{$H+}或{$H-}, 只要字符串是以长字符串方式定义的, 即使begin..end;中改成{$H-}, 该字符串的操作仍然具有长字符串的特性。 不论{$H}的状态如何, 以AnsiString定义的一定是长字符串; 以string[n]或ShortString定义的一定是短字符串。 要改变唯迭(Stack)内存配置大小时, 我们可以有以下两种选择: 使用{$MINSTACKSIZE数字}, {$MAXSTACKSIZE数字}, 分别指定最小.最大的Stack大小. 写在.DPR中才有效果。 这个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+} const不是常数吗? 为什么可以改呢? 在先前的Pascal版本中, 以const VarName: DataType = const value; 定义的具型态常数的确是可以改的, 假如您希望常数就是常数, 它不应该允许修改, 请将这个Compiler directive设为{$J-} 不论是{$J+}或{$J-}, 以const VarName = const value; 定义的常数(没有加上 型别宣告), 是一个真正的常数, 其它的程序不可以改变其内容。 其实{$J+}时还有一个妙用, 那就是宣告出类似C语言static的变量, 换句话说, 产生了一个与Application相同生命周期的变量。在这种情形下, 变量只在第一次 使用时才会建立, 函数或程序结束时, 该变量也不会消灭, 下一次再呼叫到这个 函数或程序时, 我们仍然可以参考到上次执行结束时的值。让我们试一下这个例子: {$J+} 第一次执行时, 我们分别会看到「0」「1」, 再点一次这个按钮时, 看到的将是 「1」「2」。 |
DELPHI的编译指令
最新推荐文章于 2022-11-15 22:00:00 发布