Click here to Skip to main content
15,886,518 members
Articles / Desktop Programming / ATL

Recursive Assertions Obscure The Real Problem with a Phantom Assertion

Rate me:
Please Sign up or sign in to vote.
3.90/5 (8 votes)
2 Jul 2008CPOL5 min read 27.5K   92   12  
Assertions are a very effective debugging tool for C/C++ code. But, a very subtle problem exists with assertions that can cause you to waste a lot of debugging time chasing the wrong problem.
//////////////////////////////////////////////////////////////////////
// MessageBoxThread.h
//

#pragma once

// All Windows assertions have a problem:
// They all end up calling the Win32 API MessageBox().  The problem with MessageBox is that it has a message pump that will
// continue processing windows messages for the thread it was called on.
// This can lead to recursive assertions if a windows message handler also asserts.
//
// I have rewritten these macros so that the MessageBox is on its own thread.  Now, the calling thread will actually, completely halt.
// All you need to do is #include this header file, and it will replace the standard assertions with these threaded ones.
//
// You can also call the functions directly if you want to do your own error handling or put up a different message type
// as in the code below
//
// CMessageBoxThread::DebugReport(_CRT_WARN, _T(__FILE__), __LINE__, _T(__FUNCTION__), msg);
//
// CMessageBoxThread::MessageBox will put up a standard MessageBox, but also in its own thread so the calling thread will be completely halted.
//
// CMessageBoxThread::MessageBox("The main thread is really halted", "Alert!", MB_ICONEXCLAMATION | MB_OK);
//
// Written by Curt Dixon (curtd2004-code@yahoo.com)
//
// This code is provided under the The Code Project Open License (CPOL)

#include <crtdbg.h>
#include <stdlib.h>
#include <stdarg.h>


#if _DEBUG

#ifdef ATLASSERT
#undef ATLASSERT
#endif

#ifdef ASSERT
#undef ASSERT
#endif

#ifdef assert
#undef assert
#endif

#ifdef _ASSERT_EXPR
#undef _ASSERT_EXPR
#endif


// replacing _RPT_BASE has the effect of replacing all the _RPTn() macros
#undef _RPT_BASE
#define _RPT_BASE(args) \
	(void) ((1 != CMessageBoxThread::DebugReport args) || (__debugbreak(), 0))

#undef _RPT_BASE_W
#define _RPT_BASE_W(args) \
        (void) ((1 != CMessageBoxThread::DebugReport args) || (__debugbreak(), 0))


// !! avoids problems with overloaded operator||()
#define _ASSERT_EXPR(exp, msg) \
	(void)(!!(exp) || (CMessageBoxThread::DebugReport(_CRT_ASSERT, _T(__FILE__), __LINE__, _T(__FUNCTION__), msg) != 1) || (__debugbreak(), 0))

#define _ASSERTE(expr)  _ASSERT_EXPR((expr), _CRT_WIDE(#expr))
#define ASSERT(expr)   _ASSERT_EXPR((expr), NULL)
#define assert(expr) _ASSERT_EXPR((expr), NULL)


#ifdef _UNICODE
#define _CrtDbgReport _CrtDbgReportW
#endif


#else // _DEBUG

#ifndef ATLASSERT
#define ATLASSERT
#endif

#ifndef ASSERT
#define ASSERT
#endif

#ifndef assert
#define assert
#endif


#endif


class CMessageBoxThread
{
public:

	// parameters for _CrtDbgReport that are passed between threads
	struct _DEBUG_DATA
	{
		int nRptType;        // _CrtDbgReport type (_CRT_WARN, _CRT_ERROR, or _CRT_ASSERT)
		LPCTSTR pszFile;     // File name
		int nLine;           // Line #
		LPCTSTR pszModule;   // Module name
		TCHAR msgbuf[1024];  // make this as large as you need
	};


	// parameters for MessageBox that are passed between threads
	struct _MSG_DATA
	{
		HWND hWnd;
		LPCTSTR lpText;
		LPCTSTR lpCaption;
		UINT uType;
	};


	// this is the thread routine for an assertion (or other _CrtDbgReport type)
	static DWORD WINAPI CrtDbgReportThread(LPVOID lpParameter)
	{
		return _CrtDbgReport(((_DEBUG_DATA*)lpParameter)->nRptType,
			((_DEBUG_DATA*)lpParameter)->pszFile,
			((_DEBUG_DATA*)lpParameter)->nLine,
			((_DEBUG_DATA*)lpParameter)->pszModule,
			_T("%s"),
			((_DEBUG_DATA*)lpParameter)->msgbuf);
	}

	// this is the thread routine for a general purpose MessageBox
	static DWORD WINAPI MessageBoxThread(LPVOID lpParameter)
	{
		return ::MessageBox(((_MSG_DATA*)lpParameter)->hWnd,
			((_MSG_DATA*)lpParameter)->lpText,
			((_MSG_DATA*)lpParameter)->lpCaption,
			((_MSG_DATA*)lpParameter)->uType);
	}

	// creates the message box thread and waits for it to close
	static DWORD CreateThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter)
	{
		// create the MessageBoxThread and pass it the msg data
		HANDLE hThread = ::CreateThread(NULL, 0, lpStartAddress, lpParameter, 0, NULL);
		::WaitForSingleObject(hThread, INFINITE);    // calling thread is now REALLY frozen right here
		DWORD dwExitCode = 0;
		GetExitCodeThread(hThread, &dwExitCode);
		CloseHandle(hThread);
		return dwExitCode;
	}

	// This function returns the results of the _CrtDbgReport() call.
	// -1 if errors occurred or 0 if no errors are encountered.
	// However, when the report destination is a debug message dialog and the user clicks the Retry button,
	// dwExitCode = 1. If the user clicks the Abort button in the debug message dialog,
	// _CrtDbgReport immediately aborts the application.

	static DWORD DebugReport(int nRptType, LPCTSTR szFile, int nLine, LPCTSTR szModule, LPCTSTR szFormat, ...)
	{
		_DEBUG_DATA	data;
		data.nRptType = nRptType; 
		data.pszFile = szFile;
		data.nLine = nLine;
		data.pszModule = szModule;

		va_list arglist;
		va_start(arglist, szFormat);
		memset(data.msgbuf, 0, sizeof(data.msgbuf));
		if (szFormat)
			_vstprintf_s(data.msgbuf, sizeof(data.msgbuf) / sizeof(*data.msgbuf), szFormat, arglist);

		// create the CrtDbgReportThread and pass it the msg data
		return CreateThread(&CrtDbgReportThread, (LPVOID)&data);
	}

	// This method returns the return code from the ::MessageBox() call

	static DWORD MessageBox(LPCTSTR pText, LPCTSTR pCaption = NULL, UINT uType = MB_OK, HWND hWnd = NULL)
	{
		_MSG_DATA	data;
		data.hWnd = hWnd; 
		data.lpCaption = pCaption;
		data.lpText = pText;
		data.uType = uType;

		// create the MessageBoxThread and pass it the msg data
		return CreateThread(&MessageBoxThread, (LPVOID)&data);
	}
};


By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions