4

The code below is reproduced from Toolbar2000. It is part of routine that reads toolbar positions and dock states from an INI file. I call this routine during initialisation. This code below is iterating through all the components on the main form (OwnerComponent) and loading the settings of any toolbars it finds.

for I := 0 to OwnerComponent.ComponentCount-1 do begin
  ToolWindow := OwnerComponent.Components[I];  //  <------------------------
....

This iterating takes some time (seconds - there are 1500-odd components on the form) and I'm getting a range error at the point shown. I have ascertained that one or more items is being shed from the main form's components while this loop is executing, so eventually the loop tries to access one past the end of the array once this has happened (presumably it would be better to code this as a "downto" for-loop to prevent this).

Anyway, I need to find out where the main form is losing a component. Can anybody give me any Delphi 2006 debugging tips on how to do this? I wouldn't expect any main form components to be freed at this point in my program.

UPDATE

I found that when I had repositioned a toolbar's default dock position at design-time I had inadvertently docked it onto another toolbar, rather than the dock site that the other toolbar was in. I fixed the problem by removing the toolbar from the toolbar it was docked in and adding it to the dock instead. So the arrangement that caused the problem was:

Dock 
  Toolbar 1
    Control 1
    Control 2
    Toolbar 2
      Control 3
      Control 4

and the fix was to arrange them thus:

Dock 
  Toolbar 1
    Control 1
    Control 2
  Toolbar 2
    Control 3
    Control 4

It still points to a bug in the TB2k code though - one would assume it should be able to handle nested toolbars.

rossmcm
  • 5,493
  • 10
  • 55
  • 118
  • This doesn't seem to be a normal behaviour, unless the timing is wrong (the owner itself is still in the process of creating). When/where exactly are you loading the settings for your toolbars? – Andriy M Apr 28 '11 at 07:12
  • Use a `for` loop only when the number of items is fixed before the iteration starts. Use `while` or `repeat` when you don't know how many iterations the loop will need. `Repeat` the check conditions after each iteration (and you know that it is at least one iteration), and `while` to check conditions before each iteration (it may be no iteration at all). – Jørn E. Angeltveit Apr 28 '11 at 08:25
  • @Andy - the call to load the INI file settings is from an initialisation routine that in turn is called from the DPR (main program) just before Application.Run, after all the forms have been created. Even if the main form hadn't yet finished creating, I wouldn't expect the componentcount to _decrease_ – rossmcm Apr 28 '11 at 08:52
  • @Jørn - (the code belongs to Jordan Russell) I guess he expected the owner's component list to be constant in size for the duration of the loop. If this was the case then a for...to loop would be appropriate. In the general case, where something can "get in" and remove an item from the list during the loop execution (as it looks like must be happening here), I guess you can't can't safeguard accesses to the Components list except by an explicit check before each reference. – rossmcm Apr 28 '11 at 09:03
  • @Andriy - I misspelled your name... – rossmcm Apr 28 '11 at 09:05
  • @rossmcm: Well, that sounds a fine place to me, I wouldn't expect the count to change either. So far then it seems to be beyond my level of expertise, sorry. – Andriy M Apr 28 '11 at 10:08

2 Answers2

6

In addition to Lieven's answer, you could also use debug dcu's and set a breakpoint in TComponent.Destroy just before you enter the loop.

In both cases you will need to examine the call stack to see where the call/change to count is coming from.

A very interesting article on breakpoints was written by Cary Jensen: http://caryjensen.blogspot.com/2010/08/breakpoints-with-side-effects.html

Marjan Venema
  • 19,136
  • 6
  • 65
  • 79
  • Better yet: set the breakpoint in TComponent.Notification; this will track the error if the component is not destroyed but only removed from the component list of the main form. – Ritsaert Hornstra Apr 28 '11 at 09:02
  • and @Lieven - On the basis of your comments I put a breakpoint at the start of TComponent.Remove just before I entered the loop and it triggered. Now it's just a matter of figuring out why Jordan's code removes components when it's reading the settings. I think it's tied up with the docking but I can't see it yet... – rossmcm Apr 28 '11 at 09:46
  • @rossmcm - well found, you may want to report this to TB2K's Jordan Russel. – Marjan Venema May 02 '11 at 06:00
3

You'll have to add a data breakpoint at @Self.FComponents.FCount to break whenever the count changes.

  • ComponentCount is a property that returns the value from GetComponentCount
  • GetComponentCount returns FComponents.Count.
  • FComponents is a TList instance that has a private FCount variable.
Lieven Keersmaekers
  • 57,207
  • 13
  • 112
  • 146