Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / lazarus / 2011-11

TSynEdit bug in destructor?

6 messages · 4 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Bart

Hi,

I stumbled upon this bug (?).

Consider the following simple application.

unit main;
{$mode objfpc}{$H+}
interface

uses
Classes, SysUtils, FileUtil, SynEdit, Forms, Controls, Graphics, Dialogs,

  StdCtrls, LCLType, LCLProc;
type
  { TForm1 }
  TForm1 = class(TForm)
    CreateBtn: TButton;
    FreeBtn: TButton;
    procedure CreateBtnClick(Sender: TObject);
    procedure FreeBtnClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  private
    { private declarations }
    Ed: TSynEdit;
  public
    { public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  Ed := nil;
  KeyPreview := True;
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift:
TShiftState);
begin
  if (Key = VK_F4) and (Shift = [ssCtrl]) and Assigned(Ed) then
  begin
    Debugln('Ctrl+F4');
    Key := 0;
    Ed.Free;
    Ed := nil;
  end;
end;
procedure TForm1.CreateBtnClick(Sender: TObject);
begin
  if Assigned(Ed) then Exit;
  Ed := TSynEdit.Create(Self);
  Ed.Align := alTop;
  Ed.Parent := Self;
  Ed.SetFocus;
end;
procedure TForm1.FreeBtnClick(Sender: TObject);
begin
  if not Assigned(Ed) then Exit;
  debugln('FreeBtnClick');
  Ed.Free;
  Ed := nil;
end;
end.

It creates and frees a that pressing Ctrl-F4 will also free the TSynEdit.

If you run the program and press the Free button, the TSynEdit gets freed OK. If however you press Ctrl+F4 and at that moment the TSynEdit has focus, then you get a warning when freeing the TSynEdit:

WARNING: TLCLComponent.Destroy with LCLRefCount>0. Hint: Maybe the component is processing an event?

This happened to me in an app where I create TSynEdits derived control on a TTabSheet, both created at runtime on a custom TPageControl derived control.
It took me all afternoon to find out I hadn't screwed up the constructor/destructors of my own controls, and it only happened via the shortcut-key when the TSynEdit had focus.

Feauture or bug?

Bart

Reply by email

Martin
to Bart
Bart wrote 12 lines
It creates and frees a TSynEdit at runtime. Note that pressing Ctrl-F4 will also free the TSynEdit. If you run the program and press the Free button, the TSynEdit gets freed OK. If however you press Ctrl+F4 and at that moment the TSynEdit has focus, then you get a warning when freeing the TSynEdit: WARNING: TLCLComponent.Destroy with LCLRefCount>0. Hint: Maybe the component is processing an event?

You need to do a callstack, of where you handle ctrl-f4.

But I think the bug is in your code. It is not necessarily synedit specific.

If SynEdit was focussed then SynEdit message handler may be called, the form key handler may run inside (inherited) that handler.

If you destroy synedit while one of it's methods is executed, then that is what you get.

can somehow give synedit to the application
for destruction OnIdle

Reply by email

Mattias Gaertner
to Bart: I stumbled upon this bug (?). Consider the following simple application. unit main;…

?

Bart hat am 8. November 2011 um 16:39 geschrieben:

Show quoted text 8 lines
[...] If you run the program and press the Free button, the TSynEdit gets freed OK. If however you press Ctrl+F4 and at that moment the TSynEdit has focus, then you get a warning when freeing the TSynEdit: WARNING: TLCLComponent.Destroy with LCLRefCount>0. Hint: Maybe the component is processing an event??

You should not free components while they process events.? Use instead:
Application.ReleaseComponent(Ed);?
This will free the component after the event.?
?

Show quoted text 9 lines
This happened to me in an app where I create TSynEdits derived control on a TTabSheet, both created at runtime on a custom TPageControl derived control. It took me all afternoon to find out I hadn't screwed up the constructor/destructors of my own controls, and it only happened via the shortcut-key when the TSynEdit had focus. Feauture or bug??

