1543 lines
49 KiB
ObjectPascal
1543 lines
49 KiB
ObjectPascal
|
|
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.
|