Delphi 2.0 Bug List

Posted: (EET/GMT+2)

 

Delphi 2.0 Bug List

Here are some bugs I've found while using Delphi 2.0. I have submitted these bugs to the Delphi 2.0 Bug List maintained by Reinier Sterkenburg. You can find these bugs from there, and possibly elsewhere.

Currently 14 bugs are documented.

[ VCL | RTL | IDE | Compiler ]



Visual Component Library (VCL)


  • (ComCtrls) TListView bug: when modifying the SubItem property strings, the control is not repainted automatically. The repaint method, Refresh, must be called manually.

    Problem Example:

      TListView1.SubItems[0] := 'XYZ';
      

    The above modification is not automatically reflected on-screen.

    Solution: Call Refresh after modification.

    Solution Example:

      TListView1.SubItems[0] := 'XYZ';
      TListView1.Refresh;  { call Refresh to see the new value }
      


  • (ComCtrls) TListView bug: when changing the Parent property in code, the entire control is reloaded from the form file (or executable file). This means that every property setting that has been modified in code before setting the Parent property is lost.

    Problem Example:

      With TListView1 do Begin
        Left := 123;
        Top := 321;
        Parent := Form2;
      End;
      

    The above modifications to the Left and Top properties are lost after the Parent property has been set.

    Problem location:

    The ComCtrls.pas file in procedural method TCustomListView.CreateWnd. The code calls the FMemStream.ReadComponentRes method to reload the whole component, but instead it should only read the column/item values.

    Solution: Set Parent property before setting other properties.

    Solution Example:

      With TListView1 do Begin
        Parent := Form2;	{ set the Paremeter property first }
        Left := 123;
        Top := 321;
      End;
      


  • (StdCtrls) TEdit bug: when using the read-only style (ReadOnly = True), the control is not changed the have a gray background, like suggested in the Windows Interface Guidelines for Software Design by Microsoft, page 158.

    Problem example:

      Edit1.ReadOnly := True;

    The above only sets the read-only style correctly, but does not change the color of the control.

    Solution: Set the color manually.

    Solution example:

      With Edit1 do Begin
        ReadOnly := True; { set to read-only }
        Color := clBtnFace; { indicate different behavior to user}
        ...
        ReadOnly := False; { back to read-write }
        Color := clWindow; { restore normal color }
      End;
      


  • (StdCtrls) TComboBox bug: when setting the ItemIndex property, the combobox control is redrawn regardless of the new value. For example, if the new value is equal to the old index, the control is redrawn, thus causing unpleasant and unnecessary flicker.

    [Note: This bug does not apply to TListBox.]

    Problem example:

      Var I : Integer;
      ...
      With ComboBox1 do Begin
        I := ItemIndex;
        ItemIndex := I; { re-set to old value --> flicker }
      End;
      

    The above code sets the ItemIndex of the combobox to the same value it already was. This causes the the control to be redrawn unnecessarily. This can lead to performance problems, or at least unpleasant flicker especially if the combobox items are "owner-drawn".

    Problem location: The STDCTRLS.PAS file in procedural method TCustomComboBox.SetItemIndex. The code sends a CB_SETCURSEL message to the control without checking if the item index has actually changed.

    Solution #1: Check to see if the ItemIndex actually needs to be set.

    Solution #1 example:

      Var I : Integer;
      ...
      With ComboBox1 do Begin
        I := 123;
        { set to the new value }
        If (I <> ItemIndex) Then { compare with old value }
        ItemIndex := I; { if not equal, set property }
      End;
      

    Solution #2: If you want to avoid this problem in the future and you have the VCL source code, you could modify STDCTRLS.PAS directly.

    Solution #2 example:

      {
      New version of SetItemIndex: this version checks to see
      if the "new" value is actually different than the previous one.
      }
      
      procedure TCustomComboBox.SetItemIndex(Value: Integer);
      begin
        if GetItemIndex <> Value then
      	SendMessage(Handle,CB_SETCURSEL, Value, 0);
      end;
      

  • (ComCtrls) TUpDown bug: if the minimum value (Min property) is set to below zero, and the Increment property is bigger than four (or equals three), the Position displays unpredictable values when negative.

    Problem example:

    Insert a TEdit and a TUpDown component on a form. Associate the UpDown with the Edit. Set the UpDown's properties as follows (for example):

      Increment: 10
      Max:	   100
      Min:	   -100
      
    Now run the project. The initial position (value) of the Edit is zero. When you click the down arrow, the value changes to -16, while it should be -10. Changing the position between positive and negative leads to incorrect negative values that have only a little to do with the Increment. The number 6 seems to repeat with different Increments.

    Problem cause: This appears to be a Windows common control bug.

    Solution: None known. Untested suggestion: manually change the position to a correct value when the associated Edit changes (OnChange event). However, this is a quite small bug, and as such there is really no need to do anything. Besides, negative values are less common with this control.

  • (ComCtrls) TListItem (or TListView) bug: If the EditCaption method is called when the list view is not focused, the in-place editing does not take place.

    Problem example:

      ListView1.Selected.EditCaption;
      
    The above code starts the editing only if the list view has the focus. If the method was called for example from a button OnClick handler, nothing would happen. This is true even if HideSelection is False.

    Solution: Call SetFocus before calling EditCaption if focus is elsewhere.

    Solution example:

      ListView1.SetFocus; { set focus first }
      ListView1.Selected.EditCaption;
      
    Please note that the current EditCaption implementation simply sends a Windows message to the list view, and thus this is not a real VCL bug. Still, this bug would be very easy to avoid, if the VCL developers would have included the SetFocus call in the implementation.



