EWin Tutorial - Debug Mode

What's the difference between debug mode and non-debug mode?

As far as the compiler is concerned or as far as EWin is concerned? I will answer both questions.

As far as the compiler is concerned, "Debug mode" is when the compiler disables all optimizations, and generates code in an easy-to-debug format. That's all there is to that. But the EWin wizard sets things up so that when you build the "debug build", the special value DEBUG is defined. And the EWin header files look for this and add in all sorts of yummy things. Some of the functions do redundant safety checks if DEBUG is defined. But more importantly, the ASSERT() macros and LOGF() macros will only be compiled when DEBUG is on. At other times, they are thrown away by the compiler. ASSERT() macros are very handy.

So build and test your program in Debug Mode. (In MVC, you decide which version to build by choosing Build|Set Active Configuration from the menu.) And when you are about to release the product, switch to Release build. Run it in Release mode once or twice before you ship it off, by the way. Sometimes weird things can creep in between builds.


What's an ASSERT() macro? How do I use it?

An ASSERT() macro lets you assert that various things are true. Say you have a function that takes an integer, and the integer needs to be between 1 and 10.

 
void DoThing(int n)
{
  ASSERT( (n >= 1) && (n <= 10) );
  printf("%d\n", n);
}

If you call DoThing(11), a message box will pop up that says 'Assertion Failed.' It will also tell you the file name and line number.

So why is ASSERT better than just writing an if-test yourself? Two reasons. First, ASSERT() has more powers -- when the ASSERT dialog pops up, you can have that dialog end your program, or you can ignore the assertion, or you can have ASSERT crash your program right then, so that you can more easily debug it.

And second, ASSERT() only exists if DEBUG is defined. DEBUG is defined only when you are compiling in debug mode. So when you compile the code in release mode, the entire ASSERT() line is completely ignored by the compiler.

To put it another way: in Debug mode, DoThing() translates into this:

 
void DoThing(int n)
{
  (((n >= 1) && (n <= 10)) ? (void)0 :  (void) __DoFailedAssert( NULL, 
           "(n >= 1) && (n <= 10)", whatever.c, 4 ) );
  printf("%d\n", n);
}

Don't worry about what that hideous line DOES. Just compare it to what the compiler sees when DEBUG is not #defined:

 
void DoThing(int n)
{
  printf("%d\n", n);
}

See how that whole weird part goes away? That has two ramifications:

1. ASSERT() is nice and efficient -- when you make this in Release Mode, all those extra if-tests and whatnot just go away.

2. ASSERT() completely goes away. That means you need to think just a little bit before you write one. Say you wanted to ASSERT that a particular function returned 5. You might write

  void TestThing()
{
ASSERT( MyFunction() == 5 );
}

That's nice in Debug mode. But in Release Mode, that translates to

 
void TestThing()
{
}

Nada. Nothing. The correct way to do that, then, would be

 
void TestThing()
{
  int n = MyFunction();
  ASSERT( n == 5 );
}

The moral is: Use assertions to test the parameters passed to a function, the results of a computation, that sort of thing. But never do important computation IN THE ASSERT ITSELF.

You may notice that if you compile the final version of TestThing() above, you will get a warning in Release mode, that n is not used. This is true: n never gets used in Release mode. So if you want to avoid this warning, you would have to use n in some way. Or else you would only use n in DEBUG mode:

 
void TestThing()
{
  #ifdef DEBUG
  int n =
  #endif
  MyFunction();
  ASSERT( n == 5 );
}

This is ugly, but the only perfect way to avoid the error. Don't worry too much, though; in reality you usually don't run into this warning because you use the variable that the function returns.

So that's ASSERT(). I highly recommend that you use them. Just get into the habit for every new function you write. At the beginning of the function, ASSERT() that all of the parameters make sense. And if you do important calculations in the middle of the function, ASSERT() that the resulting calculation is within a valid range. It will really save you debugging time down the line, trust me.

