In the [Adding_the_new_diagnostic_macros_to_your_code] article, we showed you how to use the TRACE() and WARN() diagnostic macros that ship with the OWL package. You can use these macros to display text messages and conditional warnings from debug versions of your application.
Unfortunately, the TRACE() and WARN() macros are all-or-nothing options. If you define the __TRACE constant when you build the application, the application will display every TRACE message that appears in the code when you run the application.
If you're looking for only one or two particular TRACE messages, you may have difficulty finding the message you want when it's surrounded by less important ones. This same problem may occur when you use the WARN() macro and define the __WARN constant.
However, you can use the extended diagnostic macros TRACEX() and WARNX() to display a specific type of message without displaying other types. In this article, we'll show you how to create and display different types of diagnostic messages by using the extended diagnostic macros.
To control the display of a particular type of diagnostic message, you'll need to define a diagnostic group. A diagnostic group manages a set of diagnostic messages. After you define a diagnostic group, you can create messages that belong to that group.
When you create a message for a given group, you'll also specify the default message level for that group. You can think of a given message's level as its priority within that diagnostic group. When you're using the extended diagnostic macros, messages with a lower numeric level have a higher priority. To determine which messages from a diagnostic group the program will display, you can set that group's level to any value between 0 and 127.
In your source file, you'll use the macro DIAG_DEFINE_GROUP() ahead of statements that use any of the extended diagnostic macro functions. You'll use this macro to name the diagnostic group and set its initial message level.
At runtime, each diagnostic group keeps track of its current level setting as well as an Enabled flag. The extended diagnostic macros use the Enabled flag to determine whether they'll display messages that belong to this particular group. If the Enabled flag is set to 1 (True), the extended diagnostic macros will display messages whose levels are equal to or lower than their group's current level setting.
For example, if you want to create a diagnostic group named Global to display messages that relate to global variables, you would add
DIAG_DEFINE_GROUP(Global, 1, 0);
to your source file. In this statement, Global is the name of the group, 1 is the initial setting for the group's Enabled flag, and 0 is the initial value for the group's message level.
Initializing a global variable is an important action, so you'll probably want to display a message at the initialization point if you're viewing any other messages from the Global diagnostic group. If you want to display a message whenever you enable its diagnostic group, set the message's level to 0.
To create a TRACEX message that precedes initialization of the global variable array1, you'd put a line similar to
TRACEX(Global, 0, "Initializing array1");
before the line that actually initializes array1. In this macro call, Global specifies the diagnostic group, 0 is the message's level, and "Initializing array1" is the message that TRACEX() will display.
Modifying a global variable's value is also important, but less so than initializing. If you want a diagnostic message to precede code that modifies the global variable userCount, you can create a message similar to the following line:
TRACEX(Global, 1, "Modifying userCount");
To force the compiler to expand the TRACEX() macro, you need to define the global constant __TRACE. Then, when you're ready to run this code in a debugging mode, you can set the Global diagnostic group's message level to 0 in the DIAG_DEFINE_GROUP() macro to make the program display only the initialization message.
If you decide later that you also want to see the message about modifying the global variable, you'll need to set the default message level higher for the Global diagnostic group. If you change the level parameter to 1 in the DIAG_DEFINE_GROUP() macro and then rebuild the program, it will display both messages.
By default, the CHECKS.H header file defines the default diagnostic group Def with the Enabled flag set to 1 and the message level set to 0. This file defines the standard TRACE() and WARN() macros as part of the Def diagnostic group. In fact, the CHECKS.H header file contains a macro that the compiler uses to expand the macro call
TRACE("HI")
into the extended macro call
TRACEX(Def, 0, "HI")
Since the default group's message level is 0 and its Enabled flag is set to 1, TRACE() macros will display their text message whenever you use them in a file that defines the global constant __TRACE. However, the diagnostic group Def is just another group with a special name. As we'll see later, you can manipulate the Def diagnostic group's Enabled flag and message level just as you would for groups you define.
The CHECKS.H header file also defines an extended version of the WARN() macro. The WARNX() macro displays a diagnostic group message only when its second parameter is 0. To force the compiler to expand the WARNX() macro, you need to define the global constant __WARN.
The WARNX() macro's first parameter is the message's diagnostic group, as was the case for the TRACEX() macro. However, since the warning condition is now the second parameter, the message level and message text parameters move to the third and fourth positions respectively.
For example, to see a warning message when your program sets the global variable count to 0, you can add the line
WARNX(Global, count, 1, "count == 0");
This macro call will display its message only when you enable the Global diagnostic group, you set the Global group's message level to 1 or higher, and the variable count is equal to 0.
So far, we've discussed the enabled/disabled state and message level of a diagnostic group as static settings. We've described changing the message level of a group in the initial definition macro and then recompiling the program.
However, you can also have your program change the message level or Enabled flag at runtime. The following table summarizes the extended diagnostic function macros you'll use to adjust these parameters.
Extended Diagnostic Function Macros DIAG_ENABLE() || Sets the Enabled flag for a given group DIAG_ISENABLED() || Returns the state of the Enabled flag for a given group DIAG_SETLEVEL() || Sets the value of the Level parameter for a given group DIAG_GETLEVEL() || Returns the value of the Level parameter for a given group
When you use the DIAG_ENABLE() and DIAG_SETLEVEL() function macros, you'll pass two parameters: the diagnostic group's name and the new state or message level. For example, to change the current message level of the Global group to 5, you would call the DIAG_SETLEVEL() function macro like this:
DIAG_SETLEVEL(Global, 5);
You can use the DIAG_ISENABLED() and DIAG_GETLEVEL() function macros as if they were normal function calls. To display the current message level for the Global group, you could use the DIAG_GETLEVEL() function macro in a statement similar to
cout
<pre> #include <owl/pch.h> DIAG_DEFINE_GROUP(Func, 1, 0); int foo(int var) { TRACEX(Func, 1, "> foo()"); TRACEX(Func, 2, "var = "
</pre>
When you finish entering the code for EXT_DIAG.CPP, save it by choosing Save from the File menu. Now, let's run the program to see the extended diagnostic macros in action.
To run this project, double-click on the EXT_DIAG.IDE project's icon in the Project window. This tells the compiler to compile the EXT_DIAG.IDE source file, link the resulting OBJ file with the appropriate library files, and then run the program from a DOS prompt.
When the program runs, it will enter the while() loop and display the following:
<pre> c:>Trace EXT_DIAG.CPP 30: [Def] Default Trace data diagnostic level 0 set diagnostic level? Enter 5 at the prompt. Next, you'll see code diagnostic level 0 set diagnostic level? Enter 5 at this prompt as well. The program now moves to the bottom of the while() loop and displays exit (Y or N)?
</pre>
Enter N. This causes the program to re-enter the while() loop and now display
<pre> Trace EXT_DIAG.CPP 30: [Def] Default Trace Trace EXT_DIAG.CPP 31: [Def] Level 1 Def Trace EXT_DIAG.CPP 32: [Def] Level 3 Def Trace EXT_DIAG.CPP 8: [Func] > foo() Trace EXT_DIAG.CPP 9: [Func] var = 10 Trace EXT_DIAG.CPP 13: [Func] returning 50 Trace EXT_DIAG.CPP 14: [Func]
</pre>
This time through, enter 1 as the data diagnostic level and 1 as the code diagnostic level. When the exit prompt reappears, enter N again and you'll see
<pre> Trace EXT_DIAG.CPP 30: [Def] Default Trace Trace EXT_DIAG.CPP 31: [Def] Level 1 Def Trace EXT_DIAG.CPP 8: [Func] > foo() Trace EXT_DIAG.CPP 14: [Func]
</pre>
If you look closely at the output above, you'll notice that the program skipped the TRACE message from line 32 because the level for the Def diagnostic group was too low. Likewise, the program skipped the messages from lines 9 and 13 that are level 2 messages for the Func diagnostic group.
Now, enter -1 for the data diagnostic level and 3 for the code diagnostic level, and then enter N at the exit prompt. During this pass, the program will display
<pre> Trace EXT_DIAG.CPP 8: [Func] > foo() Trace EXT_DIAG.CPP 9: [Func] var = 10 Trace EXT_DIAG.CPP 13: [Func] returning 50 Trace EXT_DIAG.CPP 14: [Func]
</pre>
This time, the program skipped all the messages from the Def group. This is because the -1 parameter forces the lines
<pre> if(dataLevel
</pre>
from our code to disable the Def diagnostic group by setting its Enabled flag to 0. To exit EXT_DIAG.EXE, enter any value for the diagnostic levels and then enter Y at the exit prompt.
If you decide to use the extended diagnostic macros in your projects, you may want to declare an enumeration for the different diagnostic levels. By declaring an enumeration like
const enum {FINAL, BETA2, BETA1, ALPHA1};
you can use the enumeration value in place of the level parameter in any of the extended diagnostic macros. This will make it easier for someone to know whether a given message is really appropriate for a particular debugging session.
You can create a keyboard macro for function bodies that automatically inserts entry and exit messages at the beginning and end of each function. By doing this, you'll be more likely to include some form of diagnostic message for each function.
While the TRACE() and WARN() macros are useful, the extended macros TRACEX() and WARNX() provide more display options for large projects. In addition, by using the extended function macros, you can control the diagnostic message output dynamically at runtime.
Wiki: Adding_the_new_diagnostic_macros_to_your_code
Wiki: Knowledge_Base
The instructions for Borland C++ 4.0 need to be replaced with ones for a newer development environment, like Visual C++ 2008 or 2010 --[[User:Jogybl|Jogybl]] 16:55, 20 March 2010 (UTC)