Buscar

Compiler Directives

Prévia do material em texto

Compiler Directives
Controlling the Delphi Compiler
  
They look similar to comments, but they aren't. And nearly every project has at least one, even the default project Delphi creates when you first load it. They are compiler directives, and they are the focus of this article. 
  
You use compiler directives to control how Delphi's compiler performs its task. For example, the {$R} compiler directive instructs the compiler to link a specified .res file into the resulting executable. By default, every unit associated with a form includes at least one {$R} compiler directive, which tells Delphi to include a binary version of the form's .dfm file in the compiled .dcu file as a Windows resource. 
  
You can control compiler directives in one of two ways. The first way is to use the checkboxes on the Compiler page of the Project Options dialog box, as shown in Figure 1. To see this page, select Project | Options from Delphi's main menu, and then select the Compiler tab. 
  
  
The second way is to embed compiler directives directly in your code. Embedded compiler directives look a lot like braced comments. The only difference is that compiler directives have a dollar sign ($) immediately following the opening brace. Like all other statements interpreted by the compiler, however, if the {$ combination appears within a comment, it's ignored. 
  
There are three types of compiler directives: switch, parameter, and conditional. Switch directives turn a particular feature on or off. The {$H+} directive is a switch directive. Switch directives are turned on when the directive is followed by a plus sign (+) or the word ON, and turned off when it is followed by a minus sign (-) or the word OFF. In the case of the {$H} compiler directive, it instructs the compiler to treat all string declarations as ANSIStrings when turned on, and as Pascal-style strings when turned off. 
  
Parameter directives identify a file name, file extension, or setting required by the compiler. The ($R *.RES} resource compiler directive is one example of a parameter directive. Conditional directives permit you to instruct the compiler to conditionally compile a segment of code. The {$IFDEF} directive is a conditional directive. 
  
Compiler directives can also be categorized by their scope, which is either local or global. Global scope directives apply to the compilation of the entire project. For example, the {$APPTYPE CONSOLE} directive instructs the compiler to build a console application. Local scope directives, by comparison, apply to only part of the code being compiled. For instance, a specific section of code can be compiled with the {$H-} directive, instructing the compiler to treat all declared string variables as short strings in that section, and with {$H+} for the remainder of the project. 
  
Every project has a default set of compiler directives, defined by the values that appear on the Compiler page of the Project Options dialog box. If you like, you can instruct Delphi to explicitly insert the current compiler directives into a unit. To do this, press [Ctrl][O][O] (oh-oh, not zero-zero). An example of a unit where [Ctrl][O][O] has been pressed is shown in Figure 2. 
  
Figure 2: Press [Ctrl][O][O] in the editor to insert your project's default compiler directives at the top of the current unit. 
  
As you inspect Figure 2, you will notice that the first compiler directive includes a large number of switches. When you insert switch compiler directives manually, you can enter each separately, each contained in its own set of braces, or include a comma-separated list of the switch directives in a single set of braces. 
  
Selected Compiler Directives
The following sections take a look at a sample of compiler directives, but because many of the compiler directives are self-explanatory, I will make no attempt to be comprehensive. Instead, I'll focus on those compiler directives that I find most interesting, fun, or useful. For information on compiler directives not discussed here, you can press [F1] on the Compiler page of the Project Options dialog box, or select "compiler directives" from Delphi's online help index. 
  
Suppressing Hints and Warnings
Delphi's compiler will generate hints and warnings that can help you write better code. For example, if you declare a local variable in a method, but never use it, the compiler will display a hint following compilation. It will note potentially more troublesome problems with warnings. 
  
Sometimes these generated hints and warnings are annoying. For example, you may have a function that really does return a value in every case, but the compiler cannot detect this. In these cases, you can turn hints and/or warnings off. 
  
The {$HINTS} and {$WARNINGS} compiler directives are local switch directives. To turn hints off use: 
  
{$HINTS OFF}
  
To turn them back on use: 
  
{$HINTS ON}
  
For warnings use: 
  
{$WARNINGS OFF}
  
and: 
  
{$WARNINGS ON}
  
Consider the function shown in the code segment in Figure 3. It simplifies the launching of an executable. Even though the first line of the function sets the Result variable to False, the compiler will generate a hint saying that the return value of the function may not be set. Since you know that the function always returns a value, you can turn off this annoying hint using the {$HINTS} compiler directive. 
  
{$HINTS OFF}
function LaunchApp(const AppName: string): Boolean; 
var
  ProcessInfo: TProcessInformation; 
  StartUpInfo: TStartupInfo; 
begin
  Result := False; 
  try
    FillMemory(@StartupInfo, SizeOf(StartupInfo), 0); 
    StartupInfo.cb := SizeOf(StartUpInfo); 
    Result := CreateProcess(nil, PChar(AppName), nil, nil,
      False, NORMAL_PRIORITY_CLASS, nil, nil, StartUpInfo, 
      ProcessInfo); 
  finally
    CloseHandle(ProcessInfo.hProcess); 
    CloseHandle(ProcessInfo.hThread); 
  end;
end;
{$HINTS ON}
Figure 3: Turning compiler hints off for one method. 
  
Automatically Compiling Resource Files
Until Delphi 4, the use of resource scripts to compile Windows resource files required that you compile the resource scripts manually using the Borland Resource Command-line Compiler (BRCC32.EXE). Unfortunately, the manual nature of this compilation made it possible for you to change a resource script, forget to recompile it, and then link the old resource file into your application using the {$R} resource compiler directive. 
  
A new version of the resource compiler directive was introduced in Delphi 4. This version is shown in the following example, which must be placed in your project's .dpr file: 
  
{$R 'filename.res' 'filename.rc'}
  
When the Delphi compiler encounters this compiler directive, it performs a number of valuable tasks. First it checks to see if the named .res file exists. If it doesn't, it automatically compiles the named .rc file to create the .res file. If the .res file already exists, the compiler compares the timestamps of the .res and .rc files. If the .rc file is more recent, it recompiles the .rc file into a new .res file. As a result, when you link resources using this version of the resource compiler directive, you're assured of always using the latest version of your resource script. 
  
Instead of manually adding this version of the resource compiler directive to your project source, you can use the Project Manager to generate it. With your project open in the Project Manager, right-click the node associated with your project and select Add. The Project Manager responds by displaying the Add to project dialog box shown in Figure 4. 
  
Figure 4: Adding a resource script to a project in the Project Manager inserts the enhanced compiler directive into your .dpr file. 
  
Set the File of type to Resource File (*.rc). Then select your resource file and click the Open button. In response,Delphi will add the appropriate resource compiler directive to your project source. 
  
Note: If you were using the older version of the resource compiler directive, remove it before adding the newer version. If you fail to remove the old directive after adding the new one, you'll receive a duplicate resource error when you try to compile. 
  
Controlling Optimizations
Delphi's compiler performs optimizations that produce significant increases in application performance. Sometimes, however, these optimizations can reduce your ability to debug your application. For example, it's not uncommon for the value of many of your variables to be unavailable to the integrated debugger due to optimizations. This can be annoying if these values are crucial to your debugging process. (Note: Even when compiler optimizations are turned off, some variable values can be unavailable to the integrated debugger.) 
  
The optimization compiler directive is a local directive. To turn optimizations off, use: 
  
{$OPTIMIZATIONS OFF}
  
To turn them back on again, use: 
  
{$OPTIMIZATIONS ON}
  
Setting the Executable File Extension
Under normal conditions, programs are compiled with the .exe extension and libraries with the .dll extension. Using the executable extension parameter directive, however, you can instruct Delphi to compile your executable with an alternative extension. 
  
Normally, you will only use the executable extension directive with libraries. This is because libraries can be loaded regardless of their file extension, while Windows will not know how to launch an application unless it has a .exe extension. 
  
Why, you might ask, would you ever consider changing the file extension of a library? One answer is that you might create a resource-only DLL (one that contains no exported routines - only Windows resources). If you want to distinguish these DLLs from those that you export as executable subroutines, you can provide it with an alternate extension. To set the file extension of a library, use: 
  
{$E ext}
  
where ext is the file extension. For example, when Delphi generates a library to define an ActiveX server, it uses the: 
  
{$E OCX}
  
directive to instruct the compiler to create a library with the OCX extension. This trick is useful when you want to compile routines or resources into a DLL, but don't want other developers to know what type of file you used. 
  
Conditional Compilation
You can instruct the compiler to conditionally compile one or more lines of code using the {$IFDEF} and {$IFNDEF} compiler directives. When you use these compiler directives, you follow them with a symbol name. If the symbol name is defined, the statements following {$IFDEF} are compiled, and those following {$IFNDEF} are not. You must include a corresponding {$ENDIF} for either the {$IFDEF} or {$IFNDEF} directives to indicate the scope of the conditional compilation. 
  
Symbol names are defined using the {$DEFINE} directive, which you follow with the symbol name you are defining. This symbol name will remain defined for the remainder of the compilation, or until the compiler encounters a $UNDEF compiler directive. 
  
The {$IFDEF} and {$IFNDEF} compiler directives can also be followed by an {$ELSE} directive (but only before the corresponding {$ENDIF}). This directive supplies the same if..then..else logic that you know from Object Pascal. If the symbols are defined for an {$IFDEF}, the statements following {$IFDEF} are compiled and those following the nested {$ELSE} are not. The opposite is true concerning {$IFNDEF}. Figure 5 shows a pseudo-code segment that demonstrates the use of {$DEFINE} and {$IFDEF}.
  
{$DEFINE USEINI}
uses IniFiles, Registry; 
  
procedure TForm1.Button1Click(Sender: TObject); 
var
{$IFDEF USEINI}
  info: TIniFile; 
{$ELSE}
  info: TRegistry; 
{$ENDIF}
begin
{$IFDEF USEINI}
  info := TIniFile.Create('INFO.INI'); 
{$ELSE}
  info := TRegistry.Create; 
{$ENDIF}
  ShowMessage(info.ClassName); 
end;
{$UNDEF USEINI}
Figure 5: Pseudo-code showing the use of {$DEFINE} and {$IFDEF}.
  
As the code appears here, clicking the button that uses this OnClick event handler will result in the display of the message "TIniFile." If you comment out the {$DEFINE} directive and run it again, the message will display "TRegistry." 
  
Standard Conditional Symbols
Delphi has a number of standard conditional symbols that it defines, depending on the version of Delphi you're running, and under which operating system. You can use these symbols in the {$IFDEF} and {&IFNDEF} compiler directives without using a corresponding {$DEFINE} directive. Figure 6 shows a table of the most common of these conditional symbols, and the version of Delphi with which they're compiled. 
  
	Defines
	Delphi Version
	WIN16
	16-bit
	WIN32
	32-bit
	VER80
	Delphi 1.0
	VER90
	Delphi 2.0
	VER100
	Delphi 3.0
	VER120
	Delphi 4.0
	VER130
	Delphi 5.0
Figure 6: Common standard conditional symbols. 
  
Delphi 6 and Kylix each will include one new standard conditional symbol. When compiling a project using Kylix, the LINUX symbol will be defined. By comparison, when compiling with Delphi 6 and later, the WINDOWS symbol will be defined. These defines will be valuable when you create projects that you want to be able to compile for both Linux and Windows. 
  
Use of the WIN32 standard define is demonstrated in the following section. 
  
Inserting Code into a Unit
Use the {$INCLUDE} compiler directive (or simply {$I}) to insert a segment of code at the location of the directive. This code is compiled as though it were originally part of the unit in which the directive appears. 
  
One valuable use of the {$I} compiler directive is to conditionally include one of several possible resourcestring blocks in a file (resourcestring blocks define constant-like symbols that are stored in the executable as string resources). For example, imagine you have three files, each containing a resourcestring block that defines resource strings in a particular language. The code in Figure 7 demonstrates this use, depending on the presence of a defined symbol. 
  
var
  Form1: TForm1; 
  
{$DEFINE SPAN}
  
{$IFDEF ENGL}
{$INCLUDE 'englstrs.txt'}
{$ELSE}
   {$IFDEF SPAN}
   {$INCLUDE 'spanstrs.txt'}
   {$ENDIF}
{$ENDIF}
  
implementation
...
Figure 7: This code shows a resourcestring block. 
  
The following is an example of what the ENGLSTRS.TXT file might contain: 
  
resourcestring
  sMainFormCaption = 'Example Project'; 
  sOKButtonCaption     = '&OK'; 
  sCancelButtonCaption = '&Cancel'; 
  sFileExit   = '&Exit'; 
  sFileOpen   = '&Open'
  sFileClose  = '&Close'; 
  sFileReport = 'Re&port'; 
   // More resourcestring declarations... 
  
After processing the {$INCLUDE} compiler directive, the contents of the appropriate .txt file are compiled along with all other code within the unit. 
  
Using Short Strings by Default
Delphi's ANSIString type is the default type for 32-bit applications. These strings are large, dynamically allocated, and reference counted. They are also very nice to use in most situations. However, there are certain circumstances where you might want the string type to refer to the original Pascal-style strings, which are arrays of Char of up to 255 characters in length. One such situation is when you have written an application that you must continue to compile in both 16-bit and 32-bit versions of Delphi, and you want to treat your strings the same regardless of the version. (Granted, this situation was more common a few years ago, but there are some Delphi developers who must support both 16-bit and 32-bit applications.) 
  
While the individual characters in both ANSIStrings and Pascal strings can be indexed, only the Pascal-style strings include a zero-order byte, the Ord value ofthis character indicating the length of the string. ANSIStrings don't have a zero-order byte. If you have an application that expects a string to include a zero-order byte, regardless of which version of Delphi is compiling the application, you can use the {$H OFF} compiler directive. 
  
Note, however, that while Delphi 2 and later recognize the {$H} directive, Delphi 1.0 (the 16-bit version) does not. Consequently, the {$H} compiler directive has to be made conditional, thus: 
  
{$IFDEF WIN32}
{$H OFF}
{$ENDIF}
  
Conclusion
Compiler directives permit you to control the actions of the compiler. By familiarizing yourself with the available compiler directives you can make certain that you are getting the most out of Delphi. 
  
Cary Jensen is president of Jensen Data Systems, Inc., a Houston-based database development company. Cary is the lead author and principal trainer on the Delphi Development Seminars Kylix World Tour, and is co-author of 17 books, including Oracle JDeveloper [Oracle Press, 1998], JBuilder Essentials [Osborne/McGraw-Hill, 1998], and Delphi in Depth [Osborne/McGraw-Hill, 1996]. He is a Contributing Editor of Delphi Informant Magazine, and an internationally respected trainer of Delphi and Java. For more information, visit http://www.jensendatasystems.com, or e-mail Cary at mailto:cjensen@compuserve.com.

Continue navegando