Logo

Hacker’s Guide to Visual FoxPro
An irreverent look at how Visual FoxPro really works. Tells you the inside scoop on every command, function, property, event and method of Visual FoxPro.

#Define, #UnDef, #If, #ElIf, #Else, #EndIf, #IfDef, #IfNDef, #Include, _Include

Visual FoxPro 3.0 introduced many new concepts to FoxPro programmers, and the ability to manage constants, like the other “real” languages, was a most welcome feature. Visual FoxPro supports the ability to insert defined constants into program, form and class code, as well as the ability to conditionally test and undefine these constants as required.

These are not FoxPro commands, but rather preprocessor directives. If you include one of these as a string and attempt to macro-expand it within FoxPro, you get the error message “Syntax error.” That’s because these commands are not intended to be run by the FoxPro interpreter, but are directed to the preprocessor, which produces the pseudo-object code (“p-code”), which the Visual FoxPro interpreter then runs. To distinguish them from FoxPro commands, we often refer to them as “compiler directives,” even though, strictly speaking, FoxPro lacks a true compiler.

Here’s the inside scoop. Within the development version of FoxPro, source code can be compiled at various times. When saving a method of a form, the code is compiled during the save process. When running a program whose compiled version is older than its source (with SET DEVELOPMENT ON), the source is recompiled. Finally, source is recompiled when you explicitly tell it to—either by selecting Compile from the Program menu or by building a project. When the preprocessor is called into play, it scans the source code for preprocessor directives, all of which begin with the # symbol. If it finds any of these, it performs the action specified by the command. If this command introduces more preprocessor directives (such as #INCLUDEing another file) these directives are completed, too, until all directives have been performed. At that point, FoxPro source code is converted into the p-code the interpreter (and runtime) will be able to run. Let’s review the various commands, and see what they can do.

Usage

#DEFINE cName eExpression
#UNDEF cName

#DEFINE declares a placeholder cName, which is to be replaced at compile time with the expression eExpression defined in the statement. #DEFINE statements work as in-line search-and-replace commands, replacing any occurrence of a specified phrase with an expression. This search and replace occurs in any command line anywhere a command keyword (or even a command itself) can appear. #DEFINE does not affect a literal string set off from the commands with quotation marks, but it does work within square brackets. The example below shows how you might want to take advantage of this.

#DEFINE is in effect only for the single program that contains the command or the current event or method procedure within a form or class. This is because this is the largest amount of code the preprocessor sees at one time—form methods are each compiled individually. A #DEFINE can be narrowed in scope even further with the addition of an “undefine” (#UNDEF) statement—only code between the #DEFINE and #UNDEF has the substitution performed.

Example

#DEFINE c_nVERSION_NO  "1.23"
* The following inserts the literal TTOC() expression
* into the source, rather than the date and time.
#DEFINE c_tVERSION_BUILT TTOC(DATETIME())
* 1st example: evaluate both outside of quotes.
WAIT WINDOW "Version "+ c_nVERSION_NO + ;
            ", built "+c_tVERSION_BUILT
* 2nd example: Constants evaluated inside square brackets.
WAIT WINDOW [Version c_nVERSION_NO , built c_tVERSION_BUILT]
* 3rd example: Constant substitution doesn't take place
* within single or double quotes.
WAIT WINDOW 'Version c_nVERSION_NO , built c_tVERSION_BUILT'

Usage