By design. You should not free something that is in use. ?
?
Mattias
?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.lazarus.freepascal.org/pipermail/lazarus/attachments/20111108/f213aa32/attachment.html>

Reply by email

Bart
to Mattias Gaertner
Mattias Gaertner wrote 5 lines
You should not free components while they process events. Use instead: Application.ReleaseComponent(Ed); This will free the component after the event.

I tried the following:

The PageControl which has/owns the TabSheet (which owns the SynEdit) has a function
AddPage which creates the TabSheet and SynEdit on it, basically like this:

function TEditorPageControl.AddPage: TEditor;
var
  TS: TTabSheet;
  E: TEditor;
  PgIdx: Integer;
begin
  Result := nil;
  TS := TTabSheet.Create(Self);
  TS.PageControl := Self;
  PgIdx := TS.PageIndex;
  E := TEditor.Create(TS);  //<- TEditor is TSynEdit derived
  ActivePage := Pages[PgIdx];
end;

The function ClosePage which removes the TabSheet and SynEdit on it basically does:

function TEditorPageControl.ClosePage(Index: Integer): Boolean;
var
  Cancel: Boolean;
  Pg: TTabSheet;
begin
  Result := False;
  if (Index > PageCount - 1) then Exit;
  Pg := Pages[Index];  //<-- Pg owns the SynEdit
  Cancel := False;
  if Assigned(FOnBeforeCloseEditor) then FOnBeforeCloseEditor(Pg, Cancel);
  if Not Cancel then
  begin
    Pg.PageControl := nil;
    Pg.Free;
    Result := True;
    if PageCount = 0 then InternalEditorStatusChange(nil, scAll);
  end;
end;

I then changed this into:

function TEditorPageControl.ClosePage(Index: Integer): Boolean;
var
  Cancel: Boolean;
  Pg: TTabSheet;
  Ed: TEditor;
begin
  Result := False;
  if (Index > PageCount - 1) then Exit;
  Pg := Pages[Index];  //<-- Pg owns the SynEdit
  Cancel := False;
  if Assigned(FOnBeforeCloseEditor) then FOnBeforeCloseEditor(Pg, Cancel);
  if Not Cancel then
  begin
    //newly added code
    Ed := EditorAtPage(Pg);  //<- returns the SynEdit in question
    Application.ReleaseComponent(Ed);
    Application.ProcessMessages;
    //end newly added code
    Pg.PageControl := nil;
    Pg.Free;
    Result := True;
    if PageCount = 0 then InternalEditorStatusChange(nil, scAll);
  end;
  Debugln('TEditorPageControl.ClosePage End.');
end;

This did not work, so I changed it into

function TEditorPageControl.ClosePage(Index: Integer): Boolean;
var
  Cancel: Boolean;
  Pg: TTabSheet;
  Ed: TEditor;
begin
  Result := False;
  if (Index > PageCount - 1) then Exit;
  Pg := Pages[Index];  //<-- Pg owns the SynEdit
  Cancel := False;
  if Assigned(FOnBeforeCloseEditor) then FOnBeforeCloseEditor(Pg, Cancel);
  if Not Cancel then
  begin
    Ed := EditorAtPage(Pg);  //<- returns the SynEdit in question
    Application.ReleaseComponent(Ed);
    Application.ProcessMessages;
    Pg.PageControl := nil;
    Application.ReleaseComponent(Pg);
    Application.ProcessMessages;
    Result := True;
    if PageCount = 0 then InternalEditorStatusChange(nil, scAll);
  end;
end;

I had to use Application.ReleaseComponent on both the SynEdit and the TabSheet in order to get it to work.

Does this look OK?

> Feauture or bug? By design. You should not free something that is in use.

That depends on the meaning of "in use" ;-)
I did not know it (the SynEdit) was actually doing code worked perfectly in Delphi 3. It also works fine if the SynEdit is replaced by a Memo.

