ASSERTs in der MFC und in der CRT sind tolle Hilfsmittel, aber nicht selten verfälschen sie auch das Problem alleine dadurch, dass ein Fenster aufpoppt, wenn der ASSERT zuschlägt. Hat man nun einen Code, der in einem Tooltipp etwas Böses macht, dann wird der Tooltipp selbst aber schon wieder durch das erscheinen der ASSERT Meldung zerstört. Oder es wird ein neuer ASSERT ausgelöst. Der Callstack wird dadurch oft schwer zu lesen.
Besonders heikel kann dies auch noch werden wenn man mehrere Threads hat. Gleichfalls problematisch ist, dass in dem Moment in dem die ASSERT Box auftaucht nun auch wieder alle Timer weiterlaufen und sehr eigentümliche Seiteneffekte weiter auslösen können, dito. Probleme in WM_PAINT Handlern, denn auch die lösen evtl. schon wieder Aktionen aus, die Variablen verändern.
Nett ist am ASSERT-Dialog natürlich die Möglichkeit Ignorieren zu sagen und das Programm weiter laufen zu lassen. Ganz besonders wenn man Debug Versionen im Testfeld mit Anwendern testet.
Dennoch bin ich bei Debug-Versionen dazu übergegangen ASSERTs direkt crashen zu lassen, bzw. direkt einen Debug-Break auszulösen. Das erleichtert das Lesen des Crashdumps bzw. hilft auch beim Debuggen, weil man direkt an der Stelle steht wo es hakt und alle Fenster und Variableninhalte exakt noch so sind, wie Sie es beim Auftreten des Problems waren (Tooltips, Popups, Menüs etc.).
Der Code um das zu erreichen ist relativ simpel. Man verwendet dazu _CrtSetReportHook2. In dem Hook sagt man einfach was man gerne hätte. Nämlich bei einem ASSERT oder ERROR keinen Dialog sondern einen Break (INT3).
#ifdef _DEBUG
int __cdecl DebugReportHook(int nReportType, char* , int* pnRet)
{
// Stop if no debugger is loaded and do not assert, cause a crash
// - returning TRUE indicates that we handled the problem, so no other hook
// needs to perform any action
// - setting the target of *pnRet to TRUE indicates that the CRT should
// execute an INT3 and should crash or break into the debugger.
return *pnRet = nReportType==_CRT_ASSERT ||
nReportType==_CRT_ERROR ?
TRUE : FALSE;
}
#endif
void SetBreakOnAssert(BOOL bBreakOnAssert/* =FALSE */)
{
// Need to disable the ASSERT handler?
#ifdef _DEBUG
if (bBreakOnAssert)
_CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, DebugReportHook);
else
_CrtSetReportHook2(_CRT_RPTHOOK_REMOVE, DebugReportHook);
#else
UNUSED_ALWAYS(bBreakOnAssert);
#endif
}
Durch diese kleine Funktion SetBreakOnAssert kann man dieses Verhalten nun einfach ein- und ausschalten. Nähere Details stehen im Kommentar der Hook-Funktion.