
//////////////////////////////////////////////////
//  DB Access Components
//  Copyright © 1998-2007 Core Lab. All right reserved.
//////////////////////////////////////////////////

{$IFNDEF CLR}

{$I Dac.inc}

unit CRThread;
{$ENDIF}
interface
uses
  Classes, SysUtils, SyncObjs,
{$IFDEF LINUX}
{$ELSE}
  Windows,
{$ENDIF}
{$IFDEF CLR}
  System.Runtime.InteropServices;
{$ELSE}
  CLRClasses;
{$ENDIF}

type
  TCRThread = class;
  TCRThreadWrapper = class;

  TCRThreadTerminateEvent = procedure(Sender: TObject) of object;
  TCRThreadExceptionEvent = procedure(Sender: TObject; E: Exception; var Fail: boolean) of object;
  TCRThreadEvent = procedure(Sender: TObject; Event: TObject) of object;
  TCRThreadClass = class of TCRThread;

  TCRThread = class(TThread)
  protected
    FOwner: TCRThreadWrapper;
    FStartEvent: TEvent;

    procedure InternalExecute; virtual;
    procedure Execute; override;
  public
    procedure PostEvent(Event: TObject);
    procedure SendEvent(Event: TObject);

    constructor Create(Owner: TCRThreadWrapper); virtual;
    destructor Destroy; override;
  end;

  TCRThreadWrapper = class
  protected
    FTimerID: UInt;
    FThread: TCRThread;

    FOnPostEvent: TCRThreadEvent;
    FOnSendEvent: TCRThreadEvent;
    FOnException: TCRThreadExceptionEvent;
    FOnTerminate: TCRThreadTerminateEvent;

    FEvents: TThreadList;
    FException: Exception;
    FTerminated: boolean;
    FFreeOnTerminate: boolean;
    FDoTimerProcessing, FLockDestroy: boolean;
    FDestroyAfterTimer: boolean;
    FSendEvent: TObject;
    FSendEventProcessed: TEvent;

    procedure SetTimer;
    procedure KillTimer;
    procedure DoTimer;

    procedure DoPostEvent(Event: TObject); virtual; // In FThread context
    procedure DoSendEvent(Event: TObject); virtual; // In FThread context

    procedure DoException(E: Exception); virtual; // In FThread context
    procedure DoTerminate; // In FThread context

  public
    constructor Create(ThreadClass: TCRThreadClass; CreateSuspended: Boolean);
    destructor Destroy; override;

    procedure Resume;
    procedure Suspend;
    procedure Terminate;

    property Thread: TCRThread read FThread;
    property FreeOnTerminate: boolean read FFreeOnTerminate write FFreeOnTerminate;
    property Terminated: boolean read FTerminated;

    property OnPostEvent: TCRThreadEvent read FOnPostEvent write FOnPostEvent;
    property OnSendEvent: TCRThreadEvent read FOnSendEvent write FOnSendEvent;
    property OnException: TCRThreadExceptionEvent read FOnException write FOnException;
    property OnTerminate: TCRThreadTerminateEvent read FOnTerminate write FOnTerminate;
  end;

implementation

uses
  MemData{$IFDEF VER6P}, StrUtils{$ENDIF};

var
  ThreadList: TThreadList;

{ TCRThread }

constructor TCRThread.Create(Owner: TCRThreadWrapper);
begin
  inherited Create(True);
  Assert(Owner <> nil);
  FOwner := Owner;
  FStartEvent := TEvent.Create(nil, True, False, '');
end;

destructor TCRThread.Destroy;
begin
  inherited;
  FStartEvent.Free;
end;

procedure TCRThread.InternalExecute;
begin
  // Empty
end;

procedure TCRThread.Execute;
begin
  try
    FStartEvent.SetEvent;
    InternalExecute;
  except
    on E: Exception do
      if not (E is EAbort) then
        FOwner.DoException(E);
  end;
  FOwner.DoTerminate;
end;

procedure TCRThread.PostEvent(Event: TObject);
begin
  FOwner.DoPostEvent(Event);
end;

procedure TCRThread.SendEvent(Event: TObject);
begin
  if not Terminated then
    FOwner.DoSendEvent(Event);
end;

{ TCRThreadWrapper }

procedure TimerCallBack(hWnd: HWND; Message: UInt;
{$IFDEF CLR}
  {$IFDEF VER11P}
    TimerID: UINT_PTR;
  {$ELSE}
    TimerID: UInt;
  {$ENDIF}
{$ELSE}
  TimerID: UInt;
{$ENDIF}
  SysTime: DWORD); {$IFNDEF CLR} stdcall; {$ENDIF}
var
  List: TList;
  p: TCRThreadWrapper;
  ThreadWrapper: TCRThreadWrapper;
  i: integer;
