![]() |
![]() |
|||||||
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|||
What's Wrong With This Code? Volume #7Phantom UpdatesLet's show the source code for the program one more time. //----------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //----------------------------------------------------------------- void __fastcall TForm1::btnUpdateClick(TObject *Sender) { Update(); } //----------------------------------------------------------------- void __fastcall TForm1::Update() { // update database // ... ShowMessage("Database Updated!"); } //----------------------------------------------------------------- The best way to debug a problem like this is to use the Call Stack viewer. We know that moving the splitter bar is somehow calling our database update routine. If we put a breakpoint in the Update function, we can inspect the call stack at the moment Update was called. This is exactly how I debugged the program originally. I put a breakpoint in Update, ran the program, opened the Call Stack viewer, and then moved the splitter bar. Moving the splitter bar trips the the breakpoint in Update. Figure 2 shows what the call stack looks like at this moment. ![]() Figure 2. Debugging the phantom update using the Call Stack viewer
At the top of the call stack, we see our Update function. Look at the bottom row of the call stack, as its shown in Figure 2. The last function is TControl::WMLButtonUp. This is a method of the splitter control. WMLButtonUp calls DoMouseUp, and DoMouseUp calls MouseUp. These are also member functions of the splitter control. MouseUp calls UpdateControlSize. This is where things get interesting. Let's look at the code for UpdateControlSize. procedure TSplitter.UpdateControlSize; begin ... Update; end; The splitter bar executes a VCL method called Update. That's strange. Update is the same name as the function that I added to my form to update the database. I had forgotton that the VCL also had an Update function. The VCL Update method is introduced by TControl. When you move the splitter bar, the splitter component calls its own Update method. Not to worry though, because the splitter's Update function is separate from the Update method of my form. Let's continue moving up the call stack. What does the Update method of the splitter class do? The splitter control relies on the functionality provided in TControl::Update. The Update method of TControl looks like this: procedure TControl.Update; begin if Parent <> nil then Parent.Update; end; The Update method of TControl simply executes the Update method of its parent. Who is the parent of the splitter control? It's the form. At this point, bells should be going off in your head. When the splitter control executes the Update method of the form, it actually calls the database Update method of our derived form class. The problem with my code is the name that I chose for my database update function: Update. This name conflicts with the Update method of TControl. The help file for BCB4 says this about TControl::Update: --------------------------------------------------------------------- TControl::Update Processes any pending paint messages immediately. virtual void __fastcall Update(void); ... --------------------------------------------------------------------- Notice that the function is declared as virtual. This turns out to be the source of the problem. When you move a splitter bar, the splitter eventually calls the Update member function of the form. We witnessed this while debugging the program with the call stack. So which Update method is the splitter bar trying to call? Well, it wants to call the version of Update that has to do with painting the form. However, my code accidentally overrides the painting routine with a function that updates a database. When the splitter control calls the Update method of the form, the virtual dispatching of C++ invokes the update routine in the derived form class. Instead of repainting the form, the splitter bar updates the database! So why does the code work in C++Builder 3? Originally, I thought that maybe TControl::Update was not virtual in BCB3. This is not the case. Update is virtual in C++Builder 3. The difference between BCB3 and BCB4 is that TSplitter::UpdateControlSize does not call Update in BCB3. Since it does not call Update, the bug does not appear when compiling the code in BCB3. This does not mean that the BCB3 code is bug free. It just means that the splitter control will not cause the bug to surface. There are a couple of lessons to be learned here. First of all, be careful when you choose a name for a member function. You must be careful not to accidentally override a virtual function from a base class. The second lesson is to stay calm when a tester submits a bug with strange symptoms. In this edition of "What's wrong with this code", the answer to the problem was fairly simple, but the description of the problem was very strange. Lastly, never use the splitter control. It can be a huge source of bugs. Just kidding! Note: Here is an exercise for the reader. Would this have been a problem if I wrote my database app in Delphi instead of C++Builder? Would moving the splitter bar cause an update if I compiled with Delphi 4 or Delphi 5? Why or why not? | ||||||||
All rights reserved. |