Componentes.Terceros.Mustan.../official/1.7.0/Common Library/Source/MPThreadManager.pas

1543 lines
49 KiB
ObjectPascal
Raw Normal View History

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.