Adding the new diagnostic macros to your code.
One of the most significant problems most programmers face during
application development is debugging the code. As a result, Borland C++ supplies a strong
set of debugging tools to help locate code problems.
Borland C++ 4.0 provides two new diagnostic macros that can help you embed diagnostic
messages and conditional messages in your functions. In this article, we'll show you how
to add these new macros to your code and how to turn them on and off when you compile the
application.
The Problem
Many programmers like to embed debugging or diagnostic code directly in their programs.
Some programmers do this because they're unfamiliar with Borland's debugging tools. Others
embed diagnostic code so they can analyze complex bugs in the true native environment for
the application.
Unfortunately, if you add diagnostic code to a specific function, it may significantly
reduce the speed of the function's execution. Due to speed considerations, you may have to
remove the diagnostic code after you debug the production version of your application,
only to reenter the code when a new bug appears.
For some time now, programmers have been surrounding diagnostic code with
conditional-compile statements. These usually take the form
#ifdef DIAGNOSTIC
if(param == 0)
cout << "Error at foo(), param == 0!";
#endif
This way, if you add a #define statement for the label DIAGNOSTIC
early in the file, the compiler will see and process the error-checking code. If you
remove the #define statement for DIAGNOSTIC, the preprocessor skips to
the #endif statement and the compiler never sees the code.
However, adding conditional-compile statements can be a very tedious process. If you
don't add the #define statement at the correct location in the file, or if you
misspell the label, the preprocessor may skip over the diagnostic code for an important
function.
Finally, if you decide to send your diagnostic messages to a file instead of using the
standard output stream cout or the printf() library function, you'll
have to change each line of diagnostic code individually. Obviously, this isn't practical
for a large application. Fortunately, Borland solves this problem with the two new macros TRACE
and WARN.
Using the New Macros
Borland C++ 4.0 adds two new diagnostic macrosTRACE and WARN
conditionally display messages and warnings at runtime. The CHECKS.H header file defines
these macros.
To use those macros in your code, add a #include <checks.h> statement to
your source files. Be sure to add this statement ahead of any functions that will use the
diagnostic macros.
To enable the TRACE macro, be sure the label __TRACE appears in a #define
statement. To enable the WARN macro, be sure the label __WARN appears in
a separate #define statement. You can add these statements explicitly to your
source files, or you can add the labels to the Defines list in the compiler's Options
dialog box for that particular file.
The TRACE macro allows you to display simple diagnostic messages with the
syntax
TRACE("Diagnostic Text");
Even in this form, the macro is an improvement over creating messages by hand. However,
because the macro uses a member of the class strstream to format the output text,
you can also write
TRACE("Diagnostic status " << statusCode);
to display the value of a variable. Here, we show a variable with the name statusCode,
but you can use any variable you can send to an output stream.
You can even use streamable variables, or expressions that result in streamable
variables, as the only parameters to the TRACE macro to avoid passing any text.
However, displaying a value without a description may cause readability problems.
The WARN macro is similar, but it allows you to display a warning message
based on a condition. With this macro, you can write
WARN((code > 3), "Error, run Will
Robinson");
to display the warning text only when the first parameter isn't zero.
As with the TRACE macro, the WARN macro formats its text with a
member of the class strstream. This allows you to display the value of variables
based on the macro's conditional parameter by writing
WARN((code > 3), "Error, status == "
<< status);
Finally, since these macros send their output to the standard output stream cout,
you can easily redirect the data to another output stream. Now, let's enter an example
program that uses the WARN and TRACE macros.
Writing an application with Conditional Diagnostics
Launch the Borland C++ 4.0 IDE. When the IDE main window appears, choose New Project...
from the Project menu.
When the New Project dialog box appears, enter
c:\bc4\diag\diag.ide
in the Project Path and Name entry field. In the Target Type section, select
Application [.exe] in the list box and choose DOS Standard from the Platform combo box.
When you finish, the New Project dialog box should resemble the one shown in Figure A.
Click the OK button to create the DIAG.IDE project.
Open the DIAG.CPP source file by double-clicking on its icon in the Project window. When
DIAG.CPP's editor window appears, enter the source code from Listing A.
Listing A: DIAG.CPP
#include <owl/pch.h>
#include <fstream.h>
ofstream traceFile("trace.txt");
int foo(int param)
{
TRACE("Entering foo");
WARN(param, "param == " << param << "!");
TRACE("Returning " << param << " from foo");
return param;
}
int main()
{
if(traceFile) cout = traceFile;// only for borland ???
TRACE( "Entering Main" );
foo(5);
TRACE( "Leaving Main" );
return 0;
}
The first line is a #define statement for the __TRACE label, which
enables the TRACE macro. Later, when we compile this program, we'll provide a #define
statement by changing the compiler options for this file.
The next two lines in the program are #include statements that tell the
preprocessor to embed the contents of CHECKS.H and FSTREAM.H in this file. The file
CHECKS.H defines the diagnostic macros, and the file FSTREAM.H declares the ofstream
class we'll use to create an output file stream for an error file.
Next, we declare a global ofstream object for the TRACE.TXT file. Later, we'll
redirect the standard output file cout to send its output to this file.
The foo() function uses both the TRACE and the WARN macros
to output text and the value of a variable. The main() function that follows
calls the diagnostic macros with output text only.
At the very beginning of the main() function, you'll find the line
if(traceFile) cout = traceFile;
This statement checks to see if the output file stream traceFile is valid (no
errors opening the file).
If the file is valid, we then assign the output file stream to the standard output
stream cout to redirect its output to the file TRACE.TXT. Now, let's try our
example.
Running The Example
In the Project window, right-click on the DIAG.CPP file's name. Now, choose Edit Local
Options... from the pop-up menu.
When the Options: At DIAG.CPP dialog box appears, double-click on Compiler in the
Topics list box. Then select Defines from the the same Topics list box and enter __WARN;
in the Defines entry field. Click the OK button to save these settings.
Now, compile and run DIAG.EXE by double-clicking on its name in the Project window.
When the IDE finishes compiling and linking the program, the screen will go black
momentarily and then return to normal. (The screen goes black when the IDE runs this
application as a DOS program under Windows. Because this program doesn't do anything
except write some diagnostic messages to a file, it runs and then returns quickly.)
To see the output file, choose Open... from the IDE's File menu and then enter
TRACE.TXT in the File Name entry field. When you click the OK button, an editor window for
the file will appear, as shown in Figure B.
Figure B - The error file TRACE.TXT contains the output of the diagnostic
macros.
To remove the warning message code from DIAG.EXE, just remove the __WARN label from the
DIAG.CPP file's compiler options. To remove the trace message code, remove the #define
__TRACE statement at the beginning of the DIAG.CPP file.
By the way, there's nothing special about putting the #define __TRACE
statement in the file and putting the __WARN label in the compiler options. You can put a #define
__WARN statement in the source file and the __TRACE label in the compiler options, or
you can put both in either location.
Conclusion
The TRACE and WARN macros give you more control over
conditional-compile diagnostic messages than the #ifdef statements some
programmers use. By taking advantage of these macros, you can develop the habit of leaving
diagnostic code in your application without worrying about removing it from your
production code. |