unit MPThreadManager;

// Version 1.7.0
//
// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
//
// Alternatively, you may redistribute this library, use and/or modify it under the terms of the
// GNU Lesser General Public License as published by the Free Software Foundation;
// either version 2.1 of the License, or (at your option) any later version.
// You may obtain a copy of the LGPL at http://www.gnu.org/copyleft/.
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
// specific language governing rights and limitations under the License.
//
// The initial developer of this code is Jim Kueneman <jimdk@mindspring.com>
//
// Special thanks to the following in no particular order for their help/support/code
//    Danijel Malik, Robert Lee, Werner Lehmann, Alexey Torgashin, Milan Vandrovec
//----------------------------------------------------------------------------

interface

{$I Options.inc}
{$I ..\Include\Addins.inc}

{$B-}

// See procedure TEasyThread.ExecuteStub; to understand what this does
{.$DEFINE DEBUG_THREAD}
{.$DEFINE DRAG_OUT_THREAD_SHUTDOWN}


uses
  Windows, Messages, SysUtils, Classes, Controls, ShlObj, ShellAPI, ActiveX;

const
  COMMONTHREADFILTERWNDCLASS = 'clsEasyThreadFilter';
  COMMONTHREADSAFETYVALVE = 200;  // Number of PostMessage trys before giving up
  WM_COMMONTHREADCALLBACK = WM_APP + 356;  // Handle this message to recieve the data from the thread
  WM_COMMONTHREADNOTIFIER = WM_APP + 355;  // Used internally to pass the data from the thread to the dispatch window
  TID_START = 0;   // Use this Thread ID to start custom ID's for the ThreadRequest.RequestID field
                   // This way the same thread can be used for various tasks and call a common
                   // message handler

  FORCE_KILL_THREAD_COUNT  = 10;    // 100 loops of THREAD_SHUTDOWN_WAIT_DELAY then TerminateThread()
  THREAD_SHUTDOWN_WAIT_DELAY = 200;  // miliseconds