#IF eExpression
[ #ELIF eExpression ]
[ #ELSE ]
#ENDIF

These statements tests a condition at compile time, and if it’s true, the code within the IF…ENDIF pair is included in the source code to be compiled. This lets you include lots of code in your application while developing—tracing and debugging routines, special function key definitions that can pause and single-step the code, routines to provide “backdoors” into certain parts of the application—and prevent the code from even being included in the final version you install at a client site. What a relief not to worry that some data entry clerk will find that “Ctrl+Alt+Shift+F12” routine you used to test the end-of-the-year closeout routine in July!

#IF directives can also be used to include or exclude platform- or version-specific code. In VFP 3.0 and earlier, slightly different commands or functions are needed on the different platforms to support platform-specific functions. When the new version on the new platform is released, the commands or functions are not recognized on the older software of other platforms, generating compiler errors and defeating the cross-platform compatibility trademark of FoxPro. This new code can be “wrapped” in the #IF…#ELSE…#ENDIF test so that it’s included only on the new platform, as the second example below illustrates.

Example

#IF C_DEBUGGING
  do DebugLog with Program(), Pcount()
#ENDIF

* Use the faster VARTYPE() if
* compiling under VFP 6 or later.

#IF "FOXPRO 06" $ UPPER(VERSION(1)) OR ;
    "FOXPRO 07" $ UPPER(VERSION(1))
  IF VARTYPE(toObject) <> "O"
#ELSE
  IF TYPE("toObject") # "O" or ISNULL(toObject)
#ENDIF
    RETURN .F.
  ENDIF

The other directive option used with #IF…#ENDIF is the #ELIF directive. #ELIF, which translates to “Else If,” allows you to add multiple decisions to preprocessor directives. Return either a numeric value (0 representing false, a non-zero value representing true) or a logical value to the expression after the #ELIF, to determine whether the successive lines are included. See the example, below.

Example

*
#IF "FOXPRO 07" $ UPPER(VERSION(1))
  ? "Using the current version."
#ELIF "FOXPRO 06" $ UPPER(VERSION(1))
  ? "Using an older version."
#ELIF "FOXPRO 05" $ UPPER(VERSION(1))
  ? "Using a pretty old version."
#ELSE
  ? "Using a really old version!
#ENDIF

Usage

#IFDEF cConstantName
#ENDIF

This pair of statements tests for the existence of a predefined constant, declared with the #DEFINE statement above, at compile time, and if it’s defined, the code within the IFDEF…ENDIF pair is included in the source code to be compiled.

Example

#IFDEF cAuditing  && Auditing functions to be included
  IF cAuditing
    DO AuditTrail with ...
  ELSE
.... more code
#ENDIF

Usage

#IFNDEF cConstantName
#ENDIF

This pair of statements is the reverse of the above, the equivalent of NOT in the IF portion of the test. If a predefined constant is not in effect, either through the lack of a #DEFINE statement or the removal of the definition using an #UNDEF, the code within the IFNDEF…ENDIF pair is included in the source code to be compiled.

Example

#IFNDEF C_DEBUGGING
  SET DEVELOPMENT OFF
  SET TRBETWEEN OFF
#ENDIF

Usage

#INCLUDE cFileName
_INCLUDE = eFileAndPathName

#INCLUDE inserts a second file into the first file, in memory, just before performing the compile. This allows you to keep a set of handy #DEFINEs around, written in just one place, and stick them into all of your programs. A couple of caveats apply, of course:

One very neat thing is that a file that’s #INCLUDEd can have any or all of the preprocessor directive statements within it, and these, too, are processed by the preprocessor. Our suggestion: Break constant files into bite-sized chunks and create a STANDARD.H file that #INCLUDEs them all. That way, if a specific set of definitions must be maintained, they can be located most easily. Breaking them up by function (display, I/O, security) can make it easier to swap out a set when replacing or upgrading a subsystem. An alternative is to have a “common” set of definitions in a COMMON.H and include that in “subsystem” include files.

Rather than using this function in every method of a form or class, check out the “Include File…” option on the Form or Class menu. This makes the #INCLUDEd material available to each and every method within the form or class.

In VFP version 3.x, compiling a file that #INCLUDE's other files doesn't return any error messages if the #INCLUDEd file is not located or cannot be opened because it is in use. This one trips us up all the time: If you open FoxPro.H to see what they called a constant, and then compile and run a program using that file without closing the editing window, you'll get stupid errors like "Variable 'COLOR_RED' is not found." In VFP 5.0 and later, a dialog appears, informing you the file can't be opened, and generates an ERR file if you choose "Ignore" to continue compiling. Although we at least get an error telling us it's open, we wish having the #INCLUDE file open didn't prevent you from compiling. That's just plain annoying.

We've mentioned FoxPro.H in a few places throughout the book, and thought we should clue you in on this file. This file contains predefined sets of constants for many of the more difficult-to-remember functions, such as color numbers, low-level file routines, PRTINFO() types, parameters to MessageBox() and so forth. This file is worth browsing and using. We far prefer to see code using these constants, because it's much easier to read and, therefore, to maintain.

_INCLUDE is a system memory variable introduced in VFP 6. _INCLUDE is blank unless we set the preferences in Tools | Options | File Locations. When _INCLUDE is set to a header file, this file is automatically included in the compilation of all forms and classes. This is a wonderful way to ensure that your constants are available throughout your projects. We wish, however, they would have considered making _INCLUDE available for all compilation—programs, menu code, whatever—for consistency.

While .H is a standard extension for a header file, you can use any extension you like, provided you specify it in the #INCLUDE statement. Do remember that other programmers are likely looking for the .H extension, though.

Example

* STANDARD.H
#INCLUDE FOXPRO.H
#INCLUDE MyVars.H

* Much easier-to-read code
#INCLUDE FOXPRO.H
IF MESSAGEBOX("Question",;
              MB_YESNO + MB_ICONQUESTION + MB_DEFBUTTON2, ;
              "Title") = IDYES
* than:
IF MESSAGEBOX("Question", 292, "Title") = 6  && what's 292? 6?

_INCLUDE = LOCFILE(HOME()+"FOXPRO.H","H")

See Also

Compile, Set Development