I found an article that explains why my code does not work on Windows:

http://www.clevercomponents.com/articles/article019/delphi6sync.asp

Now it works.

unit threadx;

interface

uses
  Windows,
  Classes;

type
  TThreadSynchronizer = class
  private
    FMethod: TThreadMethod;
    FSynchronizeException: TObject;
    FSyncBaseThreadID: LongWord;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Synchronize(Method: TThreadMethod);
    property SyncBaseThreadID: LongWord read FSyncBaseThreadID;
  end;

  TThreadEx = class(TThread)
  private
    FSynchronizer: TThreadSynchronizer;
    procedure HandleTerminate;
  protected
    procedure DoTerminate; override;
  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
    procedure Wait;
    property Synchronizer: TThreadSynchronizer read FSynchronizer;
  end;
  
implementation

const
  CM_EXECPROC = $8FFD;
  CM_DESTROYWINDOW = $8FFC;

type
  TSyncInfo = class
    FSyncBaseThreadID: LongWord;
    FThreadWindow: HWND;
    FThreadCount: Integer;
  end;

  TSynchronizerManager = class
  private
    FThreadLock: TRTLCriticalSection;
    FList: TList;
    procedure FreeSyncInfo(AInfo: TSyncInfo);
    procedure DoDestroyWindow(AInfo: TSyncInfo);
    function InfoBySync(ASyncBaseThreadID: LongWord): TSyncInfo;
    function FindSyncInfo(ASyncBaseThreadID: LongWord): TSyncInfo;
  public
    class function Instance: TSynchronizerManager;
    constructor Create();
    destructor Destroy; override;
    procedure AddThread(ASynchronizer: TThreadSynchronizer);
    procedure RemoveThread(ASynchronizer: TThreadSynchronizer);
    procedure Synchronize(ASynchronizer: TThreadSynchronizer);
  end;

var
  SynchronizerManager: TSynchronizerManager;

function ThreadWndProc(Window: HWND; Message, wParam, lParam: Longint): 
Longint; stdcall;
begin
  case Message of
    CM_EXECPROC:
      with TThreadSynchronizer(lParam) do
      begin
        Result := 0;
        try
          FSynchronizeException := nil;
          FMethod();
        except
          FSynchronizeException := AcquireExceptionObject();
        end;
      end;
    CM_DESTROYWINDOW:
      begin
        TSynchronizerManager.Instance().DoDestroyWindow(TSyncInfo(lParam));
        Result := 0;
      end;
  else
    Result := DefWindowProc(Window, Message, wParam, lParam);
  end;
end;

var
  ThreadWindowClass: TWndClass = (
    style: 0;
    lpfnWndProc: @ThreadWndProc;
    cbClsExtra: 0;
    cbWndExtra: 0;
    hInstance: 0;
    hIcon: 0;
    hCursor: 0;
    hbrBackground: 0;
    lpszMenuName: nil;
    lpszClassName: 'TThreadSynchronizerWindow');

{ TSynchronizerManager }

constructor TSynchronizerManager.Create;
begin
  inherited Create();
  InitializeCriticalSection(FThreadLock);
  FList := TList.Create();
end;

destructor TSynchronizerManager.Destroy;
var
  i: Integer;
begin
  for i := FList.Count - 1 downto 0 do
  begin
    FreeSyncInfo(TSyncInfo(FList[i]));
  end;
  FList.Free();
  DeleteCriticalSection(FThreadLock);
  inherited Destroy();
end;

class function TSynchronizerManager.Instance: TSynchronizerManager;
begin
  if (SynchronizerManager = nil) then
  begin
    SynchronizerManager := TSynchronizerManager.Create();
  end;
  Result := SynchronizerManager;
end;
    
procedure TSynchronizerManager.AddThread(ASynchronizer: TThreadSynchronizer);

  function AllocateWindow: HWND;
  var
    TempClass: TWndClass;
    ClassRegistered: Boolean;
  begin
    ThreadWindowClass.hInstance := HInstance;
    ClassRegistered := GetClassInfo(HInstance, ThreadWindowClass.lpszClassName,
      TempClass);
    if not ClassRegistered or (@TempClass.lpfnWndProc <> @ThreadWndProc) then
    begin
      if ClassRegistered then
        Windows.UnregisterClass(ThreadWindowClass.lpszClassName, HInstance);
      Windows.RegisterClass(ThreadWindowClass);
    end;

    Result := CreateWindow(ThreadWindowClass.lpszClassName, '', 0,
      0, 0, 0, 0, 0, 0, HInstance, nil);
  end;

var
  info: TSyncInfo;