type
  TCommonThreadRequest = class;
  TCommonThreadManager = class;
  TCommonThread = class;
  TPIDLCallbackThreadRequest = class;

  TCommonThreadPriority = 0..100;
  TNamespaceCallbackProc = procedure(Request: TPIDLCallbackThreadRequest) of object;

  // TMessage definition for how the data is passed to the target window via PostMessage
  TWMThreadRequest = packed record
    Msg: Cardinal;
    RequestID: Longint;
    Request: TCommonThreadRequest;
    Result: Longint;
  end;

  TCommonThreadDirection = (
    etdFirstInFirstOut,     // Requests are serviced from the first to the last
    etdFirstInLastOut       // Requests are serviced from the last to to first
  );

  // **************************************************************************
  // Record that is used to set the name of the Thread.
  // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsdebug/html/vxtsksettingthreadname.asp
  // http://bdn.borland.com/article/0,1410,29800,00.html
  // **************************************************************************
  TThreadNameInfo = record
    FType: LongWord;     // must be 0x1000
    FName: PChar;        // pointer to name (in user address space)
    FThreadID: LongWord; // thread ID (-1 indicates caller thread)
    FFlags: LongWord;    // reserved for future use, must be zero
  end;

  // **************************************************************************
  // Thread Request
  //   Any requests for a TCommonThread to extract data are made through a Thread
  // Request object.  The main thread typically creates the object (or decendent)
  // and fills in the basic info, the item index associated with the request, the
  // Window to notify when finished.
  // The TCommonThreadRequest object pointer is in the lParam of the message.
  // Define the message handle using the TWMRequestThread type for the parameter.
  // i.e.
  //  type
  //    TSomeTWinControl = class(TWinControl)
  //      procedure WMEasyThreadCallback(var Msg: TWMRequestThread); message WM_COMMONTHREADCALLBACK;
  //    end;
  // **************************************************************************
  TCommonThreadRequest = class(TPersistent)
  private
    FID: Cardinal;                   // The ID that identifies the request type
    FPriority: TCommonThreadPriority;  // The Thread will sort the request list by Priority, 0 being highest 100 being the lowest
    FRefCount: Integer;
    FTag: Integer;                   // User defineable field
    FThread: TCommonThread;          // Reference to the thread handling the request
    FWindow: TWinControl;            // The control to send the Message to, set to nil to have the thread free the object without dispatching it to the main thread
    FItem: Pointer;                  // Identifier of the Item the threaded data is being extracted for
    FRemainingRequests: Integer;     // Number of remaining requests in the thread prior to being dispatched to the window
    FCallbackWndMessage: Cardinal;   // This is the window message that is sent to the client window, WM_COMMONTHREADCALLBACK by default
  protected
    property RefCount: Integer read FRefCount write FRefCount;
  public
    constructor Create; virtual;
    destructor Destroy; override;
    function HandleRequest: Boolean; virtual; abstract;
    procedure Assign(Source: TPersistent); override;

    procedure Prioritize(RequestList: TList); virtual;
    procedure Release;
    property CallbackWndMessage: Cardinal read FCallbackWndMessage write FCallbackWndMessage;
    property Item: Pointer read FItem write FItem;
    property ID: Cardinal read FID write FID;
    property Priority: TCommonThreadPriority read FPriority write FPriority default 50;
    property RemainingRequests: Integer read FRemainingRequests write FRemainingRequests;
    property Tag: Integer read FTag write FTag;
    property Thread: TCommonThread read FThread;
    property Window: TWinControl read FWindow write FWindow;
  end;
  TCommonThreadRequestClass = class of TCommonThreadRequest;

  // ***************************************************************
  // A ThreadRequest that uses a PIDL to extract its data from within
  // the context of the thread.
  // ***************************************************************
  TPIDLThreadRequest = class(TCommonThreadRequest)
  private
    FPIDL: PItemIDList;
  public
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
    property PIDL: PItemIDList read FPIDL write FPIDL;
  end;

  // ***************************************************************
  // A ThreadRequest that extracts the shell supplied Icon index of an object via a PIDL
  // ***************************************************************
  TShellIconThreadRequest = class(TPIDLThreadRequest)
  private
    FImageIndex: Integer; // [OUT] the image index for the PIDL
    FLarge: Boolean; // [IN] Get the large image index
    FOpen: Boolean;  // [IN] Get the "open" (folder expanded) image index
  public
    function HandleRequest: Boolean; override; // Extracts the Icon from the PIDL
    property ImageIndex: Integer read FImageIndex;
    property Large: Boolean read FLarge write FLarge;
    property Open: Boolean read FOpen write FOpen;
  end;

  // ***************************************************************
  // A ThreadRequest that has a callback function instead of a Window Handle to send a message to
  // ***************************************************************
  TPIDLCallbackThreadRequest = class(TPIDLThreadRequest)
  private
    FCallbackProc: TNamespaceCallbackProc;
    FTargetObject: TObject;
  public
    procedure Assign(Source: TPersistent); override;
    property CallbackProc: TNamespaceCallbackProc read FCallbackProc write FCallbackProc;
    property TargetObject: TObject read FTargetObject write FTargetObject;
  end;

  // **************************************************************************
  // Easy Thread
  //   A thread object that does not use a Syncronize type implementation.  The
  // method of blocking the main thread to service the thread result is ok for
  // a long processing thread that is not used often.  Extracting Icons for an
  // Explorer type listview for instance needs to update the listview very fast
  // and very often.  In this case it is better to use the Window messaging
  // system to post message to the window with the extracted data and allow the
  // window to process it when it can.  This method is much smoother for the GUI
  // but has more challenges to keep it thread safe.  A number of syncronization
  // objects are defined and created to use for various tasks.
  // Use the TCommonThreadManager and its methods to make using TCommonThread easier
  // and safer.
  // **************************************************************************
  TCommonThread = class
  private
    FFreeOnTerminate: Boolean;
    FHandle: THandle;
    FOLEInitialized: Boolean;
    FTargetWnd: HWnd; // Window that the message is posted to.  It will get a WM_COMMONTHREADNOTIFIER message with the TCommonThreadRequest in LParam
    FThreadID: THandle;
    FStub: pointer;
    FTerminated: Boolean;
    FSuspended: Boolean;
    FEvent: THandle;
    FCriticalSectionInitialized: Boolean;
    FCriticalSection: TRTLCriticalSection;
    FRefCount: Integer;
    FRequestList: TThreadList;
    FDirection: TCommonThreadDirection;
    FRunning: Boolean;
    FRequestListLocked: Boolean;
    FTempListLock: TList;
    function GetPriority: TThreadPriority;
    procedure SetPriority(const Value: TThreadPriority);
    procedure SetSuspended(const Value: Boolean);
    procedure ExecuteStub;  stdcall;
    function GetLock: TRTLCriticalSection;
    function GetEvent: THandle;
    procedure SetDirection(const Value: TCommonThreadDirection);
    procedure SetRequestListLocked(const Value: Boolean);
  protected
    FFinished: Boolean;
    procedure AddRequest(Request: TCommonThreadRequest; DoSetEvent: Boolean);
    procedure Execute; virtual; abstract; // Called in context of thread
    procedure FinalizeThread; virtual;    // Called in context of thread
    procedure InitializeThread; virtual;  // Called in context of thread

    property CriticalSectionInitialized: Boolean read FCriticalSectionInitialized write FCriticalSectionInitialized;
    property Event: THandle read GetEvent;
    property RequestListLocked: Boolean read FRequestListLocked write SetRequestListLocked; // Don't set/reset this across threads!
    property Stub: pointer read FStub write FStub;
    property Terminated: Boolean read FTerminated;
  public
    constructor Create(CreateSuspended: Boolean); virtual;
    destructor Destroy; override;

    procedure AddRef;
    procedure FlushRequestList;
    procedure ForceTerminate;
    procedure LockThread;
    procedure Release;
    procedure Resume;
    procedure Terminate; virtual;
    procedure TriggerEvent;
    procedure UnlockThread;
    property Direction: TCommonThreadDirection read FDirection write SetDirection;
    property Finished: Boolean read FFinished;
    property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate;
    property Handle: THandle read FHandle;
    property Lock: TRTLCriticalSection read GetLock;
    property OLEInitialized: Boolean read FOLEInitialized;
    property Priority: TThreadPriority read GetPriority write SetPriority default tpNormal;
    property RefCount: Integer read FRefCount write FRefCount;
    property RequestList: TThreadList read FRequestList write FRequestList;
    property Running: Boolean read FRunning;
    property Suspended: Boolean read FSuspended write SetSuspended;
    property TargetWnd: HWnd read FTargetWnd write FTargetWnd;
    property ThreadID: THandle read FThreadID;
  end;
  TCommonBaseThreadClass = class of TCommonThread;

  // **************************************************************************
  // Event Thread
  //   A decendant of TCommonThread that takes the encapsulation a step further.
  // This class defines a thread loop that calls virtual methods that can be
  // overridden in a decendent.  It makes creating a thread VERY easy and
  // very safe.
  // **************************************************************************
  TCommonEventThread = class(TCommonThread)
  private
    FTargetWndNotifyMsg: DWORD;
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended: Boolean); override;
    destructor Destroy; override;
    property TargetWndNotifyMsg: DWORD read FTargetWndNotifyMsg write FTargetWndNotifyMsg;
  end;
  TCommonEventThreadClass = class of TCommonEventThread;

  // **************************************************************************
  // ShellExecute Thread
  //   A decendant of TCommonThread that ShellExecuteEx's in a thread
  // **************************************************************************
  TCommonShellExecuteThread = class(TCommonThread)
  private
    FlpClass: WideString;
    FlpDirectory: WideString;
    FlpFile: WideString;
    FlpParameters: WideString;
    FlpVerb: WideString;
    FPIDL: PItemIDList;
  protected
    procedure Execute; override;
  public
    ShellExecuteInfoA: TShellExecuteInfoA;
    ShellExecuteInfoW: TShellExecuteInfoW;

    constructor Create(CreateSuspended: Boolean); override;
    destructor Destroy; override;
    // Need local variable for the strings and PIDLs so they won't get freed on
    // us before the thread uses them.
    property lpClass: WideString read FlpClass write FlpClass;
    property lpDirectory: WideString read FlpDirectory write FlpDirectory;
    property lpFile: WideString read FlpFile write FlpFile;
    property lpParameters: WideString read FlpParameters write FlpParameters;
    property lpVerb: WideString read FlpVerb write FlpVerb;
    property PIDL: PItemIDList read FPIDL write FPIDL;
  end;


  // **************************************************************************
  // Callback Event Thread
  // **************************************************************************
  TCommonCallbackEventThread = class(TCommonEventThread)
  protected
    procedure Execute; override;
  end;

  // **************************************************************************
  // Thread Manager
  //  A class the encapsulate the TCommonThread or decendant. The Thread filters
  // all requests to a window created in this object for dispatch to the
  // desired window and messageID.  By accessing the Thread through the methods
  // in the class using the thread is simple and safe from race conditions since
  // the class handles the syncronization of the data for you.
  // Simply register a TWinControl decendent and create a TCommonThreadRequest
  // decendant. Make SURE to override the Assign method, the thread must make
  // a copy of each object to use to extract the data.
  // This copy is what is sent to the registered window, not the original
  // that was added to the Request list through AddRequest.
  // **************************************************************************
  TCommonThreadManager = class(TComponent)
  private
    FAClassName: String;
    FControlList: TThreadList;
    FStub: Pointer;
    FFilterWindow: HWND;
    FEnabled: Boolean;

    function GetThread: TCommonThread;
    function GetFilterWindow: HWND;
    function GetRequestCount: Integer;
    procedure SetEnabled(const Value: Boolean);
  protected
    FThread: TCommonThread;

    function FilterWndProc(Wnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
    procedure CreateThreadObject; virtual;
    function FindControl(Window: TWinControl; LockedList: TList): Integer;
    procedure DispatchRequest(lParam: LPARAM; wParam: WPARAM); virtual;
    procedure FreeThread;
    procedure InternalUnRegisterControl(Window: TWinControl; LockedControlList: TList);
    procedure RegisterFilterWindow;

    property AClassName: String read FAClassName write FAClassName;
    property ControlList: TThreadList read FControlList write FControlList;
    property FilterWindow: HWND read GetFilterWindow write FFilterWindow;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure AddRequest(Request: TCommonThreadRequest; DoSetEvent: Boolean);
    procedure FlushAllMessageCache(Window: TWinControl);
    procedure FlushMessageCache(Window: TWinControl; RequestID: Cardinal);
    function RegisterControl(Window: TWinControl): Boolean;
    procedure UnRegisterAll;
    procedure UnRegisterControl(Window: TWinControl);

    property RequestCount: Integer read GetRequestCount;
    property Thread: TCommonThread read GetThread;

  published
    property Enabled: Boolean read FEnabled write SetEnabled default False;
  end;

  // **************************************************************************
  // Callback Thread Manager
  // **************************************************************************
  TCallbackThreadManager = class(TCommonThreadManager)
  protected
    procedure CreateThreadObject; override;
    procedure DispatchRequest(lParam: LPARAM; wParam: WPARAM); override;
  public
    procedure AddRequest(Request: TPIDLCallbackThreadRequest; DoSetEvent: Boolean); reintroduce;
    procedure FlushObjectCache(AnObject: TObject);
  end;


function GlobalThreadManager: TCommonThreadManager;
function GlobalCallbackThreadManager: TCallbackThreadManager;

implementation

uses
  MPCommonUtilities, MPCommonObjects, MPResources;

var
  PIDLMgr: TCommonPIDLManager;
  GlobalThread: TCommonThreadManager;
  GlobalCallbackThread: TCallbackThreadManager;
  ThreadsAlive: Integer = 0;
  ThreadRequestsAlive: Integer = 0;

function GlobalThreadManager: TCommonThreadManager;
begin
  if not Assigned(GlobalThread) then
  begin
    GlobalThread := TCommonThreadManager.Create(nil);
    GlobalThread.Enabled := True;
  end;
  Result := GlobalThread
end;

function GlobalCallbackThreadManager: TCallbackThreadManager;
begin
  if not Assigned(GlobalCallbackThread) then
  begin
    GlobalCallbackThread := TCallbackThreadManager.Create(nil);
    GlobalCallbackThread.Enabled := True;
  end;
  Result := GlobalCallbackThread
end;

{ TCommonThreadRequest }
destructor TCommonThreadRequest.Destroy;
begin
  Dec(ThreadRequestsAlive);
  inherited Destroy;
end;


procedure TCommonThreadRequest.Assign(Source: TPersistent);
var
  S: TCommonThreadRequest;
begin
  if Source is TCommonThreadRequest then
  begin
    S := TCommonThreadRequest(Source);
    Window := S.Window;
    ID := S.ID;
    Item := S.Item;
    RemainingRequests := S.RemainingRequests;
    CallbackWndMessage := S.CallbackWndMessage;
    Priority := S.Priority;
    Tag := S.Tag
  end
end;

constructor TCommonThreadRequest.Create;
begin
  inherited;
  FCallbackWndMessage := WM_COMMONTHREADCALLBACK;
  Priority := 50;
  Inc(ThreadRequestsAlive)
end;

procedure TCommonThreadRequest.Prioritize(RequestList: TList);
begin
  // Override to allow control of the order the thread requests are sorted
end;

procedure TCommonThreadRequest.Release;
begin
  InterlockedDecrement(FRefCount);
  if RefCount <= 0 then
  begin
    RefCount := 0;
    Free
  end
end;

{ TEasyPIDLThreadRequest }

destructor TPIDLThreadRequest.Destroy;
begin
  PIDLMgr.FreePIDL(PIDL);
  inherited Destroy;
end;

procedure TPIDLThreadRequest.Assign(Source: TPersistent);
begin
  inherited Assign(Source);
  if Source is TPIDLThreadRequest then
    PIDL := PIDLMgr.CopyPIDL(TPIDLThreadRequest( Source).PIDL);
end;

{ TEasyIconThreadRequest }

function TShellIconThreadRequest.HandleRequest: Boolean;

  function GetIconByIShellIcon(PIDL: PItemIDList; var Index: integer): Boolean;
  var
    Flags: Longword;
    OldCB: Word;
    Old_ID: PItemIDList;
    Desktop, Folder: IShellFolder;
    ShellIcon: IShellIcon;
  begin
    Result := False;
    PIDLMgr.StripLastID(PIDL, OldCB, Old_ID);
    try
      SHGetDesktopFolder(Desktop);
      Desktop.BindToObject(PIDL, nil, IShellFolder, Pointer(Folder));
      Old_ID.mkid.cb := OldCB;
      if Assigned(Folder) then
        if Folder.QueryInterface(IShellIcon, ShellIcon) = S_OK then
        begin
          Flags := GIL_FORSHELL;
          if Open then
            Flags := Flags or GIL_OPENICON;
          Result := ShellIcon.GetIconOf(Old_ID, Flags, Index) = NOERROR
      end
    finally
      Old_ID.mkid.cb := OldCB
    end
  end;

  procedure GetIconBySHGetFileInfo(APIDL: PItemIDList; var Index: Integer);
  var
    Flags: integer;
    Info: TSHFILEINFO;
  begin
    Flags := SHGFI_PIDL or SHGFI_SYSICONINDEX or SHGFI_SHELLICONSIZE;
    if Large then
      Flags := Flags or SHGFI_LARGEICON
    else
      Flags := Flags or SHGFI_SMALLICON;

    if Open then
      Flags := Flags or SHGFI_OPENICON;
      
    if SHGetFileInfo(PChar(APIDL), 0, Info, SizeOf(Info), Flags) <> 0 then
      Index := Info.iIcon
    else
      Index := 0
  end;

begin
  Result := GetIconByIShellIcon(PIDL, FImageIndex);
  if not Result then
    GetIconBySHGetFileInfo(PIDL, FImageIndex);
  Result := True;
end;

{ TPIDLCallbackThreadRequest }

procedure TPIDLCallbackThreadRequest.Assign(Source: TPersistent);
begin
  inherited Assign(Source);
  CallbackProc := (Source as TPIDLCallbackThreadRequest).CallbackProc;
  TargetObject := (Source as TPIDLCallbackThreadRequest).TargetObject
end;

{ TCommonThread }

procedure TCommonThread.AddRef;
begin
  InterlockedIncrement(FRefCount);
end;

procedure TCommonThread.AddRequest(Request: TCommonThreadRequest; DoSetEvent: Boolean);
var
  List: TList;
begin
  List := RequestList.LockList;
  try
    List.Add(Request);
    if DoSetEvent then
      TriggerEvent;
  finally
    RequestList.UnlockList
  end
end;

constructor TCommonThread.Create(CreateSuspended: Boolean);
var
  Flags: DWORD;
begin
  Inc(ThreadsAlive);
  IsMultiThread := True;
  Direction := etdFirstInLastOut;
  RequestList := TThreadList.Create;
  Stub := CreateStub(Self, @TCommonThread.ExecuteStub);
  Flags := 0;
  if CreateSuspended then
  begin
    Flags := CREATE_SUSPENDED;
    FSuspended := True
  end;
  FHandle := CreateThread(nil, 0, Stub, nil, Flags, FThreadID);
end;

destructor TCommonThread.Destroy;
var
  i: Integer;
  List: TList;
begin
  Assert(Finished, 'The Thread must be terminated before destroying the TCommonThread object');
  DisposeStub(Stub);
  Stub := nil;
  if Handle <> 0 then
    CloseHandle(Handle);
  FHandle := 0;
  if Event <> 0 then
    CloseHandle(Event);
  FEvent := 0;
  if CriticalSectionInitialized then
    DeleteCriticalSection(FCriticalSection);
  List := RequestList.LockList;
  try
    for i := 0 to List.Count - 1 do
      TObject(List[i]).Free;
    List.Count := 0;
  finally
    RequestList.UnlockList;
  end;
  FreeAndNil(FRequestList);
  FRequestList := nil;
  Dec(ThreadsAlive);
  inherited;
end;

procedure TCommonThread.ExecuteStub;
// Called in the context of the thread
{$IFDEF DEBUG_THREAD}
var
  ThreadNameInfo: TThreadNameInfo;
{$ENDIF}
begin
  {$IFDEF DEBUG_THREAD}
  if IsWinNT then
  begin
    // Set the name for the thread to debug it with the Thread View panel.
    // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsdebug/html/vxtsksettingthreadname.asp
    // http://bdn.borland.com/article/0,1410,29800,00.html
    ThreadNameInfo.FType := $1000;
    ThreadNameInfo.FName := PChar(string(ClassName));
    ThreadNameInfo.FThreadID := $FFFFFFFF;
    ThreadNameInfo.FFlags := 0;
    try
      RaiseException($406D1388, 0, sizeof(ThreadNameInfo) div sizeof(LongWord), @ThreadNameInfo);
    except
    end;
  end;
  {$ENDIF}

  try
    FRunning := True;
    InitializeThread;
    try
      Execute
    except
    end
  finally
    FinalizeThread;
    {$IFDEF DRAG_OUT_THREAD_SHUTDOWN}
    Sleep(3000);
    {$ENDIF}
    if FreeOnTerminate then
    begin
      // If FreeOnTerminate then the user can't expect to look at these
      // variables since they can't be sure when the object will be freed
      FRunning := False;
      FFinished := True;
      FHandle := 0;
      Free;
      ExitThread(0);
    end else
    begin
      // Set these here or there will be a race condtion as to when the
      // main thread frees the object and we still need to access
      // local variables (like FreeOnTerminate)
      FRunning := False;
      FFinished := True;
      FHandle := 0;
      ExitThread(0);
    end
  end
end;

function TCommonThread.GetEvent: THandle;
begin
  if FEvent = 0 then
    FEvent := CreateEvent(nil, True, False, nil);
  Result := FEvent;
end;

function TCommonThread.GetLock: TRTLCriticalSection;
begin
  if not CriticalSectionInitialized then
  begin
    InitializeCriticalSection(FCriticalSection);
    CriticalSectionInitialized := True
  end;
  Result := FCriticalSection
end;

function TCommonThread.GetPriority: TThreadPriority;
var
  P: Integer;
begin
  Result := tpNormal;
  P := GetThreadPriority(FHandle);
  case P of
    THREAD_PRIORITY_IDLE:          Result := tpIdle;
    THREAD_PRIORITY_LOWEST:        Result := tpLowest;
    THREAD_PRIORITY_BELOW_NORMAL:  Result := tpLower;
    THREAD_PRIORITY_NORMAL:        Result := tpNormal;
    THREAD_PRIORITY_HIGHEST:       Result := tpHigher;
    THREAD_PRIORITY_ABOVE_NORMAL:  Result := tpHighest;
    THREAD_PRIORITY_TIME_CRITICAL: Result := tpTimeCritical;
  end
end;

procedure TCommonThread.FinalizeThread;
begin
  try
    if OLEInitialized then
      OLEUnInitialize
  except
  end
end;

procedure TCommonThread.FlushRequestList;
var
  List: TList;
  i: Integer;
  Request: TObject;
begin
  List := RequestList.LockList;
  try
    for i := List.Count - 1 downto 0 do
    begin
      Request := TObject(TObject(List[i]));
      List.Delete(i);
      Request.Free;
    end
  finally
    RequestList.UnlockList
  end
end;

procedure TCommonThread.InitializeThread;
begin
  FOLEInitialized := Succeeded( OLEInitialize(nil))
end;

procedure TCommonThread.LockThread;
begin
  if not CriticalSectionInitialized then
    Lock;
  EnterCriticalSection(FCriticalSection)
end;

procedure TCommonThread.Release;
begin
  InterlockedDecrement(FRefCount);
end;

procedure TCommonThread.Resume;
begin
  Suspended := False
end;

procedure TCommonThread.SetDirection(const Value: TCommonThreadDirection);
begin
  FDirection := Value;
end;

procedure TCommonThread.SetPriority(const Value: TThreadPriority);
begin
  case Value of
    tpIdle        : SetThreadPriority(Handle,  THREAD_PRIORITY_IDLE);
    tpLowest      : SetThreadPriority(Handle, THREAD_PRIORITY_LOWEST);
    tpLower       : SetThreadPriority(Handle, THREAD_PRIORITY_BELOW_NORMAL);
    tpNormal      : SetThreadPriority(Handle, THREAD_PRIORITY_NORMAL);
    tpHigher      : SetThreadPriority(Handle, THREAD_PRIORITY_HIGHEST);
    tpHighest     : SetThreadPriority(Handle, THREAD_PRIORITY_ABOVE_NORMAL);
    tpTimeCritical: SetThreadPriority (Handle, THREAD_PRIORITY_TIME_CRITICAL);
  end
end;

procedure TCommonThread.SetRequestListLocked(const Value: Boolean);
begin
  if FRequestListLocked <> Value then
  begin
    if Value then
      FTempListLock := RequestList.LockList
    else
      RequestList.UnlockList;
    FRequestListLocked := Value;
  end
end;

procedure TCommonThread.SetSuspended(const Value: Boolean);
begin
  if FSuspended <> Value then
  begin
    if Handle <> 0 then
    begin
      if Value then
        SuspendThread(FHandle)
      else
        ResumeThread(FHandle);
      FSuspended := Value;
    end
  end
end;

procedure TCommonThread.Terminate;
begin
  Suspended := False;
  FTerminated := True;
  TriggerEvent;
end;

procedure TCommonThread.TriggerEvent;
begin
  SetEvent(Event);
end;

procedure TCommonThread.ForceTerminate;
var
  Temp: THandle;
begin
  Temp := Handle;
  if Temp <> 0 then
  begin
    FHandle := 0;
    FRunning := False;
    FFinished := True;
    TerminateThread(Temp, 0);
  end
end;

procedure TCommonThread.UnlockThread;
begin
  if CriticalSectionInitialized then
    LeaveCriticalSection(FCriticalSection)
end;

{ TCommonEventThread }

function PrioritizeSort(Item1, Item2: Pointer): Integer;
begin
  Result := TCommonThreadRequest(Item2).Priority - TCommonThreadRequest(Item1).Priority
end;

constructor TCommonEventThread.Create(CreateSuspended: Boolean);
begin
  inherited Create(CreateSuspended);
  TargetWndNotifyMsg := WM_COMMONTHREADNOTIFIER;  // Default for the internal dispatch Window
  // Create the event
  Event;
end;

destructor TCommonEventThread.Destroy;
begin
  inherited Destroy;
end;

procedure TCommonEventThread.Execute;
var
  ARequestCopy, OriginalRequest: TCommonThreadRequest;
  List: TList;
  WorkingIndex, SafetyValve: Integer;
  LoopCount: Cardinal;
  RequestClassType: TCommonThreadRequestClass;
begin
  LoopCount := 0;
  while not Terminated and (TargetWnd <> 0) do
  try
    WaitForSingleObject(Event, INFINITE);
    if not Terminated then
    begin
      // Small breather so thread does not steal too much processor time
      Sleep(0);
      // Take the Request from the list and make a copy of it to work with.
      // This is in case a control purges the list of requests, this way the
      // thread is working on a copy and when it it ready it sees if the original
      // is still in the list.  If not it dumps it.
      List := RequestList.LockList;
      try
        // Only do this every so many extracts.  It can be rather slow with lots of items
        if LoopCount mod 10 = 0 then
          List.Sort(PrioritizeSort);

        ARequestCopy := nil;
        OriginalRequest := nil;

        if List.Count > 0 then
        begin
          if Direction = etdFirstInFirstOut then
            WorkingIndex := 0
          else
            WorkingIndex := List.Count - 1;
          // Make a copy of the pointer to the original Request
          OriginalRequest := TCommonThreadRequest(List[WorkingIndex]);
          // Need to make a copy of the Request.  This is because while we are
          // extracting the data for the Request the main thread may do something
          // to the list that renders the object in the list invalid.  By working
          // with a copy we do not worry about it.  We will later check to make
          // sure that the object on the list is still valid before dispatching the
          // results to the main thread

          RequestClassType := TCommonThreadRequestClass(OriginalRequest.ClassType);
          ARequestCopy := RequestClassType.Create;
          ARequestCopy.Assign(OriginalRequest);
        end else
        begin
          WorkingIndex := -1;
          // Reset the event to enter the WaitForSingleObject method again
          ResetEvent(FEvent)
        end
      finally
        RequestList.UnlockList
      end;

      if Assigned(ARequestCopy) then
      begin
        ARequestCopy.FThread := Self;
        // Extract the data for the Request
        if ARequestCopy.HandleRequest then
        begin
          List := RequestList.LockList;
          try
            // Check to see if the WorkingIndex is still valid in the list.
            // If not the item will be left if the queue and it will have to be done again
            if (List.Count > 0) and (WorkingIndex < List.Count) then
            begin
              // Now make sure the actual object is still in the list in the same
              // position.  If not then we will leave it in the list and extract it
              // again.
              if (OriginalRequest = TCommonThreadRequest(List[WorkingIndex])) then
              begin
                // It still exists so we can delete it as we will dispatch it
                List.Delete(WorkingIndex);
                ARequestCopy.RemainingRequests := List.Count;
                // Try to Post it to the main thread through the ThreadManagers window
                SafetyValve := 0;
                while not PostMessage(TargetWnd, TargetWndNotifyMsg, 0,
                  LPARAM(ARequestCopy)) and (SafetyValve < COMMONTHREADSAFETYVALVE) do
                begin
                  Inc(SafetyValve);
                  Sleep(10);
                end;

                // If failed (VERY, VERY, VERY unlikely) then add it back to the list
                // and try it again later
                if SafetyValve >= COMMONTHREADSAFETYVALVE then
                begin
                  List.Add(OriginalRequest);
                  FreeAndNil(ARequestCopy)
                end else
                  FreeAndNil(OriginalRequest);
              end else
                FreeAndNil(ARequestCopy)
            end else
              FreeAndNil(ARequestCopy)
          finally
            RequestList.UnlockList
          end
        end else
          FreeAndNil(ARequestCopy)
      end;
      Inc(LoopCount)
    end
  except
  end
end;

{ TCommonThreadManager }

procedure TCommonThreadManager.AddRequest(Request: TCommonThreadRequest; DoSetEvent: Boolean);
var
  DoAdd: Boolean;
begin
  if Assigned(Request) and Enabled then
  begin
    DoAdd := False;
    if (Request is TPIDLCallbackThreadRequest) then
      DoAdd := Assigned( TPIDLCallbackThreadRequest(Request).CallbackProc) and
        Assigned(TPIDLCallbackThreadRequest(Request).TargetObject);
    if not DoAdd then
      DoAdd := (FindControl(Request.Window, nil) > -1) or Assigned(Request.Window);
    Assert(DoAdd, STR_UNREGISTEREDCONTROL);
    if DoAdd and Assigned(Thread) then
      Thread.AddRequest(Request, DoSetEvent);
  end else
    Request.Free
end;

constructor TCommonThreadManager.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlList := TThreadList.Create;
  AClassName := COMMONTHREADFILTERWNDCLASS + IntToStr( Integer( Self))
end;

procedure TCommonThreadManager.CreateThreadObject;
begin
  FThread := TCommonEventThread.Create(True);
  FThread.TargetWnd := FilterWindow;
  Thread.Resume;
end;

destructor TCommonThreadManager.Destroy;
begin
  UnRegisterAll;
  FlushMessageCache(nil, TID_START);
  // Thread Freed with last client is unregistered
  // Safe to destroy the window now
  if FFilterWindow <> 0 then
    DestroyWindow(FFilterWindow);
  FFilterWindow := 0;
  // Unregister the window class.  If another thread is also using this class
  // windows will not unregister it until the last thread has destroyed any windows
  // based on this class
  if AClassName <> '' then
    Windows.UnregisterClass(PChar(AClassName), hInstance);
  // Free the stub for the window procedure
  if Assigned(FStub) then
    DisposeStub(FStub);
  FStub := nil;
  FreeAndNil(FControlList);
  FreeThread;
  inherited;
end;

function TCommonThreadManager.FilterWndProc(Wnd: HWND; uMsg: UINT;
  wParam: WPARAM; lParam: LPARAM): LRESULT;
begin
  Result := 0;
  case uMsg of
    WM_NCCREATE:  Result := 1;
    WM_COMMONTHREADNOTIFIER: DispatchRequest(lParam, wParam);
  else
    Result := DefWindowProc(Wnd, uMsg, wParam, lParam);
  end
end;

function TCommonThreadManager.FindControl(Window: TWinControl; LockedList: TList): Integer;
//
// Loops the Window/MessageID pairs in the ControlList looking for a match
//
var
  List: TList;
  I: Integer;
  Found: Boolean;
begin
  Result := -1;
  if not Assigned(LockedList) then
    List := ControlList.LockList
  else
    List := LockedList;

  try
    I := 0;
    Found := False;
    while (I < List.Count) and not Found do
    begin
      if (Window = TWinControl(List[I])) then
      begin
        Result := I;
        Found := True
      end;
      Inc(i);
    end
  finally
    if not Assigned(LockedList) then
      ControlList.UnlockList
  end
end;

procedure TCommonThreadManager.DispatchRequest(lParam: LPARAM; wParam: WPARAM);
var
  i: Integer;
  RegList: TList;
  Request: TCommonThreadRequest;
  RequestList: TList;
begin
  Request := TCommonThreadRequest(lParam);
  try
    RequestList := Thread.RequestList.LockList;
    try
      // Allow the Request to prioritize the list if it wants to
      Request.Prioritize(RequestList);

      if not Assigned(Request.Window) then
      begin
        RegList := ControlList.LockList;
        try
          Request.RefCount := 1;
          for i := 0 to RegList.Count - 1 do
          begin
            if TWinControl(RegList[i]).HandleAllocated then
              Request.RefCount := RegList.Count + 1;
          end;
                // It is a broadcast message
          wParam := Request.ID;
          for i := 0 to RegList.Count - 1 do
          begin
            if TWinControl(RegList[i]).HandleAllocated then
              SendMessage(TWinControl(RegList[i]).Handle, Request.CallbackWndMessage, wParam, lParam);
          end
        finally
          ControlList.UnlockList
        end
      end
    finally
      Thread.RequestList.UnLockList;
    end;

          // Not a broadcast message
    if Assigned(Request.Window) then
    begin
      Request.RefCount := 1;
      if Request.Window.HandleAllocated then
      begin
        Request.RefCount := Request.RefCount + 1;
        wParam := Request.ID;
        SendMessage(Request.Window.Handle, Request.CallbackWndMessage, wParam, lParam);
      end
    end;
  finally
    Request.Release
  end;
end;

procedure TCommonThreadManager.FlushAllMessageCache(Window: TWinControl);
// Flushes all the message cache
begin
  FlushMessageCache(Window, TID_START);
end;

procedure TCommonThreadManager.FlushMessageCache(Window: TWinControl; RequestID: Cardinal);
// First locks the thread by locking its RequestList.  This stops the thread
// from accessing a new request.  It then flushes the Windows message cache
// of pending messages matching the RequestId.
// Next any pending requests in the Threads RequestList matching the RequestID
// are removed and freed.
var
  Msg: TMsg;
  List: TList;
  I: Integer;
  R: TCommonThreadRequest;
  RepostQuitMsg: Boolean;
  QuitMsgExitCode: Integer;
begin
  List := nil;
  if Enabled then
  begin
    RepostQuitMsg := False;
    QuitMsgExitCode := 0;
    // If the thread is not created yet then there is no point in flushing it
    if Assigned(FThread) then
      List := Thread.RequestList.LockList;
    try
      // Remove the requests in the hidden dispatch message cache
      // I have seen PeekMessage return true and return the WM_QUIT message, this
      // strips the queue of the message to shut down the app!  Don't strip it out
      // until we check to see if it is the right message
      if PeekMessage(Msg, FilterWindow, WM_COMMONTHREADNOTIFIER, WM_COMMONTHREADNOTIFIER, PM_REMOVE) then
      begin
        if Msg.Message = WM_QUIT then
        begin
          QuitMsgExitCode := Msg.wParam;
          RepostQuitMsg := True;
        end else
        begin
          // If the message is for the window to flush then free it, else dispatch it normally
          R := TCommonThreadRequest(Msg.lParam);
          if (R.Window = Window) and ((RequestID = TID_START) or (R.ID = RequestID)) then
            R.Free
          else begin
            if R.Window.HandleAllocated then
              SendMessage(R.Window.Handle, R.CallbackWndMessage, Msg.wParam, Msg.lParam)
          end
        end
      end;

      if Assigned(Window) then
      begin
        // Remove the requests in the windows cache
        if Window.HandleAllocated then
        begin
          // I have seen PeekMessage return true and return the WM_QUIT message, this
          // strips the queue of the message to shut down the app!  Don't strip it out
          // until we check to see if it is the right message
          if PeekMessage(Msg, Window.Handle, WM_COMMONTHREADCALLBACK, WM_COMMONTHREADCALLBACK, PM_REMOVE) then
          begin
            if Msg.Message = WM_QUIT then
            begin
              QuitMsgExitCode := Msg.wParam;
              RepostQuitMsg := True;
            end else
            begin
              // If the message is for the window to flush then free it
              R := TCommonThreadRequest(Msg.lParam);
              if (R.Window = Window) and ((RequestID = TID_START) or (R.ID = RequestID)) then
                R.Free;
            end
          end;
          if RepostQuitMsg then
            PostQuitMessage(QuitMsgExitCode);
        end
      end;

      if Assigned(List) then
      begin
        // Now remove any waiting requests from the list in the thread
        for I := List.Count - 1 downto 0 do
        begin
          R := TCommonThreadRequest(List[I]);
          if (Window = nil) or ((R.Window = Window) and ((RequestID = TID_START) or (R.ID = RequestID))) then
          begin
            R.Free;
            List.Delete(I);
          end
        end;
      end
    finally
      if Assigned(FThread) then
        Thread.RequestList.UnlockList
    end;
  end;
end;

function TCommonThreadManager.GetFilterWindow: HWND;
begin
  if FFilterWindow = 0 then
  begin
    RegisterFilterWindow;
  end;
  Result := FFilterWindow;
end;

function TCommonThreadManager.GetRequestCount: Integer;
var
  List: TList;
begin
  if Enabled then
  begin
    List := Thread.RequestList.LockList;
    try
      Result := List.Count
    finally
      Thread.RequestList.UnlockList
    end
  end else
    Result := 0
end;

function TCommonThreadManager.GetThread: TCommonThread;
begin
  if not Assigned(FThread) then
    CreateThreadObject;
  Result := FThread;
end;

procedure TCommonThreadManager.FreeThread;
var
  i: Integer;
begin        
  if Assigned(FThread) then
  begin
    try
      i := 0;
      // Signal the thread the terminate
      FThread.Terminate;
      while not FThread.Finished do
      begin
        Sleep(THREAD_SHUTDOWN_WAIT_DELAY);
        if i > FORCE_KILL_THREAD_COUNT then
        begin
          FThread.ForceTerminate;
          Break
        end;
        Inc(i)
      end;
    finally
      // Done with the thread
      FreeAndNil(FThread);
    end
  end
end;

procedure TCommonThreadManager.InternalUnRegisterControl(Window: TWinControl; LockedControlList: TList);
//
// Unregisters the Window/MessageID pair
//
var
  List: TList;
  I: Integer;
begin
  if not Assigned(LockedControlList) then
    List := ControlList.LockList
  else
    List := LockedControlList;

  try
    if Enabled then
    begin
      // Lock the Thread from accessing its Request List until we are finished
      // If the thread is not created yet there is no point creating it
      if Assigned(FThread) then
        Thread.RequestListLocked := True;
      try
        I := FindControl(Window, List);
        if I > -1 then
        begin
          FlushAllMessageCache(Window);
          List.Delete(I);
        end;
      finally
        if Assigned(FThread) then
          Thread.RequestListLocked := False;
      end
    end else
    begin
      I := FindControl(Window, List);
      if I > -1 then
      begin
        FlushAllMessageCache(Window);
        List.Delete(i);
      end
    end
  finally
    if not Assigned(LockedControlList) then
      ControlList.UnlockList
  end

end;

function TCommonThreadManager.RegisterControl(Window: TWinControl): Boolean;
var
  List: TList;
begin
  RegisterFilterWindow;
  List := ControlList.LockList;
  try
    Result := List.Add(Window) > -1
  finally
    ControlList.UnlockList
  end
end;

procedure TCommonThreadManager.RegisterFilterWindow;
var
  ClassInfo: TWndClass;
begin
  if FFilterWindow = 0 then
  begin
    if not GetClassInfo(hInstance, PChar( AClassName), ClassInfo) then
    begin
      if not Assigned(FStub) then
        FStub := CreateStub(Self, @TCommonThreadManager.FilterWndProc);
      ClassInfo.style := 0;
      ClassInfo.lpfnWndProc := FStub;
      ClassInfo.cbClsExtra := 0;
      ClassInfo.cbWndExtra := 0;
      ClassInfo.hInstance := hInstance;
      ClassInfo.hIcon := 0;
      ClassInfo.hCursor := 0;
      ClassInfo.hbrBackground := 0;
      ClassInfo.lpszMenuName := '';
      ClassInfo.lpszClassName := PChar( AClassName);
      Windows.RegisterClass(ClassInfo);
    end;
    FFilterWindow := CreateWindow(PChar( AClassName), '', 0, 0, 0, 0, 0, 0, 0, hInstance, nil);
  end
end;

procedure TCommonThreadManager.SetEnabled(const Value: Boolean);
begin
  if Value <> FEnabled then
  begin
    if not (csDesigning in ComponentState) then
    begin
      if not Value then
      begin
        if Assigned(Thread) then
        begin
          if not Thread.Terminated then
          begin
            Thread.Terminate;
            while not Thread.Finished do
              Sleep(200);
          end;
          FreeThread;
        end
      end
    end;
    FEnabled := Value
  end
end;

procedure TCommonThreadManager.UnRegisterAll;
var
  List: TList;
  i: Integer;
begin
  List := ControlList.LockList;
  try
    for i := List.Count - 1 downto 0 do
      InternalUnRegisterControl(TWinControl(List[i]), List);
  finally
    ControlList.UnlockList
  end
end;

procedure TCommonThreadManager.UnRegisterControl(Window: TWinControl);
var
  List: TList;
begin
  List := ControlList.LockList;
  try
    InternalUnRegisterControl(Window, List)
  finally
    ControlList.UnlockList;
  end;
end;

procedure TCallbackThreadManager.AddRequest(Request: TPIDLCallbackThreadRequest;
  DoSetEvent: Boolean);
begin
  inherited AddRequest(Request, DoSetEvent)
end;

procedure TCallbackThreadManager.CreateThreadObject;
begin
  FThread := TCommonCallbackEventThread.Create(True);
  FThread.TargetWnd := FilterWindow;
  Thread.Resume;
  RegisterFilterWindow;
end;

procedure TCallbackThreadManager.DispatchRequest(lParam: LPARAM; wParam: WPARAM);
var
  RequestList: TList;
  Request: TPIDLCallbackThreadRequest;
begin
  Request := TPIDLCallbackThreadRequest(lParam);
  try
    Request.FRefCount := 1;
    RequestList := Thread.RequestList.LockList;
    try
      // Allow the Request to prioritize the list if it wants to
      Request.Prioritize(RequestList);
      if Assigned(Request.CallbackProc) then
      begin
        Request.FRefCount := 2;
        Request.CallbackProc(Request)
      end
    finally
      Thread.RequestList.UnLockList;
    end;
  finally
    Request.Release
  end
end;

procedure TCallbackThreadManager.FlushObjectCache(AnObject: TObject);
var
  List: TList;
  i: Integer;
begin
  if Assigned(Thread) then
  begin
    List := Thread.RequestList.LockList;
    try
      for i := 0 to List.Count - 1 do
      begin
        if TPIDLCallbackThreadRequest( List[i]).TargetObject = AnObject then
        begin
          List.Delete(i);
          Exit;
        end
      end;
    finally
      Thread.RequestList.UnlockList
    end
  end
end;

{ TCommonCallbackEventThread }
procedure TCommonCallbackEventThread.Execute;
begin
  inherited Execute;
end;

{ TCommonShellExecuteThread }
constructor TCommonShellExecuteThread.Create(CreateSuspended: Boolean);
begin
  inherited;
  FreeOnTerminate := True;
  FillChar(ShellExecuteInfoW, SizeOf(ShellExecuteInfoW), #0);
  FillChar(ShellExecuteInfoA, SizeOf(ShellExecuteInfoA), #0);
end;

destructor TCommonShellExecuteThread.Destroy;
begin
  PIDLMgr.FreePIDL(FPIDL);
  inherited Destroy;
end;

procedure TCommonShellExecuteThread.Execute;
begin
  if IsUnicode then
  begin
    if lpClass <> '' then
      ShellExecuteInfoW.lpClass := PWideChar(lpClass);
    if lpDirectory <> '' then
      ShellExecuteInfoW.lpDirectory := PWideChar(lpDirectory);
    if lpFile <> '' then
      ShellExecuteInfoW.lpFile := PWideChar(lpFile);
    if lpParameters <> '' then
      ShellExecuteInfoW.lpParameters := PWideChar(lpParameters);
    if lpVerb <> '' then
      ShellExecuteInfoW.lpVerb := PWideChar(lpVerb);
    ShellExecuteInfoW.lpIDList := PIDL;
    ShellExecuteExW_MP(@ShellExecuteInfoW);
  end else
  begin
    if lpClass <> '' then
      ShellExecuteInfoA.lpClass := PChar(string( lpClass));
    if lpDirectory <> '' then
      ShellExecuteInfoA.lpDirectory := PChar(string( lpDirectory));
    if lpFile <> '' then
      ShellExecuteInfoA.lpFile := PChar(string( lpFile));
    if lpParameters <> '' then
      ShellExecuteInfoA.lpParameters := PChar(string( lpParameters));
    if lpVerb <> '' then
      ShellExecuteInfoA.lpVerb := PChar(string( lpVerb));
    ShellExecuteInfoA.lpIDList := PIDL;
    ShellExecuteExA(@ShellExecuteInfoA);
  end
end;

initialization
  IsMultiThread := True;
  PIDLMgr := TCommonPIDLManager.Create;

finalization
  FreeAndNil(GlobalThread);
  FreeAndNil(GlobalCallbackThread);
  FreeAndNil(PIDLMgr);

end.