There are a few more versions of ASSERT(). The first that I'll mention is ASSERTMSG(). This one is useful for when you are writing libraries or code that will be used by other people. It takes two parameters: the first is just like the parameter you pass to ASSERT(), and the second parameter is a message that the ASSERT box should display.

 
void TestThis(int n)
{
  ASSERTMSG( n > 5, "The parameter n needs to be greater than 5 when"
                    "you call TestThis(n)");
}

Then, if they call TestThis(3), they will get a standard Assertion Failed message, but it will also prominently display your text string. In this particular case, the text string isn't too helpful -- they can see that n should be greater than 5 because of the "n > 5" part. But in this case:

 
void TestThis(int n)
{
  ASSERTMSG(n != 2, "In TestThis(n), n cannot equal two because 2 is a"
                    "special constant that means something else.");
}

In general, use ASSERTMSG() when you want to give the user of your code a little help in figuring out what went wrong or why. Again, ASSERTMSG() is completely ignored by the compiler in Release Build.

The last version of ASSERT is pretty arcane. It is called ASSERT_RTTI. This one is handy for ASSERTing in a template. It takes two parameters. The first is the same as in ASSERT(), and the second is a variable of some type. The Assertion Failed dialog will display the type name of that second parameter. To find this name, it uses the new RTTI (Run Time Type Identification) operator that was just recently added to C++, which is where the "RTTI" part of the name comes from. If you are porting EWin to an older compiler, this may give you trouble, and you'll want to just rip it out or something. Better still, get a new compiler.

So why is ASSERT_RTTI useful? In a template, as I said, when you don't know what type of thing it's happening in. Here's a template function that adds two variables of any type and then returns the summed value. And for some made up reason, it will ASSERT if the summed value is 6.

 
template <class T1, class T2> inline T1 Sum(T1 a, T2 b)
{
  T1 x = a * b;
  ASSERT_RTTI(x != 6, T1);
  return x;
}

Now, if this ASSERTs, it will display something along the lines of "Assertion Failed: x != 6 (Type is 'int'). File blah blah Line xx." Where 'int' will be replaced with whatever type T1 happens to be in that template.


What does LOG do? What about LOGF? LOG1?

This is another very useful trick debugging trick: it writes text messages to a console window. By calling

 
LOG("This is text");

Your program will create a console window (if need be) and will display that text, along with the file name and line number that it originated from. So when you can't or don't want to trace through your code, you can use LOG and its cousins to write debugging information. And like ASSERT(), these are only displayed when using the Debug Build of your program.

To pass parameters to the log, you can use LOGF. This is like printf(), which is where it gets its name.

 
 LOGF("The variable in question is currently set to %d", VarToDebug);

This is mighty handy. Unfortunately, LOGF should no longer be used because it isn't thread safe. That is, if you have multiple threads all calling LOGF at the same time, only one of the two LOGFs will have the correct filename and line number. The other one may get gibberish. Additionally, and more importantly, LOGF doesn't completely go away when you use Release Build. A very small portion of the code remains. It doesn't hurt anything, but it makes your release program bigger. This is bad. And I can't fix it -- these flaws are due to a limitation of the C macro preprocessor.

So don't use LOGF. Instead, use LOG1, LOG2, LOG3, etc. Count the number of extra parameters you want to pass, and use that version:

 
LOG1("This has one other parameter: %d", n);
LOG2("%d %d", n, m);
LOG8("%d %f %s %d %f %d %d %d", n, s, r, t, w, q, z, p);

That sort of thing. These are thread-safe, and they also completely remove themselves when you make a Release Build.

Oh, one other note: the data is sent both to a console window and also to the "Debug" tab of MSVC. This is nice if you want to see the LOG output after the program has ended (since when the program ends, the console also goes away). You can also view the output in several other ways, as explained in the next section.



How do I fine-tune the debugging stuff?

So logging to a console is pretty handy, right? But logging to a console can also be very slow. Sometimes you don't want to write to a console. You may also want to send LOG messages to a file, or to a socket, or to an external viewer. You can do all this, plus other things. How? You set some flags, you call some functions. An important thing to note is that all of these functions only exist in Debug Build; in Release Build, all these functions just mystically disappear, replaced with empty macros that do nothing.

