OperationWithTimeout.pas 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. unit OperationWithTimeout;
  2. interface
  3. uses
  4. Winapi.Windows, Winapi.ShlObj, Winapi.ShellAPI, ActiveX;
  5. function ShellFolderGetAttributesOfWithTimeout(
  6. ShellFolder: IShellFolder; cidl: UINT; var apidl: PItemIDList; var rgfInOut: UINT; Timeout: Integer): HResult;
  7. function SHGetFileInfoWithTimeout(
  8. pszPath: LPCWSTR; dwFileAttributes: DWORD; var psfi: TSHFileInfoW;
  9. cbFileInfo, uFlags: UINT; Timeout: Integer): DWORD_PTR;
  10. function ShellFolderParseDisplayNameWithTimeout(
  11. ShellFolder: IShellFolder; hwndOwner: HWND; pbcReserved: Pointer; lpszDisplayName: POLESTR;
  12. out pchEaten: ULONG; out ppidl: PItemIDList; var dwAttributes: ULONG; Timeout: Integer): HResult;
  13. implementation
  14. uses
  15. System.Classes, System.Types, System.SysUtils, System.SyncObjs, System.Contnrs, CompThread;
  16. type
  17. TOperation = class;
  18. TOperationEvent = procedure(Operation: TOperation);
  19. TOperation = class(TObject)
  20. public
  21. OperationEvent: TOperationEvent;
  22. // ShellFolderGetAttributesOfWithTimeout
  23. ShellFolder: IShellFolder;
  24. cidl: UINT;
  25. apidl: PItemIDList;
  26. rgfInOut: UINT;
  27. Timeout: Integer;
  28. ResultHResult: HResult;
  29. // SHGetFileInfoWithTimeout
  30. PIDL: PItemIDList;
  31. Path: string;
  32. dwFileAttributes: DWORD;
  33. psfi: TSHFileInfoW;
  34. cbFileInfo, uFlags: UINT;
  35. ResultDWordPtr: DWORD_PTR;
  36. // ShellFolderParseDisplayNameWithTimeout
  37. // Uses ShellFolder and ResultHResult
  38. hwndOwner: HWND;
  39. pbcReserved: Pointer;
  40. DisplayName: string;
  41. pchEaten: ULONG;
  42. ppidl: PItemIDList;
  43. dwAttributes: ULONG;
  44. constructor Create(AOperationEvent: TOperationEvent);
  45. end;
  46. constructor TOperation.Create(AOperationEvent: TOperationEvent);
  47. begin
  48. OperationEvent := AOperationEvent;
  49. end;
  50. type
  51. TOperationWithTimeoutThread = class(TCompThread)
  52. public
  53. constructor Create;
  54. destructor Destroy; override;
  55. procedure Terminate; override;
  56. procedure Queue(Operation: TOperation);
  57. function WaitForOperation(Milliseconds: Cardinal): Boolean;
  58. procedure Remove(Operation: TOperation);
  59. protected
  60. procedure Execute; override;
  61. private
  62. FCriticalSection: TCriticalSection;
  63. FRequestEvent: THandle;
  64. FResultEvent: THandle;
  65. FQueue: TObjectList;
  66. FResults: TObjectList;
  67. end;
  68. constructor TOperationWithTimeoutThread.Create;
  69. begin
  70. inherited Create(True);
  71. FRequestEvent := CreateEvent(nil, False, False, nil);
  72. FResultEvent := CreateEvent(nil, False, False, nil);
  73. FCriticalSection := TCriticalSection.Create;
  74. FQueue := TObjectList.Create;
  75. FResults := TObjectList.Create;
  76. Resume;
  77. end;
  78. destructor TOperationWithTimeoutThread.Destroy;
  79. begin
  80. inherited;
  81. FQueue.Free;
  82. FResults.Free;
  83. FCriticalSection.Free;
  84. CloseHandle(FRequestEvent);
  85. CloseHandle(FResultEvent);
  86. end;
  87. procedure TOperationWithTimeoutThread.Terminate;
  88. begin
  89. inherited;
  90. SetEvent(FRequestEvent);
  91. end;
  92. procedure TOperationWithTimeoutThread.Execute;
  93. var
  94. Operation: TOperation;
  95. begin
  96. // Needed for various API, particularly:
  97. // - SHGetFileInfo fails to return icon index on some systems;
  98. // - ICustomDestinationList.BeginList returns invalid "removed" array.
  99. CoInitialize(nil);
  100. while WaitForSingleObject(FRequestEvent, INFINITE) = WAIT_OBJECT_0 do
  101. begin
  102. if Terminated then
  103. begin
  104. break;
  105. end
  106. else
  107. begin
  108. FCriticalSection.Enter;
  109. try
  110. Operation := TOperation(FQueue[0]);
  111. FQueue.Extract(Operation);
  112. finally
  113. FCriticalSection.Leave;
  114. end;
  115. Operation.OperationEvent(Operation);
  116. FResults.Add(Operation);
  117. SetEvent(FResultEvent);
  118. end;
  119. end;
  120. end;
  121. procedure TOperationWithTimeoutThread.Queue(Operation: TOperation);
  122. begin
  123. FCriticalSection.Enter;
  124. try
  125. FQueue.Add(Operation);
  126. finally
  127. FCriticalSection.Leave;
  128. end;
  129. SetEvent(FRequestEvent);
  130. end;
  131. function TOperationWithTimeoutThread.WaitForOperation(Milliseconds: Cardinal): Boolean;
  132. begin
  133. ResetEvent(FResultEvent);
  134. Result := (WaitForSingleObject(FResultEvent, Milliseconds) = WAIT_OBJECT_0);
  135. end;
  136. procedure TOperationWithTimeoutThread.Remove(Operation: TOperation);
  137. begin
  138. FCriticalSection.Enter;
  139. try
  140. FResults.Remove(Operation);
  141. finally
  142. FCriticalSection.Leave;
  143. end;
  144. end;
  145. var
  146. Thread: TOperationWithTimeoutThread = nil;
  147. procedure NeedThread;
  148. begin
  149. if not Assigned(Thread) then
  150. begin
  151. Thread := TOperationWithTimeoutThread.Create;
  152. end;
  153. end;
  154. function WaitForOperation(Milliseconds: Cardinal): Boolean;
  155. begin
  156. Result := Thread.WaitForOperation(Milliseconds);
  157. if not Result then
  158. begin
  159. // There's a chance for memory leak, if thread is terminated
  160. // between WaitFor() and this line
  161. Thread.FreeOnTerminate := True;
  162. Thread.Terminate;
  163. Thread := nil;
  164. end;
  165. end;
  166. procedure ShellFolderGetAttributesOfOperation(Operation: TOperation);
  167. var
  168. ErrorMode: Word;
  169. begin
  170. ErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS or SEM_NOOPENFILEERRORBOX);
  171. try
  172. Operation.ResultHResult := Operation.ShellFolder.GetAttributesOf(Operation.cidl, Operation.apidl, Operation.rgfInOut);
  173. except
  174. Operation.ResultHResult := E_FAIL;
  175. end;
  176. SetErrorMode(ErrorMode);
  177. end;
  178. function ShellFolderGetAttributesOfWithTimeout(
  179. ShellFolder: IShellFolder; cidl: UINT; var apidl: PItemIDList; var rgfInOut: UINT; Timeout: Integer): HResult;
  180. var
  181. Operation: TOperation;
  182. begin
  183. NeedThread;
  184. Operation := TOperation.Create(ShellFolderGetAttributesOfOperation);
  185. Operation.ShellFolder := ShellFolder;
  186. Operation.cidl := cidl;
  187. Operation.apidl := apidl;
  188. Operation.rgfInOut := rgfInOut;
  189. Thread.Queue(Operation);
  190. if WaitForOperation(Timeout) then
  191. begin
  192. apidl := Operation.apidl;
  193. rgfInOut := Operation.rgfInOut;
  194. Result := Operation.ResultHResult;
  195. Thread.Remove(Operation);
  196. end
  197. else
  198. begin
  199. rgfInOut := 0;
  200. Result := E_FAIL;
  201. end;
  202. end;
  203. procedure SHGetFileInfoOperation(Operation: TOperation);
  204. var
  205. pszPath: LPCWSTR;
  206. begin
  207. if Operation.uFlags and SHGFI_PIDL <> 0 then
  208. begin
  209. pszPath := LPCWSTR(Operation.PIDL);
  210. end
  211. else
  212. begin
  213. pszPath := LPCWSTR(Operation.Path);
  214. end;
  215. Operation.ResultDWordPtr :=
  216. SHGetFileInfo(pszPath, Operation.dwFileAttributes, Operation.psfi, Operation.cbFileInfo, Operation.uFlags);
  217. end;
  218. function SHGetFileInfoWithTimeout(
  219. pszPath: LPCWSTR; dwFileAttributes: DWORD; var psfi: TSHFileInfoW;
  220. cbFileInfo, uFlags: UINT; Timeout: Integer): DWORD_PTR;
  221. var
  222. Operation: TOperation;
  223. begin
  224. NeedThread;
  225. Operation := TOperation.Create(SHGetFileInfoOperation);
  226. if uFlags and SHGFI_PIDL <> 0 then
  227. begin
  228. Operation.PIDL := PItemIDList(pszPath);
  229. end
  230. else
  231. begin
  232. Operation.Path := pszPath;
  233. end;
  234. Operation.dwFileAttributes := dwFileAttributes;
  235. Operation.psfi := psfi;
  236. Operation.cbFileInfo := cbFileInfo;
  237. Operation.uFlags := uFlags;
  238. Thread.Queue(Operation);
  239. if WaitForOperation(Timeout) then
  240. begin
  241. psfi := Operation.psfi;
  242. Result := Operation.ResultDWordPtr;
  243. Thread.Remove(Operation);
  244. end
  245. else
  246. begin
  247. FillChar(psfi, SizeOf(psfi), 0);
  248. Result := 0;
  249. end;
  250. end;
  251. procedure ShellFolderParseDisplayNameOperation(Operation: TOperation);
  252. begin
  253. Operation.ResultHResult :=
  254. Operation.ShellFolder.ParseDisplayName(
  255. Operation.hwndOwner, Operation.pbcReserved, PChar(Operation.DisplayName),
  256. Operation.pchEaten, Operation.ppidl, Operation.dwAttributes);
  257. end;
  258. function ShellFolderParseDisplayNameWithTimeout(
  259. ShellFolder: IShellFolder; hwndOwner: HWND; pbcReserved: Pointer; lpszDisplayName: POLESTR; out pchEaten: ULONG;
  260. out ppidl: PItemIDList; var dwAttributes: ULONG; Timeout: Integer): HResult;
  261. var
  262. Operation: TOperation;
  263. begin
  264. NeedThread;
  265. Operation := TOperation.Create(ShellFolderParseDisplayNameOperation);
  266. Operation.ShellFolder := ShellFolder;
  267. Operation.hwndOwner := hwndOwner;
  268. Operation.pbcReserved := pbcReserved;
  269. Operation.DisplayName := lpszDisplayName;
  270. Operation.pchEaten := 0;
  271. Operation.ppidl := nil;
  272. Operation.dwAttributes := 0;
  273. Thread.Queue(Operation);
  274. if WaitForOperation(Timeout) then
  275. begin
  276. ppidl := Operation.ppidl;
  277. dwAttributes := Operation.dwAttributes;
  278. Result := Operation.ResultHResult;
  279. Thread.Remove(Operation);
  280. end
  281. else
  282. begin
  283. ppidl := nil;
  284. dwAttributes := 0;
  285. Result := E_FAIL;
  286. end;
  287. end;
  288. initialization
  289. finalization
  290. if Assigned(Thread) then
  291. begin
  292. Thread.Free;
  293. end;
  294. end.