help

解决方案 »

  1.   

    刚刚请教了别人这个问题,就有人撞上来了,:)拿分来转载如何使用Delphi中预编译指令
    www.lvyin.net  2002-9-19 绿荫网络
    在Delphi中,也有与C相似的预编译指令,虽然该类指令只在当前的单个文件有效(也有可能是笔者未全面了解该类指令的真正用法),但是这一类指令对于进行多版本的制作工作(如从标准版中出学习版),确实有着相当不错的用途。
    一.指令介绍:1. DEFINE指令:格式:{$DEFINE 名称}说明 :用于定义一个在当前单元有效的符号(Symbol)。定义了之后可以使用IF DEF和IFNDEF指令来判断该符号是否存在。
    2. UNDEF指令:格式:{$UNDEF 名称}说明:用于取消一个在当前单元已经定义的符号(Symbol)。该指令和DEFINE配合使用。
    3. IFDEF指令:格式:{$IFDEF 名称}说明:如果该指令后的名称已经定义,则编译该指令后直到{$ELSE}或{$ENDIF}之间的代码段。
    4. IFNDEF指令:格式:{$IFNDEF 名称}说明:如果该指令后的名称没有定义,则编译该指令后直到{$ELSE}或{$ENDIF}之间的代码段。
    5. IFOPT指令:格式:{$IFOPT 开关}说明:如果该指令后的开关已经设立,则编译该指令后直到{$ELSE}或{$ENDIF}之间的代码段。举例:{$IFOPT R+}Writeln('编译时打开范围检查开关');{$ENDIF}
    6. ELSE指令:格式:{$ELSE}说明:通过判断前缀Ifxxx的条件式来确定该指令到{$ENDIF}之间的代码段是否应该被编译或者忽略掉。
    7. ENDIF指令:格式:{$ENDIF}说明:和Ifxxx配合,指明条件预编译段源代码段的结束位置。
    二.范例:编写例子,通过预先定义不同的编译符号,进行不用代码段的编译工作。1. 新建一个Delphi项目,在Unit1单元的窗体上添加一个Button按钮。2. 编写程序如下:unit Unit1;
    interface
    usesWindows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls;typeTForm1 = class(TForm)Button1: TButton;procedure FormCreate(Sender: TObject);procedure Button1Click(Sender: TObject);private{ Private declarations }public{ Public declarations }a : String;end;
    varForm1: TForm1;
    implementation
    {$R *.DFM}
    {$DEFINE AAA} // 定义行。
    procedure TForm1.FormCreate(Sender: TObject);begina := 'Other';{$IFDEF AAA}a := 'AAA';{$ENDIF}{$IFDEF BBB}a := 'BBB';{$ENDIF}end;
    procedure TForm1.Button1Click(Sender: TObject);beginCaption := a;end;
    end.{注:粗体字部分为输入的代码}3. 编译后运行,按下Button,则看到窗体标题栏显示“AAA”。程序编译了a := ’AAA’的语句。4. 改变定义行的程序段:当改为{$DEFINE BBB}时,再次编译运行,则看到窗体标题栏显示“BBB”。程序编译了a := ’BBB’的语句。当取消定义行或改为{$DEFINE NOTHING}或其他名称时,再次编译运行,则看到窗体标题栏显示“Other”。程序只编译了a := ’Other’的语句。
    三.如何快速的制作和更改版本:使用预编译指令,在制作同一个程序的多个版本时,只需找出各版本中有区别的单元,依次定义统一的版本符号(Symbol),然后在程序段中加入条件预编译指令,就可以在实际编译中取舍编译不同的程序部分,这样对于程序的规范性(定义统一的版本符号)和保密性(不同的版本编译不同的程序部分)有很好的作用。然而,由于该类预编译指令只能作用于当前单元,所以不便之处在于不能在一个公共单元定义一次版本符号,而必须在各单元中定义统一版本符号才行,故此,在更换版本时,需要确定所有的版本符号都已改变,这样才能保证各版本的正确性,对此,可以使用Delphi IDE的“Find in Files…”(多个文件中查找字符串)的功能,找出所有定义版本符号的文件和位置,然后依次更改,保证所有位置已经改正。
      

  2.   

    DELPHI的编译指令{$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, 在上述ProjectOptions对话盒的左下方有一个「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 }
      

  3.   

    程序的可移植性     我们都可能会用到其它公司或个人创作的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-} 布尔评估