Using Non-Breaking Breakpoints in Delphi

By: Cary Jensen

Abstract: Non-breaking breakpoints are breakpoints that do not load Delphi's integrated debugger when they trigger. These special breakpoints are more powerful than they sound, and this article explains why.

At first you might wonder "What is the value of a breakpoint if it does not load the integrated debugger?". Let me tell you, the value can be very big indeed. Non-breaking breakpoints permit you to perform a variety of actions without interrupting the execution of your application.

For example, a non-breaking breakpoint can write a message to the error log, a Windows-based mechanism for logging information while your application is running. Non-breaking breakpoints can also execute functions, evaluate expressions, instruct the debugger to handle or ignore exceptions, and enable or disable entire groups of breakpoints.

But before we go further, let me clarify the terminology. A non-breaking breakpoint, so long as it is enabled, does triggers. Specifically, there is a difference between a disabled breakpoint, which does not trigger, and a non-breaking breakpoint, which does trigger, so long as its Pass count and Condition properties permit it to trigger. The only difference between a non-breaking breakpoint and a breaking breakpoint (the default) is that a breaking breakpoint loads the integrated debugger when it triggers (given that the code is running in the integrated development environment (IDE) and the integrated debugger is enabled). By comparison, a non-breaking breakpoint, when it triggers, does not load the integrated debugger.

Creating a Non-Breaking Breakpoint

To create a non-breaking breakpoint, begin by first creating a source breakpoint, an address breakpoint, or a data breakpoint. Next, display the Breakpoint Properties dialog box.

If your breakpoint is a source breakpoint (one associated with an executable line in your code), you display the Breakpoint Properties dialog box by right-clicking the breakpoint symbol in the left gutter of the editor window and select Breakpoint properties. If your breakpoint is an address breakpoint (one associated with an instruction at a particular memory address being executed), right-click the breakpoint symbol in the left gutter of the disassembled pane in the CPU window and select Breakpoint properties. If your breakpoint is a data breakpoint (one associated with the contents of a particular memory address being changed), view the Breakpoints dialog box by selecting View | Debug Windows | Breakpoints, right-click the data breakpoint, and select Properties. The Breakpoint Properties dialog box is shown in the following figure.

Once you have the Breakpoint properties dialog box displayed, click the button labeled Advanced to display the Actions section of the Breakpoint Properties dialog box (shown in the following figure). To make the breakpoint a non-breaking breakpoint, uncheck the Break checkbox in the Actions section. Unless you enable this checkbox again in the future, the integrated debugger will not load when this breakpoint triggers. 

Configuring Non-Breaking Breakpoints

You use the remaining options in the Actions section of the Breakpoints Properties dialog box to specify what should happen when this breakpoint is encountered. Each of these options is described in the following sections.

Controlling How the Debugger Handles Exceptions

If you check the Ignore subsequent exceptions checkbox, the integrated debugger will stop loading when an exception is raised, at least until another breakpoint that enables subsequent exceptions is encountered. To define a breakpoint that enables exceptions, place a second breakpoint (breaking or non-breaking), and check the Enabled subsequent exceptions checkbox.

Disabling and enabling exceptions using non-breaking breakpoints permits you to conveniently run a section of code from the IDE that raises exceptions, without interruption from the debugger. This technique is particularly useful for sections of code that raise exceptions for the purpose of displaying errors or warnings to the user, but which do not constitute bugs or other similar errors in your code.

For example, when client-side validation code (code that tests the validity of user input) raises an exception, it is really not an error, code-wise. Instead, it is to inform the user that validation has failed, suggest to the user how to correct the problem, and to abort any remaining validations or operations that would proceed had the user's input been valid. While testing your validation code it serves you no purpose to be interrupt by the integrated debugger. I often disable the debuggers involvement in exceptions prior to executing a block of code that performs client-side validation, enabling exceptions once again after the validations have been performed.

Writing Messages to the Event Log

Use the Log message field to enter a string that will be written to the event log when the non-breaking breakpoint triggers. This option is a nice alternative to using the Windows API call OutputDebugString, which requires adding additional code to your project. After running your application in the IDE, or while the integrated debugger is loaded, you can view messages written to the event log using the Log message field by select View | Debug Windows | Event Log. Message written to the event log using breakpoints are identified by the label "Breakpoint Message."