Runtime Library (RTL)


  • (Windows) Type definition bug: The type "TEnumLogFontEx" needed with the EnumFontFamiliesEx API function is defined as hard to use. All extended fields of the record are defined as arrays of bytes, although they contain normal textual information (arrays of chars).

    [Please note: The actual "porting" from C/C++ could be correct. The Win32 API Help files state that the fields are arrays of "BCHARS". IMO this is a bit confusing anyway.]

    Problem example:

      Function MyFontEnumProc(lpelfe : PEnumLogFontEx;
      						lpntme : PNewTextMetricEx; FontType : Integer;
      						LParam : LongInt) : LongInt; StdCall;
      Begin
        TStrings(LParam).Add(lpelfe^.elfFullName); { this line won't compile }
        Result := 1; { continue font enumeration }
      End;
      

    In this example, the data in "elfFullName" should be added to a string list (for example, a listbox item list). But because the the field is defined as an array of bytes, the compiler cannot convert the array correctly to a (long) string.

    Problem location: The Win32 API header/import file WINDOWS.PAS.

    Solution #1: Define a temporary type to equal an array of chars and do a typecast.

    Solution #1 example:

      Function MyFontEnumProc(lpelfe : PEnumLogFontEx;
      						lpntme : PNewTextMetricEx; FontType : Integer;
      						LParam : LongInt) : LongInt; StdCall;
      
      Type
        TelfFullName = { define temporary type (see WINDOWS.PAS) }
      	Array[0..lf_FullFaceSize-1] of Char;
      
      Begin
        TStrings(LParam).Add(TelfFullName(lpelfe^.elfFullName));
        { do a typecast }
        Result := 1; { continue font enumeration }
      End;
      

    Solution #2: If you want to avoid this problem in the future and you have the RTL (VCL) source code, you could modify WINDOWS.PAS directly.

    Solution #2 example:

      { Redefine TEnumLogFontEx record to allow quick
      conversion of the fields to long strings. }
      
      PEnumLogFontEx = ^TEnumLogFontEx;
      
      TEnumLogFontEx = packed record
        elfLogFont: TLogFont;
        elfFullName: array[0..LF_FULLFACESIZE - 1] of char; { modify }
        elfStyle: array[0..LF_FACESIZE - 1] of char;		  { modify }
        elfScript: array[0..LF_FACESIZE - 1] of char; 	  { modify }
      end;
      



Integrated Development Environment (IDE)


  • Bug with the TForm.ActiveControl property. When you create a form which has a Win 95 style TPageControl, and set the form's ActiveControl to a control which is no longer visible (on another tab sheet), Delphi fails to load the form completely the next time you try to load your project.

    Problem example:

      Create a new project. On the form, drop a TPageControl component. Create two new TTabSheets on it. Create two TEdits, one on each tab sheet. Set the form's ActiveControl property to Edit1 (on either tab sheet). Be sure the tab sheet on which Edit1 is is active.

      Then save your project. Now close the project, and load it again. Everything is OK. Next, simply change the active tab sheet to the other one (on which Edit2 is). Save the project again, close it, and try to reload it. Delphi just informs you: "Error creating form: Cannot focus a disabled or invisible window."

      The problem is that you cannot even view or edit the form anymore to set the original tab sheet back.

    Solution #1: Time to restore the backups... ;-)

    Solution #2: Open the Form file manually as text, and remove the line which sets the form's ActiveControl property.

    Solution #2 How To:

      Choose Open from the File menu. Choose to list only form files (*.dfm), and open the form which is causing problems. (Note that you must close the project causing problems, otherwise Delphi will try to show the form again.) The form will be shown as text. Remove the line which sets the ActiveControl property. Usually this is the 6th line in the file. Save the form, and re-open your project. Everything should be OK now.


  • Problem with the editor after a failed compilation in a special case. If a line has a opening quotation mark, but no closing quotation mark, the editor is shifted one position too far to the left, causing the editor to misbehave.

    Problem example:

      Place the character ' (quotation mark) to its own line in your code. Compile the code, and you get two error messages: "Unterminated string" and "Missing operator or semicolon". Notice how the focus is shifted to the left. If you now try to select an area or scroll the screen, you notice bizarre effects in the left margin.

    Solution:

      Try to avoid the situation. Normal behaviour can be returned by moving the cursor, minimizing the editor window and then restoring it back.



Compiler


  • Bug with the module descriptions. The directive $D (module description) simply does nothing. The longer version of the same directive, $DESCRIPTION is rejected by the compiler: "Invalid compiler directive: 'DESCRIPTION'."

    Problem example:

      Create a plain simple program with a description:

        program Test;
        
        {$D My Program v2.6b}
        
        begin
          { do nothing }
        end.
        

      (Try the longer version of $D - it won't work)

      Now create a similar program with no descrption:

        program Test;
        
        begin
          { do nothing }
        end.
        

      Both compiles result in a 7,168 byte EXE file. The problem is that the first program doesn't include the module description as it should. Even TDUMP won't display it. Doing a file compare results:

      [Win95]E:\delphi32\exefiles>fc test.exe test2.exe /b
      Comparing files test.exe and test2.exe
      FC: no differences encountered
      

      So the compiler simply ignored the directive. Using the other comment marks "(*" and "*)" doesn't help. I also tried the other "long" compiler directives, but they appear to work as expected. Next I checked the compiler module (DCC.DLL and DCC32.EXE) for misspellings, to no avail. Command line compiling doesn't help either.

      Also, linking resources with $R directive or using other units in the code doesn't help.

      Solution: If you're compiling inside the IDE, use the Project Options EXE description field. Otherwise, use the long version of the directive, and quote the description: {$DESCRIPTION 'Copyright...'}. Still, the $D directive won't work.


  • It is possible to "use" the current unit in the implementation section without errors.

    Problem example:

      Create an unit:
      Unit MyUnit;
      
      Inteface
      
      Implementation
      
      Uses MyUnit; { this should not compile }
      
      End.
      

    The above code compiles happily. For example in Borland Pascal 7.0 the above fails with error message "Identifier redeclared.". If the Uses clause is moved to the Interface section, Delphi doesn't compile the unit.

    Solution:

      None needed, as this bug doesn't result in any problems.


  • Problem accessing array properties of strings. If an array property is of type strings (like TStrings.Strings property), it is not possible to access individual characters in the property with the normal array access code. Instead, alternate code must be used.

    Problem example:

      Type TMyArray = Array[1..2] of String;
      Var MyArray : TMyArray;
      ...
      1:	 If (MyArray[1,1] = 'A') Then ...      { this works }
      2:	 If (MyArray[1][1] = 'A') Then ...     { this works too }
      ...
      3:	 If (Memo1.Lines[1,1] = 'B') Then ...  { this doesn't work }
      4:	 If (Memo1.Lines[1][1] = 'B') Then ... { this works OK }
      

    Above, normal arrays of strings can be accessed in two ways (1 and 2). However, array properties can only be accessed with the case 4. Case 3 gives error message "Too many actual parameters".

    Problem cause (supposition):

      The compiler compares the array indices with the parameters of the read access method, and notices that there are too many parameters. However, the compiler forgets to check if the property type is an indexable type (like strings are). The other indexing method (case 4 above) is interpreted differently from case 3 inside the compiler and thus doesn't cause an error.

    Solution:

      Use the method in case 4 above, or first assign the property value to a string variable, and then access the individual characters of the string.


  • Problem with code optimizations and global variables. When code relies on initial global variable zeroing, the optimizer can get confused and produce bad code.

    Problem example:

         program Project1;
      
         {$OPTIMIZATION ON}
      
         Uses Dialogs,SysUtils;
      
         Var I,J : Integer;
      
         begin
      	 J := 123;
      A:	 I := I+J;
      B:	 I := J-I; { compiler warning here: "I" not initialized }
      	 ShowMessage(IntToStr(I));
         end.
      

    Above, this simple program should display zero as its result. On statement A, the program relies on the fact that global variables initially have a zero value (I, J both equal zero) and thus the addition is unnecessary and could be an assignment instead (I := J). On B, the code does a substract, and the result should be zero. However, if optimization is on, a garbage result (usually something like -5636096) will occur.

    Problem cause:

      Because the compiler optimizes the variables to be on CPU registers and not in memory, the register for I is not cleared. If you look at the assembled code, you can see that the EBX register is not cleared and contains a garbage value. The reason for this seems to be the incorrect warning that variable I might not be initialized on statement B (shouldn't the warning occur on statement A?) and thus the optimizer gets confused producing bad code. Stack or memory based variables seem to work OK in this kind of situations.

    Solution:

      Initialize global variables, even though that is unnecessary:

        ...
        begin
          I := 0; { initialize even if not needed }
          J := 123;
          ...
        

      (Alternative: modify statement A to not to use I: "I := J")

      Also rearranging or rewriting portions of code seems to help. However, until this kind of bugs have been fully documented, the only way to be really sure is to turn optimization off. However, this bug seems to be a special case and should not haunt "normal" Delphi programs.


  • Bug with Continue standard procedure in Repeat..Until loops where the Until expression is always True. If Continue is used in such situations, absurd things will occur.

    Problem example:

      procedure ...
      Var I : Integer;
      begin
        I := 1;
        Repeat
      	Inc(I);
      	If (I < 10) Then Continue
      	Else Exit;
      	ShowMessage('Bug (inside Repeat)!');
        Until True;
        ShowMessage('Bug (after Repeat)!');
      end;
      

      Above, the code should Increase I from 1 to 10, and then exit without displaying anything. However, if the above code is executed, usually an access violation will occur when Continue is called.

      Note that if the Until expression would have been False (Until False), Continue would work correctly.

    Problem cause:

      Because the Until expression is always True, the control would always go through the Repeat loop like it never existed. However, in the code above, this is not the case because Continue and Exit procedures are used. The compiler correctly "optimizes away" the unnecessary Until expression, but on the same time leaves the Continue procedure destination to an arbitary address.

      If you look at the code generated by the compiler, you can see that the Continue procedure really jumps to a rather random address, thus causing access violations. Occasionally, the destination address might contain valid executable code, but usually not.

    Problem solutions:

      While a "Repeat..Until True" is a rather obscure programming solution, it is still useful when avoiding Gotos. To avoid any problems with Continue in this *particular situation*, either:

      • Replace the "True" expression with a "False". This makes no difference - except that using False will work correctly.

          ...
          Until False;
          ...
          

      Or alternatively:

      • Use a "While True do..End" construct. A While loop does the same thing, but doesn't cause any problems.

      Of course, changing code to use more "intelligent" constructs is the best solution.

    Note about the bug:

      This appears not to be only a code generation bug. If you create a plain simple project with the above code as a button's OnClick event handler, Delphi will create an access violation the second time you try to compile the project in a row (provided that you modify the file to force it's compilation). On my computer the second compilation always results in access violation at address 832A99. After this, Delphi must be restarted to restore normal behaviour.

      This crash could result from other things of course, but it is still very strongly linked with the Continue bug.