begin
  List := ThreadList.LockList;
  try
    ThreadWrapper := nil;
    for i := 0 to List.Count - 1 do begin
      p := TCRThreadWrapper(List[i]);
      if TCRThreadWrapper(p).FTimerID = TimerID then begin
        ThreadWrapper := TCRThreadWrapper(p);
        Break;
      end;
    end;
  finally
    ThreadList.UnlockList;
  end;

  if ThreadWrapper <> nil then
    ThreadWrapper.DoTimer;
 end;

constructor TCRThreadWrapper.Create(ThreadClass: TCRThreadClass; CreateSuspended: Boolean);
begin
  inherited Create;
  
  FEvents := TThreadList.Create;
  FSendEventProcessed := TEvent.Create(nil, True, False, '');

  SetTimer;
  ThreadList.Add(Self);

  FThread := ThreadClass.Create(Self);
  if not CreateSuspended then
    FThread.Resume;
end;

destructor TCRThreadWrapper.Destroy;
begin
  if FLockDestroy then
    Exit;

  if FDoTimerProcessing then begin
    FDestroyAfterTimer := True;
    Exit;
  end;

  if not FFreeOnTerminate then begin
    FLockDestroy := True;
    Terminate;
  end;
  FThread.Free;
  FEvents.Free;

  if FTimerID <> 0 then
    KillTimer;
  ThreadList.Remove(Self);

  inherited;
  FSendEventProcessed.Free;
end;

procedure TCRThreadWrapper.Resume;
begin
  Thread.Resume;
end;

procedure TCRThreadWrapper.Suspend;
begin
  Thread.Suspend;
end;

procedure TCRThreadWrapper.Terminate;
begin
  WaitForSingleObject(Thread.FStartEvent.Handle, INFINITE);
  Thread.Terminate;
  FSendEventProcessed.SetEvent;
{$IFNDEF CLR}
  WaitForSingleObject(Thread.Handle, INFINITE);
{$ELSE}
  Thread.WaitFor;
{$ENDIF}
  DoTimer;
end;

procedure TCRThreadWrapper.DoPostEvent(Event: TObject); // In FThread context
begin
  Assert(FEvents <> nil);
  FEvents.Add(Event);
end;

procedure TCRThreadWrapper.DoSendEvent(Event: TObject); // In FThread context
begin
  FSendEvent := Event;
  WaitForSingleObject(FSendEventProcessed.Handle, INFINITE);
  FSendEventProcessed.ResetEvent;
end;

procedure TCRThreadWrapper.DoException(E: Exception); // In FThread context
begin
  Assert(FException = nil);
  FException := Exception.Create(E.Message);
end;

procedure TCRThreadWrapper.DoTerminate; // In FThread context
begin
  Assert(not FTerminated);
  FTerminated := True;
end;

procedure TCRThreadWrapper.SetTimer;
const
  USER_TIMER_MINIMUM = $A;
var
  TimerFunc: TFNTimerProc;
begin
  Assert(FTimerID = 0);
  TimerFunc := {$IFNDEF CLR}@{$ENDIF}TimerCallBack;
  FTimerID := Windows.SetTimer(0, 0, USER_TIMER_MINIMUM, TimerFunc);
  Win32Check(FTimerID <> 0);
end;

procedure TCRThreadWrapper.KillTimer;
begin
  Assert(FTimerID <> 0);
  Windows.KillTimer(0, FTimerID);
  FTimerID := 0;
end;

procedure TCRThreadWrapper.DoTimer; // In main thread context
var
  Fail: boolean;
  List: TList;
  FreeThread: boolean;
begin
  if FDoTimerProcessing then
    Exit; // For example - on showing error message
  FDoTimerProcessing := True;

  KillTimer; // To prevent multiple calls to DoTimer if any event handler call ProcessMessage
  try
    if (FSendEvent <> nil) then begin
      if Assigned(FOnSendEvent) then
        FOnSendEvent(Self, FSendEvent);
      FSendEvent := nil;
      FSendEventProcessed.SetEvent;
    end;

    if FEvents <> nil then begin
      List := FEvents.LockList;
      try
        while List.Count > 0 do begin
          try
            if Assigned(FOnPostEvent) then
              FOnPostEvent(Self, TObject(List[0]));
          finally
            List.Delete(0);
          end;
        end;
      finally
        FEvents.UnlockList;
      end;
    end;

    if FException <> nil then begin
      try
        Fail := True;
        if Assigned(FOnException) then
          FOnException(Self, FException, Fail);
        if Fail then begin
          try
            raise FException;
          except
            if Assigned(ApplicationHandleException) then
              ApplicationHandleException(FException)
            else
              ShowException(FException, ExceptAddr);
          end;
        end
        else
          FException.Free;
      finally
        FException := nil;
      end;
    end;

    if FTerminated and Assigned(FOnTerminate) then
      FOnTerminate(Self);
  finally
    FDoTimerProcessing := False;
    FreeThread := (FTerminated and FFreeOnTerminate) or FDestroyAfterTimer;
    if FreeThread then
      Free
    else begin
      FTerminated := False;
      SetTimer;
    end;
  end;
end;

initialization
  ThreadList := TThreadList.Create;

finalization
  ThreadList.Free;

end.
