Preprocessor macros are used in all C++ Builder programs, and are one of the most basic building blocks of any C++ project. When a source file is compiled, it is first processed by the "preprocessor". Some commonly used preprocessor symbols are the familiar #include and #define directives.
The preprocessor is responsible for creating the final C++ code, which is given to the compiler. This includes bringing in all header files and stripping out comments. To see for yourself how this works, you can create a small program such as the "hello" listing shown below. To see the output created by the preprocessor, go to the command prompt and type bcc32 hello.cpp. This will create an output file called hello.i containing the output from the preprocessor. As you can see, lots of stuff was added before the first line of code. If you want to see a huge amount of code returned by the preprocessor, try including <windows.h> at the top—in this case, you will want to use bcc32 -P- hello.cpp. The `P’ option prevents the addition of source file location comments. Without it, the hello.i file will be approximately 20 MB! The difference between #include <filename> and #include "filename" is the way in which the preprocessor searches for the file. With <filename>, only the directories listed under the project "Include Path" will be searched. With "filename", the preprocessor first looks in the same directory as the source file that includes it, then the current directory, and then the project include path.
// hello.cpp
#include <stdio.h>
void main()
{
printf("hello");
}
In addition to including files, the preprocessor also processes macros. A macro is anything that begins with #define. This can be as simple as defining logical names for constants, such as the following line:
#define ERROR_VALUE –1
The #define directive can also be used for more interesting things. For example, the following macro mimics the max() function:
#define MAX(x,y) (x > y ? x : y)
The macro is named MAX to differentiate it from the run-time library max() function. The difference between a macro "function" and a real C++ function is that the macro doesn’t really execute the function—it just performs text replacement for the specified arguments. Because of this, you must use caution when using macros to perform arithmetic functions. For example, suppose you use the MAX macro function defined above like this:
z = MAX(++x, y);
The precompiler would expand it to:
z = (++x > y ? ++x : y);
Not the intended result!
Preprocessor directives can be used to conditionally compile parts of a source file. The #ifdef and #ifndef directives evaluate whether a certain macro is or is not defined. One common use of these conditionals is in DLL projects, where a function should be declared with the _export keyword in the DLL project, and with the _import keyword for applications, which use the DLL. In this case, the code would look something like this:
#ifdef DLL
void _export f();
#else
void _import f();
#endif
These are also commonly used in header files to prevent the header from being compiled more than once for each source file. C++ Builder automatically inserts a #ifndef block around the header for all new modules that it adds to a project. For example, if you add a new unit to a project, the unit header file will look like this:
#ifndef Unit1H
#define Unit1H
// This code will be compiled only
// once, no matter how many times
// the file is included.
// ...
#endif
Another common use of #ifdef is to make code portable across different platforms. For example, if you’re writing code, which will be compiled on both Windows and Macintosh platforms, it might look like the following:
#ifdef MACINTOSH
sFilename = GetMacFilename();
#else
sFilename = GetWindowsFilename();
#endif
Another helpful preprocessor directive is #error. This can be useful in tracking down where the precompiler is when unknown errors are occurring. For example:
#ifdef FOO
// ...
#error "FOO is defined!"
// ...
#endif
This will display a compiler error such as "Error in file.cpp line xxx: error FOO is defined!"
I like to make the preprocessor do mundane typing jobs for me. I usually define a string copy macro such as the following when copying lots of character arrays:
#define NCOPY(x,y) \
lstrcpyn(x, y, sizeof(x));
This is especially useful if x is something like "mystruct.somefieldname".
There are some lesser-known precompiler operators, which can be really useful:
Operator |
Use |
| \ | This is the line continuation character. It lets you define a precompiler macro on more than one line. |
| # | When placed before a macro argument, it turns the macro argument into a string. |
| ## | When placed between two symbols, it concatenates the symbols together. |
Here is an example that uses all three of these to add colors to a combo box. The color names are added as the combo box strings, and the color values are added as the associated objects.
#define ADD_COLOR(color) \
cmbColors->Items->AddObject(#color, (TObject*)cl##color);
ADD_COLOR(Aqua)
ADD_COLOR(Black)
ADD_COLOR(DkGray)
ADD_COLOR(Fuchsia)
ADD_COLOR(Gray)
ADD_COLOR(Green)
ADD_COLOR(Lime)
ADD_COLOR(LtGray)
ADD_COLOR(Maroon)
ADD_COLOR(Navy)
ADD_COLOR(Olive)
ADD_COLOR(Purple)
ADD_COLOR(Red)
ADD_COLOR(Silver)
ADD_COLOR(Teal)
ADD_COLOR(White)
ADD_COLOR(Yellow)
Here’s another example that displays application error messages:
void DisplayError(int iError)
{
#define SHOW_ERR(err) \
if (iError == err) { \
MessageBox( \
NULL, #err, "Error", MB_OK); \
}
SHOW_ERR(ERROR_NOT_ENOUGH_MEMORY)
SHOW_ERR(ERROR_BAD_ENVIRONMENT)
SHOW_ERR(ERROR_INVALID_ACCESS)
SHOW_ERR(ERROR_INVALID_DATA)
SHOW_ERR(ERROR_OUTOFMEMORY)
SHOW_ERR(ERROR_INVALID_DRIVE)
// Others...
}
I hope you’ve learned some useful things about preprocessor macros in this article. They can be used to save repetative typing, reduce coding errors, and make your code more portable.