Evaluating Expressions

The Eval expression field serves two purposes. First, it can be used to evaluate any expression. This expression can include any object, method, variable, constant, resource, function, or type that is within scope of the breakpoint. After having entered a value in Eval expression, you have the option to enabled the Log result checkbox. When Log result is checked, the value of the expression is written to the event log. As with the Log Message field, Eval expression results that are logged permit you to avoid writing expression to the event log using OutputDebugString.

One of my favorite uses of Eval expression is the execution of a function that is within scope of the breakpoint, particularly a function that has side-effects. For example, imagine that upon startup your application tests for the existence of a CDS file that is used by a ClientDataSet. If the file is absent, you call CreateDataSet to create the data structure that the ClientDataSet will use, after which that structure is written to a CDS file.

During testing, you may always want to begin your application without an existing CDS file. One way of doing this is the write a function that deletes the CDS file if it exists. You can then call this function with a breakpoint by adding the function name to the Eval expression field. So long as this Eval expression is associated with a breakpoint that is encountered prior to the first testing for the presence of the CDS file, the function will always ensure that no file exists once the test occurs. Since breakpoints only trigger when the application is run from the IDE, a deployed application, or one executed outside of the IDE, will not destroy an existing CDS file.

In order to execute a function using the Eval expression field, that function must be included in your compiled code by the linker. In other words, this function must be referenced in code that might be executed, otherwise it will not be included in the compiled code due to optimizations. You can ensure that a function will not be removed during optimization by calling it from an event handler that will never execute. 

Managing Breakpoint Groups

The final two options in the Actions section of the Breakpoint Properties dialog box permit you to enable and disable groups of breakpoints. A breakpoint group consists of one or more breakpoints to which you have assigned the same group name, using the Group field on the Breakpoint Properties dialog box.

When a breakpoint group is disabled, none of the breakpoints in the group will trigger when encountered by code running in the IDE. Enabling a group restores the enabled property of any disabled breakpoints in the group.

Here is how I have used these two options. I had a segment of code that I assumed ran only once each time the application launched. If ever this code segment ran a second time, I wanted to be able to break repeatedly during the segment, in order to inspect the status of watch expressions. What I did was to place disabled breakpoints throughout this code segment, assigning each one of them the same group name, WhyTwice. In this particular instance, the WhyTwice breakpoints were breaking breakpoints. Then, I placed a separate, non-breaking breakpoint on the first executable line of this code segment, set the Pass count property of this breakpoint to 2, and finally, set the Enable group field to the group name WhyTwice.

During testing, if the non-breaking breakpoint was encountered a second time, indicating that the code segment was executing a second time, the breakpoints in the WhyTwice group became enabled, and loaded the integrated debugger when encountered, permitting me to determine what conditions existed that caused the code segment to be executed a second time.

About the Author

Cary Jensen is President of Jensen Data Systems, Inc., a Texas-based training and consulting company that won the 2002 and 2003 Delphi Informant Magazine Readers Choice Awards for Best Training. He is the author and presenter for Delphi Developer Days (www.DelphiDeveloperDays.com), an information-packed Delphi (TM) seminar series that tours North America and Europe, and Delphi Developer Days Power Workshops, focused Delphi (TM) training. Cary is also an award-winning, best-selling co-author of nineteen books, including Advantage Database Server: The Official Guide (2003, McGraw-Hill/Osborne), Building Kylix Applications (2001, Osborne/McGraw-Hill), Oracle JDeveloper (1999, Oracle Press), JBuilder Essentials (1998, Osborne/McGraw-Hill), and Delphi In Depth (1996, Osborne/McGraw-Hill). For information about onsite training and consulting you can contact Cary at cjensen@jensendatasystems.com, or visit his company's Web site at www.JensenDataSystems.com.

Copyright ) 2003 Cary Jensen, Jensen Data Systems, Inc.
ALL RIGHTS RESERVED. NO PART OF THIS DOCUMENT CAN BE COPIED IN ANY FORM WITHOUT THE EXPRESS, WRITTEN CONSENT OF THE AUTHOR.


Server Response from: ETNASC04