|
|
@@ -3,7 +3,7 @@ unit OperationWithTimeout;
|
|
|
interface
|
|
|
|
|
|
uses
|
|
|
- Winapi.Windows, Winapi.ShlObj, Winapi.ShellAPI, ActiveX, Winapi.ObjectArray;
|
|
|
+ Winapi.Windows, Winapi.ShlObj, Winapi.ShellAPI, ActiveX;
|
|
|
|
|
|
function ShellFolderGetAttributesOfWithTimeout(
|
|
|
ShellFolder: IShellFolder; cidl: UINT; var apidl: PItemIDList; var rgfInOut: UINT; Timeout: Integer): HResult;
|
|
|
@@ -22,7 +22,7 @@ function DestinationListBeginList(
|
|
|
implementation
|
|
|
|
|
|
uses
|
|
|
- System.Classes, System.Types, System.SysUtils, System.SyncObjs, System.Contnrs, CompThread;
|
|
|
+ System.Classes, System.Types, System.SysUtils, CompThread;
|
|
|
|
|
|
type
|
|
|
TOperation = class;
|
|
|
@@ -31,8 +31,6 @@ type
|
|
|
|
|
|
TOperation = class(TObject)
|
|
|
public
|
|
|
- OperationEvent: TOperationEvent;
|
|
|
-
|
|
|
// ShellFolderGetAttributesOfWithTimeout
|
|
|
ShellFolder: IShellFolder;
|
|
|
cidl: UINT;
|
|
|
@@ -63,146 +61,68 @@ type
|
|
|
pcMaxSlots: UINT;
|
|
|
riid: TIID;
|
|
|
ppv: Pointer;
|
|
|
-
|
|
|
- constructor Create(AOperationEvent: TOperationEvent);
|
|
|
end;
|
|
|
|
|
|
-constructor TOperation.Create(AOperationEvent: TOperationEvent);
|
|
|
-begin
|
|
|
- OperationEvent := AOperationEvent;
|
|
|
-end;
|
|
|
-
|
|
|
type
|
|
|
TOperationWithTimeoutThread = class(TCompThread)
|
|
|
public
|
|
|
- constructor Create;
|
|
|
- destructor Destroy; override;
|
|
|
- procedure Terminate; override;
|
|
|
-
|
|
|
- procedure Queue(Operation: TOperation);
|
|
|
- function WaitForOperation(Milliseconds: Cardinal): Boolean;
|
|
|
- procedure Remove(Operation: TOperation);
|
|
|
+ constructor Create(Operation: TOperation; OperationEvent: TOperationEvent);
|
|
|
|
|
|
protected
|
|
|
procedure Execute; override;
|
|
|
|
|
|
private
|
|
|
- FCriticalSection: TCriticalSection;
|
|
|
- FRequestEvent: THandle;
|
|
|
- FResultEvent: THandle;
|
|
|
- FQueue: TObjectList;
|
|
|
- FResults: TObjectList;
|
|
|
+ FOperation: TOperation;
|
|
|
+ FOperationEvent: TOperationEvent;
|
|
|
end;
|
|
|
|
|
|
-constructor TOperationWithTimeoutThread.Create;
|
|
|
+constructor TOperationWithTimeoutThread.Create(Operation: TOperation; OperationEvent: TOperationEvent);
|
|
|
begin
|
|
|
inherited Create(True);
|
|
|
- FRequestEvent := CreateEvent(nil, False, False, nil);
|
|
|
- FResultEvent := CreateEvent(nil, False, False, nil);
|
|
|
- FCriticalSection := TCriticalSection.Create;
|
|
|
- FQueue := TObjectList.Create;
|
|
|
- FResults := TObjectList.Create;
|
|
|
- Resume;
|
|
|
-end;
|
|
|
-
|
|
|
-destructor TOperationWithTimeoutThread.Destroy;
|
|
|
-begin
|
|
|
- inherited;
|
|
|
- FQueue.Free;
|
|
|
- FResults.Free;
|
|
|
- FCriticalSection.Free;
|
|
|
- CloseHandle(FRequestEvent);
|
|
|
- CloseHandle(FResultEvent);
|
|
|
-end;
|
|
|
-
|
|
|
-procedure TOperationWithTimeoutThread.Terminate;
|
|
|
-begin
|
|
|
- inherited;
|
|
|
- SetEvent(FRequestEvent);
|
|
|
+ FOperation := Operation;
|
|
|
+ FOperationEvent := OperationEvent;
|
|
|
end;
|
|
|
|
|
|
procedure TOperationWithTimeoutThread.Execute;
|
|
|
-var
|
|
|
- Operation: TOperation;
|
|
|
begin
|
|
|
// Needed for various API, particularly:
|
|
|
// - SHGetFileInfo fails to return icon index on some systems;
|
|
|
// - ICustomDestinationList.BeginList returns invalid "removed" array.
|
|
|
CoInitialize(nil);
|
|
|
-
|
|
|
- while WaitForSingleObject(FRequestEvent, INFINITE) = WAIT_OBJECT_0 do
|
|
|
- begin
|
|
|
- if Terminated then
|
|
|
- begin
|
|
|
- break;
|
|
|
- end
|
|
|
- else
|
|
|
- begin
|
|
|
- FCriticalSection.Enter;
|
|
|
- try
|
|
|
- Operation := TOperation(FQueue[0]);
|
|
|
- FQueue.Extract(Operation);
|
|
|
- finally
|
|
|
- FCriticalSection.Leave;
|
|
|
- end;
|
|
|
-
|
|
|
- Operation.OperationEvent(Operation);
|
|
|
- FResults.Add(Operation);
|
|
|
- SetEvent(FResultEvent);
|
|
|
- end;
|
|
|
- end;
|
|
|
-end;
|
|
|
-
|
|
|
-procedure TOperationWithTimeoutThread.Queue(Operation: TOperation);
|
|
|
-begin
|
|
|
- FCriticalSection.Enter;
|
|
|
- try
|
|
|
- FQueue.Add(Operation);
|
|
|
- finally
|
|
|
- FCriticalSection.Leave;
|
|
|
- end;
|
|
|
-
|
|
|
- SetEvent(FRequestEvent);
|
|
|
-end;
|
|
|
-
|
|
|
-function TOperationWithTimeoutThread.WaitForOperation(Milliseconds: Cardinal): Boolean;
|
|
|
-begin
|
|
|
- ResetEvent(FResultEvent);
|
|
|
- Result := (WaitForSingleObject(FResultEvent, Milliseconds) = WAIT_OBJECT_0);
|
|
|
-end;
|
|
|
-
|
|
|
-procedure TOperationWithTimeoutThread.Remove(Operation: TOperation);
|
|
|
-begin
|
|
|
- FCriticalSection.Enter;
|
|
|
- try
|
|
|
- FResults.Remove(Operation);
|
|
|
- finally
|
|
|
- FCriticalSection.Leave;
|
|
|
- end;
|
|
|
+ FOperationEvent(FOperation);
|
|
|
end;
|
|
|
|
|
|
+function WaitForOperation(
|
|
|
+ Operation: TOperation; OperationEvent: TOperationEvent; Milliseconds: Cardinal): Boolean;
|
|
|
+{$IFNDEF IDE}
|
|
|
var
|
|
|
- Thread: TOperationWithTimeoutThread = nil;
|
|
|
-
|
|
|
-procedure NeedThread;
|
|
|
+ Thread: TOperationWithTimeoutThread;
|
|
|
+{$ENDIF}
|
|
|
begin
|
|
|
- if not Assigned(Thread) then
|
|
|
+// When running from IDE, it triggers starting/exiting the thread taking ages.
|
|
|
+// So in IDE we revert to single-thread approach.
|
|
|
+{$IFDEF IDE}
|
|
|
+ OperationEvent(Operation);
|
|
|
+ Result := True;
|
|
|
+{$ELSE}
|
|
|
+ // Have to start new thread for each request. When shared thread is used, it eventually hangs.
|
|
|
+ // Most probably do to the fact that we violate COM threading model.
|
|
|
+ // So using a new thread for each request, is only a hack that happens to work by pure luck.
|
|
|
+ // We may want to use shared thread at least for COM-free operations, like SHGetFileInfo.
|
|
|
+ Thread := TOperationWithTimeoutThread.Create(Operation, OperationEvent);
|
|
|
+ Thread.Resume;
|
|
|
+ Result := Thread.WaitFor(MSecsPerSec);
|
|
|
+ if Result then
|
|
|
begin
|
|
|
- Thread := TOperationWithTimeoutThread.Create;
|
|
|
- end;
|
|
|
-end;
|
|
|
-
|
|
|
-function WaitForOperation(Milliseconds: Cardinal): Boolean;
|
|
|
-begin
|
|
|
- Result := Thread.WaitForOperation(Milliseconds);
|
|
|
- if not Result then
|
|
|
+ Thread.Free;
|
|
|
+ end
|
|
|
+ else
|
|
|
begin
|
|
|
// There's a chance for memory leak, if thread is terminated
|
|
|
// between WaitFor() and this line
|
|
|
Thread.FreeOnTerminate := True;
|
|
|
- Thread.Terminate;
|
|
|
- Thread := nil;
|
|
|
end;
|
|
|
+{$ENDIF}
|
|
|
end;
|
|
|
|
|
|
procedure ShellFolderGetAttributesOfOperation(Operation: TOperation);
|
|
|
@@ -223,19 +143,17 @@ function ShellFolderGetAttributesOfWithTimeout(
|
|
|
var
|
|
|
Operation: TOperation;
|
|
|
begin
|
|
|
- NeedThread;
|
|
|
- Operation := TOperation.Create(ShellFolderGetAttributesOfOperation);
|
|
|
+ Operation := TOperation.Create;
|
|
|
Operation.ShellFolder := ShellFolder;
|
|
|
Operation.cidl := cidl;
|
|
|
Operation.apidl := apidl;
|
|
|
Operation.rgfInOut := rgfInOut;
|
|
|
- Thread.Queue(Operation);
|
|
|
- if WaitForOperation(Timeout) then
|
|
|
+ if WaitForOperation(Operation, ShellFolderGetAttributesOfOperation, Timeout) then
|
|
|
begin
|
|
|
apidl := Operation.apidl;
|
|
|
rgfInOut := Operation.rgfInOut;
|
|
|
Result := Operation.ResultHResult;
|
|
|
- Thread.Remove(Operation);
|
|
|
+ Operation.Free;
|
|
|
end
|
|
|
else
|
|
|
begin
|
|
|
@@ -267,8 +185,7 @@ function SHGetFileInfoWithTimeout(
|
|
|
var
|
|
|
Operation: TOperation;
|
|
|
begin
|
|
|
- NeedThread;
|
|
|
- Operation := TOperation.Create(SHGetFileInfoOperation);
|
|
|
+ Operation := TOperation.Create;
|
|
|
if uFlags and SHGFI_PIDL <> 0 then
|
|
|
begin
|
|
|
Operation.PIDL := PItemIDList(pszPath);
|
|
|
@@ -281,12 +198,11 @@ begin
|
|
|
Operation.psfi := psfi;
|
|
|
Operation.cbFileInfo := cbFileInfo;
|
|
|
Operation.uFlags := uFlags;
|
|
|
- Thread.Queue(Operation);
|
|
|
- if WaitForOperation(Timeout) then
|
|
|
+ if WaitForOperation(Operation, SHGetFileInfoOperation, Timeout) then
|
|
|
begin
|
|
|
psfi := Operation.psfi;
|
|
|
Result := Operation.ResultDWordPtr;
|
|
|
- Thread.Remove(Operation);
|
|
|
+ Operation.Free;
|
|
|
end
|
|
|
else
|
|
|
begin
|
|
|
@@ -309,8 +225,7 @@ function ShellFolderParseDisplayNameWithTimeout(
|
|
|
var
|
|
|
Operation: TOperation;
|
|
|
begin
|
|
|
- NeedThread;
|
|
|
- Operation := TOperation.Create(ShellFolderParseDisplayNameOperation);
|
|
|
+ Operation := TOperation.Create;
|
|
|
Operation.ShellFolder := ShellFolder;
|
|
|
Operation.hwndOwner := hwndOwner;
|
|
|
Operation.pbcReserved := pbcReserved;
|
|
|
@@ -318,13 +233,12 @@ begin
|
|
|
Operation.pchEaten := 0;
|
|
|
Operation.ppidl := nil;
|
|
|
Operation.dwAttributes := 0;
|
|
|
- Thread.Queue(Operation);
|
|
|
- if WaitForOperation(Timeout) then
|
|
|
+ if WaitForOperation(Operation, ShellFolderParseDisplayNameOperation, Timeout) then
|
|
|
begin
|
|
|
ppidl := Operation.ppidl;
|
|
|
dwAttributes := Operation.dwAttributes;
|
|
|
Result := Operation.ResultHResult;
|
|
|
- Thread.Remove(Operation);
|
|
|
+ Operation.Free;
|
|
|
end
|
|
|
else
|
|
|
begin
|
|
|
@@ -344,19 +258,17 @@ function DestinationListBeginList(
|
|
|
var
|
|
|
Operation: TOperation;
|
|
|
begin
|
|
|
- NeedThread;
|
|
|
- Operation := TOperation.Create(DestinationListBeginListOperation);
|
|
|
+ Operation := TOperation.Create;
|
|
|
Operation.DestinationList := DestinationList;
|
|
|
Operation.pcMaxSlots := pcMaxSlots;
|
|
|
Operation.riid := riid;
|
|
|
Operation.ppv := ppv;
|
|
|
- Thread.Queue(Operation);
|
|
|
- if WaitForOperation(Timeout) then
|
|
|
+ if WaitForOperation(Operation, DestinationListBeginListOperation, Timeout) then
|
|
|
begin
|
|
|
pcMaxSlots := Operation.pcMaxSlots;
|
|
|
ppv := Operation.ppv;
|
|
|
Result := Operation.ResultHResult;
|
|
|
- Thread.Remove(Operation);
|
|
|
+ Operation.Free;
|
|
|
end
|
|
|
else
|
|
|
begin
|
|
|
@@ -365,12 +277,4 @@ begin
|
|
|
end;
|
|
|
end;
|
|
|
|
|
|
-
|
|
|
-initialization
|
|
|
-
|
|
|
-finalization
|
|
|
- if Assigned(Thread) then
|
|
|
- begin
|
|
|
- Thread.Free;
|
|
|
- end;
|
|
|
end.
|