Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi and threads: "System Error. Code: 1400. Invalid window handle"

Being kinda new to threading I'm running into an issue:

I've built a small wrapper for Synapse THTTPSend object to handle Async calls through means of a thread. All seems to go well until I exit the application and get this error (using madExcept exceptions handler) "System Error. Code: 1400. Invalid window handle."

main thread ($2d00):
0047f931 +091 x.exe  System.SysUtils          RaiseLastOSError
0047f88e +00e x.exe  System.SysUtils          RaiseLastOSError
006198c4 +064 x.exe  Vcl.Controls             TWinControl.DestroyWindowHandle
0061674c +0dc x.exe  Vcl.Controls             TWinControl.Destroy
0067487b +05b x.exe  Vcl.ComCtrls             TTabSheet.Destroy
00616781 +111 x.exe  Vcl.Controls             TWinControl.Destroy
00673218 +0b8 x.exe  Vcl.ComCtrls             TCustomTabControl.Destroy
0067529c +06c x.exe  Vcl.ComCtrls             TPageControl.Destroy
00616781 +111 x.exe  Vcl.Controls             TWinControl.Destroy
0073d95e +06e x.exe  Vcl.Forms                TScrollingWinControl.Destroy
0073f5d2 +1e2 x.exe  Vcl.Forms                TCustomForm.Destroy
0040b2d5 +015 x.exe  System                   TObject.Free
005a034e +08e x.exe  System.Classes           TComponent.DestroyComponents
0073be06 +046 x.exe  Vcl.Forms                DoneApplication
00472520 +030 x.exe  System.SysUtils          DoExitProc
0040e0d9 +079 x.exe  System                   @Halt0

I've tracked this down to accessing a listview, it goes like this:

  • GUI calls a proc in my wrapper and assigns a callback method
  • Wrapper creates a thread and sets a callback
  • Thread does its job (http post) then calls the wrapper's callback
  • Wrapper's callback triggers another callback in the GUI which then updates some items in a listview

If I skip that listview part the error never happens, so I think something may be wrong in my thread code that messes up with the vcl/gui, probably cause it's still running while the VCL is being accessed? If I check the listview there is something very odd with it after the thread ends, sometimes the listview isn't even visible, or the added items aren't clickable.

Listview part

procedure Tx.AddLog(url,DelURL: string);
begin
  if Settings.OptEnableLogging.Checked then begin
    With UploadsForm.ListView1.Items.Add do begin
      Caption := DateTimeToStr(Now);
      SubItems.Add(OriginalFilename);
      SubItems.Add(url);
      SubItems.Add('');
      SubItems.Add(DelURL);
    end;
    SaveLoggingLog;
  end;

    With UploadsForm.ListView2.Items.Add do begin
      Caption := DateTimeToStr(Now);
      SubItems.Add(OriginalFilename);
      SubItems.Add(url);
      SubItems.Add('');
      SubItems.Add(DelURL);
    end;
end;

The thread object

type
  TMySynHTTPAsync = class(TThread)
  protected
    procedure Execute; override;
  private
    sObj: TSynHTTP;
  public
    Constructor Create(SynObj: TSynHTTP);
    Destructor Destroy; override;
end;

implementation

Constructor TMySynHTTPAsync.Create(SynObj: TSynHTTP);
begin
  inherited Create(False);
  Self.FreeOnTerminate := True;
  sObj := SynObj;
end;

Destructor TMySynHTTPAsync.Destroy;
begin
  //
  inherited Destroy;
end;

Procedure TMySynHTTPAsync.Execute;
begin
  With sObj do begin
    try
      case tCallType of
        thPostString: ThreadResult := sObj.Post(tURL, tPostVars);
      end;
    except
      //
    end;
    if Assigned(sObj.xOnAsyncRequestDone) then sObj.xOnAsyncRequestDone;
    FThread := nil;
  end;
end;

creating the thread

FThread: TThread;

procedure TSynHTTP.DoAsync;
begin
  ThreadResult := False;
  FThread := TMySynHTTPAsync.Create(Self);
  FThread.Resume;
end;

I'm guessing this is the culprit, as it goes through all the GUI processing before the thread finishes.

if Assigned(sObj.xOnAsyncRequestDone) then sObj.xOnAsyncRequestDone;

How could I solve this?

like image 361
hikari Avatar asked Jun 20 '26 11:06

hikari


2 Answers

You have posted a lot of code but not the key, relevant part. Specifically the implementation of your xOnAsyncRequestDone event handler/method (unless it literally only calls that log method that you posted).