So the first thing to explain would be the various flags you can set. These control all sorts of things. [Note that all the flags turn things *OFF*. By default, all of these powers are enabled.] You call SETDEBUGFLAGS(n), where n is a combination of some of these flags:

 
  //never write to a console window
#define DEBUGFLAG_NOCONSOLEWRITE            1 
  //never call OutputDebugString
#define DEBUGFLAG_NOOUTPUTDEBUGSTRINGWRITE  2 
  //never call MessageBox()
#define DEBUGFLAG_NOMESSAGEBOXWRITE         4 
  //don't watch to see if recursion occurs in __DoFailedAssert
#define DEBUGFLAG_NOTRACKINGRECURSIVEASSERT 8 
  //don't change colors in console windows
#define DEBUGFLAG_NOCOLORCONSOLETEXT       16 
  //never send messages to an external log-viewer
#define DEBUGFLAG_NOEXTERNALVIEWERWRITE    32 
  //when writing LOG/LOGF messages, don't truncate the filename path
#define DEBUGFLAG_NOTRUNCATEDFILEPATH      64 
  //never write LOG/LOGF messages to COM1
#define DEBUGFLAG_NOCOMLOG                128 
  //never write LOG/LOGF messages to COM1 or external logfile
#define DEBUGFLAG_NOLOGFILELOG            128 
  //assert messageboxes won't be application-modal
#define DEBUGFLAG_NOAPPMODALMSGBOX        256

Some of these are kind of arcane. I will go over the useful ones.

DEBUGFLAG_NOCONSOLEWRITE. If you add this flag, you will not get a little logging console. Data will never be sent there.

DEBUGFLAG_NOOUTPUTDEBUGSTRINGWRITE. If you add this flag, LOG and its friends will never write data to the "Debug" tab of MSVC.

DEBUGFLAG_NOMESSAGEBOXWRITE. If you add this flag, you will never get popup dialogs, not even for failed ASSERT macros. You will just get a little note in the log that an assertion failed. This is mostly only useful when you are testing your program via scripts, and you don't want unexpected message boxes to appear.

DEBUGFLAG_NOTRACKINGRECURSIVEASSERT. Very arcane. But it does just as it says: it doesn't keep track of whether ASSERT() functions happen recursively. Normally it does. I don't have any idea why you would want to use this flag!

DEBUGFLAG_NOCOLORCONSOLETEXT. Hoo boy, this is arcane. But there is a way to get color text in your output log. I will explain how a little later. Anyway, this flag turns off colors.

DEBUGFLAG_NOEXTERNALVIEWERWRITE. Normally, you can use a simple viewer program called LOGVIEW.EXE to view the debug messages. By using this flag, you turn that power off. LOGVIEW is pretty much like a console window, except that it is faster to write to than a console window. Turn this off if you want LOG to be ever-so-slightly-faster.

DEBUGFLAG_NOTRUNCATEDFILEPATH. Arcane. If you have very long file names, LOG will truncate them to a nicer size so that the output looks nicer. But you can turn truncation off via this flag.

DEBUGFLAG_NOLOGFILELOG. Never write to a separate output file. You can have LOG messages and whatnot sent to a separate file via calling LOGFILEOPEN() as documented below. Setting this flag causes no messages to be written to this file. It also stops things from being written to the COM port, since the COM port uses the same system as LOGFILEOPEN().

DEBUGFLAG_NOCOMLOG. Same as DEBUGFLAG_NOLOGFILELOG. See above.

DEBUGFLAG_NOAPPMODALMSGBOX. Arcane. Assertion Failed MessageBoxes will not have the application-modal flag set.

So those are the flags. In addition to SETDEBUGFLAGS(), there are some more debugging-tool functions:

SETDEBUGCONSOLESIZE(x,y) : this sets the size of the debugging console to a given number of columns and rows. If this isn't used, the "system defaults" are used.

