Introduction
The more complex the applications get, the more we focus on the functionality to be provided by the code we write. It happens sometimes that we are so absorbed by our ideas that we simply forget to put a semi-colon (or we put it just where it should not be). In most of the cases, we get rid of the problem as soon as we try to compile the code. That is the case of syntax errors (and just do not claim being an "error-free" developer, I will not buy it). Things are going worse when dealing with logical errors. One common practice is to use infinite loops implemented through for
or while
blocks and decide when the loop is to be left. The code block gets a little bit longer than expected and someone may forget to write the line of code responsible for breaking the loop. At other times, one might use a loop with a well defined condition for leaving the loop, and, by mistake, alter an index involved in the looping condition as a result of a sequence of computations. There are also situations when one may use a while
loop for extracting data from a record set, with the reaching of EOF
as condition to leave the loop, and simply forget to call MoveNext()
. This last one happened to me a little time ago. Of course I found the error, but it was somehow embarrassing using the well-known CTRL-ALT_DEL key sequence (God save CTRL-ALT-DEL) several times. And anyway, that is not the point. My point is looking for a way to make the programming environment be aware as much as possible of my programming act and allow me recover fast from and localize my logical errors when working with loops.
I wanted a means of being able to press a key when seeing the application is hanging, and regain the control over the application while receiving some hint on the cause of hanging and, if possible, a hint on the control which had the focus at the moment the application started hanging. I found that using macros could help a lot. And here is the BreakInfinite.h file, which you simply include in StdAfx.h to help the programming environment help you in the infinite looping issue.
Working with BreakInfinite
Start by copying the BreakInfinite.h file in one of the folders used by Visual Studio as include folders. Then add the following line in your StdAfx.h file:
.
.
#endif
#include "BreakInfinite.h" // <-- this is your new line
//{{AFX_INSERT_LOCATION}}
.
.
Note: The BreakInfinite support is active only in DEBUG mode.
In order to activate the BreakInfinite support for while
-loops, write while
with capital letters, i.e. WHILE
. The _fn_while()
function behind the scene will let you gain the control back from the hanging application.
In order to activate the BreakInfinite support for for
-loops, use the FOR / NEXT
pair instead of for
and the accompanying {}
brackets. The _fn_for()
function behind the scene will let you gain the control back from the hanging application.
I wrote a small dialog-based application to test the BreakInfinite functionality in DEBUG mode. Two buttons throw the application in infinite loops. By pressing the F8 key, a message box pops up indicating the infinite loop condition (while
or for
) and the caption of the control which had the focus at the moment the application started hanging. At the same time, an exclamation icon appears over that control.
After closing the message box, the exclamation icon disappears off the control and the application hangs no longer.
The BreakInfinite also provides support for further use of its functionality, wherever you may need it. You may want to stick to the classical while
style and use the break feature in RELEASE mode. Or you may want to take some action on pressing of the F8 key. For that, you just have to place a call to the _fn_continue()
within your code:
if ( !_fn_continue(this, "Some message") ) break;
The code behind
Through the definition of the following macros, in RELEASE mode, WHILE
becomes simply while
, and FOR / NEXT
turns back to for
and its accompanying {}
brackets.
#ifdef _DEBUG
#if !defined(_INFINITE_LOOP_BREAK_)
#define _INFINITE_LOOP_BREAK_
#define WHILE(CONDITION) while ( _fn_while(CONDITION, this) )
#define FOR(BLOCK_EXPR) for ( BLOCK_EXPR ) {
#define NEXT if ( !_fn_for(this) ) break; }
#endif
#else
#if !defined(_INFINITE_LOOP_BREAK_)
#define _INFINITE_LOOP_BREAK_
#define WHILE while
#define FOR(BLOCK_EXPR) for ( BLOCK_EXPR ) {
#define NEXT }
#endif
#endif
In DEBUG mode, the boolean WHILE
-condition is replaced with the boolean value returned by the inline
function _fn_while()
to which the original WHILE
-condition is passed as parameter. In the case of the FOR / NEXT
block, FOR
is replaced by the classical for
followed by the opening {
bracket. NEXT
is replaced with the check of the boolean value returned by the inline
function _fn_for()
and the closing }
bracket. Both of the previously mentioned functions decide on the value they return upon the result of calling another inline
function: _fn_continue()
:
inline BOOL _fn_while(BOOL bCondition, CWnd *pWnd)
{
if ( !_fn_continue(pWnd, "Infinite while-loop") ) return FALSE;
else return bCondition;
}
inline BOOL _fn_for(CWnd *pWnd)
{
return _fn_continue(pWnd, "Infinite for-loop");
}
_fn_continue()
is the core of the BreakInfinite functionality. It needs two parameters: a pointer to the window in whose message queue it looks for the keyboard message related to pressing the F8 key, and a pointer to a null-terminated string used to build the text message shown by the message box that pops up in response to that key event.
_fn_continue()
starts by checking the thread message queue for a keyboard message and places the message (if any) in a MSG
structure (theMsg
):
PeekMessage(&theMsg, pMsgWnd->m_hWnd, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)
Should such a message be available, it checks if the message is related to pressing the F8 key. If no F8-related keyboard message is present, the function translates and dispatches the message (if any) and returns TRUE
, telling the application to continue whatever it was doing before.
In case the F8 key was pressed, _fn_continue()
gets the handler of the control currently having the focus and pops up a message box showing the infinite loop condition and the caption (if any) of that control. Through popping-up the message box, you get the control from the hanging application, as the function returns FALSE
after the message box closes. The FALSE
return value is further used as a condition to break the loop. Therefore the application hangs no longer.
History