This method is being executed in the context of the TMySynHTTPAsync thread and based on the behaviour you are describing - particularly the fact that Synchronize resolves your problem - it is highly likely that some activity in that event handler is creating a window handle.

That window handle is then owned by the HTTP Async thread, not the main application thread (sometimes referred to as the "VCL thread") that is otherwise running your application. When your application closes, the VCL thread performs some final housekeeping, destroying objects and windows etc. if one of those windows was created by some other thread this will cause problems.

Window handles are the strict property of the thread in which they were created. You cannot create a window handle in one thread and then destroy it in another.

NOTE: This is a fundamental of Windows, not Delphi.

Worth noting here is that window handles in VCL can often be created indirectly. You won't necessarily see an explicit creation of a control that marks the creation of the underlying window handle. It is quite common for window handles only to be actually created when required. Similarly changing the properties of a control can trigger the VCL to attempt to recreate the window for that control, destroying the current one in the process.

It should be fairly apparent that these mechanisms are highly vulnerable to problems that can arise when VCL methods are called by threads other than the VCL thread. This is why you will often here it said that "the VCL is not thread-safe".

The safest way to operate is to only manipulate VCL objects from code running in the VCL thread itself.

Synchronize to the Rescue

This is in fact precisely why Synchronize exists.

The mechanism that you are invoking by using Synchronize actually works to ensure that the method you are Synchronizing is execute on the VCL thread. If this is in fact creating a window handle then when the VCL thread later comes to destroy that window handle it is quite free to do so since it did in fact create it.

Hence your problem is solved.

Other Options

The Synchronize mechanism is quite complex however, dealing (these days) with cross platform concerns among other things, and as a result may be overkill in this case.

If your code is specific to Windows, a possible alternate solution to this problem may be to exploit the fact that windows allows threads to send (or post) messages to windows in other threads. When those messages are received by those windows, they are then processed by that window's own thread just as all other messages to those windows are. i.e. you cannot end up interrupting a "click" message received by that window by suddenly jumping across to run the notification from the thread. That notification message simply has to wait it's turn while the window finishes processing that click message. For example.

You can think of this as a 'Synchronize' system "built-in" in to the OS.

So you could, for example, pass a window handle to a form (or control or anything with a window handle) to your HTTP async thread during initialisation, identifying a VCL window that wishes to receive the "request complete" or other notifications from the thread. The thread can then send notifications to that window handle using PostMessage or SendMessage which you could handle either by overriding the WindowProc on the form or using a declared message handler.

If the thread uses SendMessage() to send the notification, then it is automatically suspended and forced to wait until the message is received and processed by the window (in the VCL thread).

If the thread uses PostMessage() then the message is sent asynchronously and the thread can continue with other work without having to wait. The VCL thread will eventually pick up the message and process it.

NOT a Recommendation

This is not to say that I would recommend this alternative in this case. Although it does seem that it might be appropriate given that it does appear to be a simple "work is complete" notification in this case, without a more comprehensive understanding of your specific needs it is impossible to say which is most appropriate.

I mention it only to highlight the fact that alternatives do exist and that the key to safe, reliable threading is to understand the principles and the mechanisms involved.

like image 55
Deltics Avatar answered Jun 23 '26 11:06

Deltics


The golden rule with threading is to not touch the GUI from another thread. Depending on the situation this can be solved with Synchronize(), posting messages async (PostMessage()) or synchronized (SendMessage()). Another asynchronic option is using the TThread.Queue() call.

Last but not least, if you want to notify the GUI that the thread is done, assign an OnTerminate event handler to the thread. This event is executed in the main thread when the thread finishes executing. This is an example how it could be implemented:

type
  TMySynHTTPAsync = class(TThread)
  protected
    procedure Execute; override;
  private
    sObj: TSynHTTP;
    procedure MyTerminateHandler(Sender: TObject);
  public
    Constructor Create(SynObj: TSynHTTP);
    Destructor Destroy; override;
end;

procedure TMySynHTTPAsync.MyTerminateHandler(Sender: TObject);
begin // Executed in the main thread
  if Assigned(sObj) and Assigned(sObj.xOnRequestDone) then sObj.xOnRequestDone;
end;

procedure TMySynHTTPAsync.Execute;
begin
  Self.OnTerminate := MyTerminateHandler;  // Assign the OnTerminate event handler
  ...
end;
like image 20
LU RD Avatar answered Jun 23 '26 10:06

LU RD



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!