Thanks for the help.

Bart

Reply by email

Mattias Gaertner
to Bart

?

Bart hat am 8. November 2011 um 17:49 geschrieben:

Show quoted text 19 lines
[...] function TEditorPageControl.ClosePage(Index: Integer): Boolean; var ? ?Cancel: Boolean; ? ?Pg: TTabSheet; ? ?Ed: TEditor; begin ? ?Result := False; ? ?if (Index > PageCount - 1) then Exit; ? ?Pg := Pages[Index];? //<-- Pg owns the SynEdit ? ?Cancel := False; ? ?if Assigned(FOnBeforeCloseEditor) then FOnBeforeCloseEditor(Pg, Cancel); ? ?if Not Cancel then ? ?begin ? ? ?//newly added code ? ? ?Ed := EditorAtPage(Pg);? //<- returns the SynEdit in question ? ? ?Application.ReleaseComponent(Ed); ? ? ?Application.ProcessMessages;?

ProcessMessages only processes the next messages in the event queue. You are still processing the current message. Processing means calling a function and waiting for it to return.?
?
?

Show quoted text 50 lines
? ? ?//end newly added code ? ? ?Pg.PageControl := nil; ? ? ?Pg.Free; ? ? ?Result := True; ? ? ?if PageCount = 0 then InternalEditorStatusChange(nil, scAll); ? ?end; ? ?Debugln('TEditorPageControl.ClosePage End.'); end; This did not work, so I changed it into function TEditorPageControl.ClosePage(Index: Integer): Boolean; var ? ?Cancel: Boolean; ? ?Pg: TTabSheet; ? ?Ed: TEditor; begin ? ?Result := False; ? ?if (Index > PageCount - 1) then Exit; ? ?Pg := Pages[Index];? //<-- Pg owns the SynEdit ? ?Cancel := False; ? ?if Assigned(FOnBeforeCloseEditor) then FOnBeforeCloseEditor(Pg, Cancel); ? ?if Not Cancel then ? ?begin ? ? ?Ed := EditorAtPage(Pg);? //<- returns the SynEdit in question ? ? ?Application.ReleaseComponent(Ed); ? ? ?Application.ProcessMessages; ? ? ?Pg.PageControl := nil; ? ? ?Application.ReleaseComponent(Pg); ? ? ?Application.ProcessMessages; ? ? ?Result := True; ? ? ?if PageCount = 0 then InternalEditorStatusChange(nil, scAll); ? ?end; end; I had to use Application.ReleaseComponent on both the SynEdit and the TabSheet in order to get it to work. Does this look OK? >> Feauture or bug? > By design. You should not free something that is in use. That depends on the meaning of "in use" ;-) I did not know it (the SynEdit) was actually doing something at all.?

See the call stack in View / Debug Windows / Call Stack.? ?
?

This code worked perfectly in Delphi 3.?

The winapi works differently than other widgetsets. Other widgetsets work more object oriented. The LCL was designed to work with various widgetsets.? ?
?

It also works fine if the SynEdit is replaced by a Memo.?

?
Mattias
?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.lazarus.freepascal.org/pipermail/lazarus/attachments/20111108/c1a46af4/attachment-0001.html>

Reply by email

Lukasz Sokol
to Mattias Gaertner: ? Bart hat am 8. November 2011 um 16:39 geschrieben: You should not free components…
Mattias Gaertner wrote

[...]

Show quoted text 9 lines
You should not free components while they process events. Use instead: Application.ReleaseComponent(Ed); This will free the component after the event.

[...]

Thanks! This did the trick for me too (background : my application was invoking Form1.Close when an '> exit[VK_RETURN]' command was recognized typed in a TMemo which had the /foreseeable/ effect of the app crashing on exit... This (ReleaseComponent(Memo1) did the trick!

Lukasz

Show quoted text 5 lines
By design. You should not free something that is in use. Mattias

Reply by email