I'm trying to free a component when i click it. So, i've written the simplest code i could imagine to achieve this: a procedure that frees it's sender. But on Delphi 7 (Tried on Delphi XE 10 and it worked with no errors) it sometimes throws an Access Violation or Abstract Error randomly. The easiest way to replicate this is to insert like 30 Buttons and assign an onclick procedure with the code below, then click them.
I've tried the two codes below, both on onclick:
procedure FreeMe(Sender: TObject);
begin
TButton(Sender).Free;
end;
or
procedure FreeMe(Sender: TObject);
begin
(Sender as TButton).Free;
end;
You need to delay the freeing until after the button's OnClick
event handier has fully exited. It is important that the freeing happens when the object being freed is idle and not in the middle of processing anything.
One way to do that is to use PostMessage()
, eg:
var
MyReleaseWnd: HWND;
procedure TMyMainForm.FormCreate(Sender: TObject);
begin
MyReleaseWnd := AllocateHWnd(MyReleaseWndProc);
end;
procedure TMyMainForm.FormDestroy(Sender: TObject);
begin
DeallocateHWnd(MyReleaseWnd);
end;
procedure TMyMainForm.MyReleaseWndProc(var Message: TMessage);
begin
if Message.Msg = CM_RELEASE then
TObject(Msg.LParam).Free
else
Message.Result := DefWindowProc(MyReleaseWnd, Message.Msg, Message.WParam, Message.LParam);
end;
procedure DelayFreeMe(Sender: TObject);
begin
PostMessage(MyReleaseWnd, CM_RELEASE, 0, LPARAM(Sender));
end;
Alternatively, in 10.2 Tokyo and later, you can use TThread.ForceQueue()
instead:
procedure DelayFreeMe(Sender: TObject);
begin
TThread.ForceQueue(nil, Sender.Free);
end;
Either way, you can then do this:
procedure TSomeForm.ButtonClick(Sender: TObject);
begin
DelayFreeMe(Sender);
end;