begin
  EnterCriticalSection(FThreadLock);
  try
    info := FindSyncInfo(ASynchronizer.SyncBaseThreadID);
    if (info = nil) then
    begin
      info := TSyncInfo.Create();
      info.FSyncBaseThreadID := ASynchronizer.SyncBaseThreadID;
      FList.Add(info);
    end;
    if (info.FThreadCount = 0) then
    begin
      info.FThreadWindow := AllocateWindow();
    end;
    Inc(info.FThreadCount);
  finally
    LeaveCriticalSection(FThreadLock);
  end;
end;

procedure TSynchronizerManager.RemoveThread(ASynchronizer: TThreadSynchronizer);
var
  info: TSyncInfo;
begin
  EnterCriticalSection(FThreadLock);
  try
    info := InfoBySync(ASynchronizer.SyncBaseThreadID);
    PostMessage(info.FThreadWindow, CM_DESTROYWINDOW, 0, Longint(info));
  finally
    LeaveCriticalSection(FThreadLock);
  end;
end;

procedure TSynchronizerManager.DoDestroyWindow(AInfo: TSyncInfo);
begin
  EnterCriticalSection(FThreadLock);
  try
    Dec(AInfo.FThreadCount);
    if AInfo.FThreadCount = 0 then
    begin
      FreeSyncInfo(AInfo);
    end;
  finally
    LeaveCriticalSection(FThreadLock);
  end;
end;

procedure TSynchronizerManager.FreeSyncInfo(AInfo: TSyncInfo);
begin
  if AInfo.FThreadWindow <> 0 then
  begin
    DestroyWindow(AInfo.FThreadWindow);
    AInfo.Free();
    FList.Remove(AInfo);
  end;
end;

procedure TSynchronizerManager.Synchronize(ASynchronizer: TThreadSynchronizer);
begin
  SendMessage(InfoBySync(ASynchronizer.SyncBaseThreadID).FThreadWindow, 
CM_EXECPROC, 0, Longint(ASynchronizer));
end;

function TSynchronizerManager.FindSyncInfo(
  ASyncBaseThreadID: LongWord): TSyncInfo;
var
  i: Integer;
begin
  for i := 0 to FList.Count - 1 do
  begin                       
    Result := TSyncInfo(FList[i]);
    if (Result.FSyncBaseThreadID = ASyncBaseThreadID) then Exit;
  end;
  Result := nil;
end;

function TSynchronizerManager.InfoBySync(
  ASyncBaseThreadID: LongWord): TSyncInfo;
begin
  Result := FindSyncInfo(ASyncBaseThreadID);
  Assert(Result <> nil, 'Cannot find SyncInfo for the specified thread 
synchronizer');
end;

{ TThreadSynchronizer }

constructor TThreadSynchronizer.Create;
begin
  inherited Create();
  FSyncBaseThreadID := GetCurrentThreadId();
  TSynchronizerManager.Instance().AddThread(Self);
end;

destructor TThreadSynchronizer.Destroy;
begin
  TSynchronizerManager.Instance().RemoveThread(Self);
  inherited Destroy();
end;

procedure TThreadSynchronizer.Synchronize(Method: TThreadMethod);
begin
  FSynchronizeException := nil;
  FMethod := Method;
  TSynchronizerManager.Instance().Synchronize(Self);
  if Assigned(FSynchronizeException) then raise FSynchronizeException;
end;

{ TThreadEx }

constructor TThreadEx.Create(CreateSuspended: Boolean);
begin
  inherited Create(CreateSuspended);
  FSynchronizer := TThreadSynchronizer.Create();
end;

destructor TThreadEx.Destroy;
begin
  FSynchronizer.Free();
  inherited Destroy();
end;

procedure TThreadEx.DoTerminate;
begin
  if Assigned(OnTerminate) then Synchronizer.Synchronize(HandleTerminate);
end;

procedure TThreadEx.HandleTerminate;
begin
  if Assigned(OnTerminate) then OnTerminate(Self);
end;

procedure TThreadEx.Wait;
var
  Msg: TMsg;
  H: THandle;
begin
  DuplicateHandle(GetCurrentProcess(), Handle, GetCurrentProcess(), @H, 0, 
False, DUPLICATE_SAME_ACCESS);
  try
    if GetCurrentThreadID = Synchronizer.SyncBaseThreadID then
    begin
      while MsgWaitForMultipleObjects(1, H, False, INFINITE, QS_SENDMESSAGE) = 
WAIT_OBJECT_0 + 1 do
      begin
        while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
        begin
          DispatchMessage(Msg);
        end;
      end;
    end else
    begin
      WaitForSingleObject(H, INFINITE);
    end;
  finally
    CloseHandle(H);
  end;
end;

initialization
  SynchronizerManager := nil;

finalization
  SynchronizerManager.Free();
  SynchronizerManager := nil;

end.

Reply via email to