OperationWithTimeout.pas 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  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. CoInitialize(nil);
  97. while WaitForSingleObject(FRequestEvent, INFINITE) = WAIT_OBJECT_0 do
  98. begin
  99. if Terminated then
  100. begin
  101. break;
  102. end
  103. else
  104. begin
  105. FCriticalSection.Enter;
  106. try
  107. Operation := TOperation(FQueue[0]);
  108. FQueue.Extract(Operation);
  109. finally
  110. FCriticalSection.Leave;
  111. end;
  112. Operation.OperationEvent(Operation);
  113. FResults.Add(Operation);
  114. SetEvent(FResultEvent);
  115. end;
  116. end;
  117. end;
  118. procedure TOperationWithTimeoutThread.Queue(Operation: TOperation);
  119. begin
  120. FCriticalSection.Enter;
  121. try
  122. FQueue.Add(Operation);
  123. finally
  124. FCriticalSection.Leave;
  125. end;
  126. SetEvent(FRequestEvent);
  127. end;
  128. function TOperationWithTimeoutThread.WaitForOperation(Milliseconds: Cardinal): Boolean;
  129. begin
  130. ResetEvent(FResultEvent);
  131. Result := (WaitForSingleObject(FResultEvent, Milliseconds) = WAIT_OBJECT_0);
  132. end;
  133. procedure TOperationWithTimeoutThread.Remove(Operation: TOperation);
  134. begin
  135. FCriticalSection.Enter;
  136. try
  137. FResults.Remove(Operation);
  138. finally
  139. FCriticalSection.Leave;
  140. end;
  141. end;
  142. var
  143. Thread: TOperationWithTimeoutThread = nil;
  144. procedure NeedThread;
  145. begin
  146. if not Assigned(Thread) then
  147. begin
  148. Thread := TOperationWithTimeoutThread.Create;
  149. end;
  150. end;
  151. function WaitForOperation(Milliseconds: Cardinal): Boolean;
  152. begin
  153. Result := Thread.WaitForOperation(Milliseconds);
  154. if not Result then
  155. begin
  156. // There's a chance for memory leak, if thread is terminated
  157. // between WaitFor() and this line
  158. Thread.FreeOnTerminate := True;
  159. Thread.Terminate;
  160. Thread := nil;
  161. end;
  162. end;
  163. procedure ShellFolderGetAttributesOfOperation(Operation: TOperation);
  164. var
  165. ErrorMode: Word;
  166. begin
  167. ErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS or SEM_NOOPENFILEERRORBOX);
  168. try
  169. Operation.ResultHResult := Operation.ShellFolder.GetAttributesOf(Operation.cidl, Operation.apidl, Operation.rgfInOut);
  170. except
  171. Operation.ResultHResult := E_FAIL;
  172. end;
  173. SetErrorMode(ErrorMode);
  174. end;
  175. function ShellFolderGetAttributesOfWithTimeout(
  176. ShellFolder: IShellFolder; cidl: UINT; var apidl: PItemIDList; var rgfInOut: UINT; Timeout: Integer): HResult;
  177. var
  178. Operation: TOperation;
  179. begin
  180. NeedThread;
  181. Operation := TOperation.Create(ShellFolderGetAttributesOfOperation);
  182. Operation.ShellFolder := ShellFolder;
  183. Operation.cidl := cidl;
  184. Operation.apidl := apidl;
  185. Operation.rgfInOut := rgfInOut;
  186. Thread.Queue(Operation);
  187. if WaitForOperation(Timeout) then
  188. begin
  189. apidl := Operation.apidl;
  190. rgfInOut := Operation.rgfInOut;
  191. Result := Operation.ResultHResult;
  192. Thread.Remove(Operation);
  193. end
  194. else
  195. begin
  196. rgfInOut := 0;
  197. Result := E_FAIL;
  198. end;
  199. end;
  200. procedure SHGetFileInfoOperation(Operation: TOperation);
  201. var
  202. pszPath: LPCWSTR;
  203. begin
  204. if Operation.uFlags and SHGFI_PIDL <> 0 then
  205. begin
  206. pszPath := LPCWSTR(Operation.PIDL);
  207. end
  208. else
  209. begin
  210. pszPath := LPCWSTR(Operation.Path);
  211. end;
  212. Operation.ResultDWordPtr :=
  213. SHGetFileInfo(pszPath, Operation.dwFileAttributes, Operation.psfi, Operation.cbFileInfo, Operation.uFlags);
  214. end;
  215. function SHGetFileInfoWithTimeout(
  216. pszPath: LPCWSTR; dwFileAttributes: DWORD; var psfi: TSHFileInfoW;
  217. cbFileInfo, uFlags: UINT; Timeout: Integer): DWORD_PTR;
  218. var
  219. Operation: TOperation;
  220. begin
  221. NeedThread;
  222. Operation := TOperation.Create(SHGetFileInfoOperation);
  223. if uFlags and SHGFI_PIDL <> 0 then
  224. begin
  225. Operation.PIDL := PItemIDList(pszPath);
  226. end
  227. else
  228. begin
  229. Operation.Path := pszPath;
  230. end;
  231. Operation.dwFileAttributes := dwFileAttributes;
  232. Operation.psfi := psfi;
  233. Operation.cbFileInfo := cbFileInfo;
  234. Operation.uFlags := uFlags;
  235. Thread.Queue(Operation);
  236. if WaitForOperation(Timeout) then
  237. begin
  238. psfi := Operation.psfi;
  239. Result := Operation.ResultDWordPtr;
  240. Thread.Remove(Operation);
  241. end
  242. else
  243. begin
  244. FillChar(psfi, SizeOf(psfi), 0);
  245. Result := 0;
  246. end;
  247. end;
  248. procedure ShellFolderParseDisplayNameOperation(Operation: TOperation);
  249. begin
  250. Operation.ResultHResult :=
  251. Operation.ShellFolder.ParseDisplayName(
  252. Operation.hwndOwner, Operation.pbcReserved, PChar(Operation.DisplayName),
  253. Operation.pchEaten, Operation.ppidl, Operation.dwAttributes);
  254. end;
  255. function ShellFolderParseDisplayNameWithTimeout(
  256. ShellFolder: IShellFolder; hwndOwner: HWND; pbcReserved: Pointer; lpszDisplayName: POLESTR; out pchEaten: ULONG;
  257. out ppidl: PItemIDList; var dwAttributes: ULONG; Timeout: Integer): HResult;
  258. var
  259. Operation: TOperation;
  260. begin
  261. NeedThread;
  262. Operation := TOperation.Create(ShellFolderParseDisplayNameOperation);
  263. Operation.ShellFolder := ShellFolder;
  264. Operation.hwndOwner := hwndOwner;
  265. Operation.pbcReserved := pbcReserved;
  266. Operation.DisplayName := lpszDisplayName;
  267. Operation.pchEaten := 0;
  268. Operation.ppidl := nil;
  269. Operation.dwAttributes := 0;
  270. Thread.Queue(Operation);
  271. if WaitForOperation(Timeout) then
  272. begin
  273. ppidl := Operation.ppidl;
  274. dwAttributes := Operation.dwAttributes;
  275. Result := Operation.ResultHResult;
  276. Thread.Remove(Operation);
  277. end
  278. else
  279. begin
  280. ppidl := nil;
  281. dwAttributes := 0;
  282. Result := E_FAIL;
  283. end;
  284. end;
  285. initialization
  286. finalization
  287. if Assigned(Thread) then
  288. begin
  289. Thread.Free;
  290. end;
  291. end.