Search code examples
multithreadingdelphidelphi-xe2

Delphi XE2 GetTextExtentPoint32 fails when called by a thread


This simple example raise exception (invalid parameter) when double clicking on Button1.

You may have to click several times to get the message.

What is wrong with this code ?

type
  TForm2 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  end;

  TTestThread = class(TThread)
  protected
    procedure Execute; override;
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.Button1Click(Sender: TObject);
var MyThread : TTestThread;
begin
  MyThread:=TTestThread.Create(true);
  MyThread.FreeOnTerminate:=True;
  MyThread.Priority:=tpHighest;
  MyThread.Resume;
end;

{ TTestThread }

procedure TTestThread.Execute;
var len : integer;
begin
 len := Form2.Canvas.TextWidth('test');
 if (len=0) then
  Raise Exception.Create(SysErrorMessage(GetLastError));
end;

end.

Solution

  • Windows GUI functions have thread affinity. This constraint is passed on to the VCL. Which means that you can only access VCL routines from the main GUI thread.

    In your code you have broken this rule by calling Form2.Canvas.TextWidth from a thread other than the main GUI thread. When that code is mapped down to Win32 it ends up calling GetTextExtentPoint32 with a device context that is associated with a different thread from the caller. That's against the rules.

    The solution is to abide by the rules. Only call VCL functions from the main GUI thread.