SETDEBUGIDENTIFIER(x) : this sets the ID of this console. This is theoretically usable by external viewer programs. However, only one external viewer exists, LOGVIEW.EXE, and it does not currently use this ID.

LOGFILEOPEN(fn) : opens the file fn and writes all logged messages (and messages about failed assertions) to this file.

LOGFILECLOSE() : if you call LOGFILEOPEN(), you should call this before your program ends, so that any buffered log messages are written to the file.

LOGFILEWRITE(s) : directly writes a string to the file that you opened via LOGFILEOPEN.

COMOPEN(fn), COMCLOSE(), COMWRITE(s) : similar to LOGFILEOPEN, LOGFILECLOSE, and LOGFILEWRITE. In fact, they are the same functions, internally. Instead of passing a file name, though, you pass COMOPEN the string "COM1" or "COM2" or "COM3" or "COM4".

COMSETSTATE(s) : sets the state of the COM port opened via COMOPEN. This string should be in the format received by the BuildCommDCB API function.

SOCKOPEN(fn), SOCKCLOSE(), SOCKWRITE(s) : same as the LOGFILE functions and the COM functions above, but intended for use with sockets. You pass SOCKOPEN() a string in the format "#127.0.0.1:3053" but replacing 127.0.0.1 with any IP address, and 3053 with any port number. This causes log messages to be sent as DATAGRAM packets to that address & port number.

ENABLEDEBUGTYPE(), ENABLEALLDEBUGTYPES(), DISABLEDEBUGTYPE(),
DISABLEALLDEBUGTYPES(), ISDEBUGTYPEENABLED() : these are used with the XLOG family of functions, described in the next and final question about debugging.


Okay, what is XLOG? How does it differ from LOG?

XLOG is like LOG, except that it takes a first parameter that indicates what "type" of logging message it is. For instance, if you had this:

 
LOG("Beginning matrix sequence now.");

You might decide to mark this as a "matrix" log message:

 
XLOG("matrix", "Beginning matrix sequence now.");

When you write it out to the log, this type string will be noted in the console. And similarly there are XLOGF, XLOG1, XLOG2, etc. that mimic LOGF, LOG1, LOG2, etc., but that take an extra parameter at the beginning.

By giving your log messages "types", you can turn different types on and off at run-time, so that you only log the data you want to see at that particular time. If you have a dozen different types of log messages, you'll probably only want to see one or two types at a time, as you try to debug those systems. So in your OnInit() or wherever, you can disable or enable the types you want.

By default, all types are enabled. The functions you can use to enable or disable different types are:

  ENABLEDEBUGTYPE(),
ENABLEALLDEBUGTYPES(),
DISABLEDEBUGTYPE(),
DISABLEALLDEBUGTYPES().

So you could disable all the types except for one particular type via this code:

 
DISABLEALLDEBUGTYPES();
ENABLEDEBUGTYPE("matrix");

Note that type names are case sensitive.

And you can use ISDEBUGTYPEENABLED() to test if a type is enabled. Note that this function always compiles to 0 (false) in release mode.

And for weirdness points, there's this thing that allows colors: if the type name starts with # and then two hex digits, messages of that type name will be in color. For instance,

 
XLOG("#14DATA", "IMPORTANT DATA");

This will be red text on a blue background. The two-digit number is evaluated as a hex number made up of the following console-color flags:

 
#define FOREGROUND_BLUE      0x0001 // text color contains blue.
#define FOREGROUND_GREEN     0x0002 // text color contains green.
#define FOREGROUND_RED       0x0004 // text color contains red.
#define FOREGROUND_INTENSITY 0x0008 // text color is intensified.
#define BACKGROUND_BLUE      0x0010 // background color contains blue.
#define BACKGROUND_GREEN     0x0020 // background color contains green.
#define BACKGROUND_RED       0x0040 // background color contains red.
#define BACKGROUND_INTENSITY 0x0080 // background color is intensified.

 

 (go on to the next page in the tutorial...)