OperationWithTimeout.pas 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. unit OperationWithTimeout;
  2. interface
  3. uses
  4. Winapi.Windows, Winapi.ShlObj, Winapi.ShellAPI, ActiveX;
  5. function ShellFolderGetAttributesOfWithTimeout(
  6. ShellFolder: IShellFolder; cidl: UINT; 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. function DestinationListBeginList(
  14. DestinationList: ICustomDestinationList; var pcMaxSlots: UINT; const riid: TIID; out ppv: Pointer; Timeout: Integer): HRESULT;
  15. var
  16. TimeoutShellOperations: Boolean = True;
  17. implementation
  18. uses
  19. System.Classes, System.Types, System.SysUtils, CompThread;
  20. type
  21. TOperation = class;
  22. TOperationEvent = procedure(Operation: TOperation);
  23. TOperation = class(TObject)
  24. public
  25. // ShellFolderGetAttributesOfWithTimeout
  26. ShellFolder: IShellFolder;
  27. cidl: UINT;
  28. apidl: PItemIDList;
  29. rgfInOut: UINT;
  30. Timeout: Integer;
  31. ResultHResult: HResult;
  32. // SHGetFileInfoWithTimeout
  33. PIDL: PItemIDList;
  34. Path: string;
  35. dwFileAttributes: DWORD;
  36. psfi: TSHFileInfoW;
  37. cbFileInfo, uFlags: UINT;
  38. ResultDWordPtr: DWORD_PTR;
  39. // ShellFolderParseDisplayNameWithTimeout
  40. // Uses ShellFolder and ResultHResult
  41. hwndOwner: HWND;
  42. pbcReserved: Pointer;
  43. DisplayName: string;
  44. pchEaten: ULONG;
  45. ppidl: PItemIDList;
  46. dwAttributes: ULONG;
  47. // DestinationListBeginList uses ResultHResult
  48. DestinationList: ICustomDestinationList;
  49. pcMaxSlots: UINT;
  50. riid: TIID;
  51. ppv: Pointer;
  52. end;
  53. type
  54. TOperationWithTimeoutThread = class(TCompThread)
  55. public
  56. constructor Create(Operation: TOperation; OperationEvent: TOperationEvent);
  57. protected
  58. procedure Execute; override;
  59. private
  60. FOperation: TOperation;
  61. FOperationEvent: TOperationEvent;
  62. end;
  63. constructor TOperationWithTimeoutThread.Create(Operation: TOperation; OperationEvent: TOperationEvent);
  64. begin
  65. inherited Create(True);
  66. FOperation := Operation;
  67. FOperationEvent := OperationEvent;
  68. end;
  69. procedure TOperationWithTimeoutThread.Execute;
  70. begin
  71. // Needed for various API, particularly:
  72. // - SHGetFileInfo fails to return icon index on some systems;
  73. // - ICustomDestinationList.BeginList returns invalid "removed" array.
  74. CoInitialize(nil);
  75. FOperationEvent(FOperation);
  76. end;
  77. function WaitForOperation(
  78. Operation: TOperation; OperationEvent: TOperationEvent; Milliseconds: Cardinal): Boolean;
  79. {$IFNDEF IDE}
  80. var
  81. Thread: TOperationWithTimeoutThread;
  82. {$ENDIF}
  83. begin
  84. // When running from IDE, it triggers starting/exiting the thread taking ages.
  85. // So in IDE we revert to single-thread approach.
  86. {$IFNDEF IDE}
  87. if not TimeoutShellOperations then
  88. {$ENDIF}
  89. begin
  90. OperationEvent(Operation);
  91. Result := True;
  92. end
  93. {$IFNDEF IDE}
  94. else
  95. begin
  96. // Have to start new thread for each request. When shared thread is used, it eventually hangs.
  97. // Most probably do to the fact that we violate COM threading model.
  98. // So using a new thread for each request, is only a hack that happens to work by pure luck.
  99. // We may want to use shared thread at least for COM-free operations, like SHGetFileInfo.
  100. Thread := TOperationWithTimeoutThread.Create(Operation, OperationEvent);
  101. Thread.Resume;
  102. Result := Thread.WaitFor(MSecsPerSec);
  103. if Result then
  104. begin
  105. Thread.Free;
  106. end
  107. else
  108. begin
  109. // There's a chance for memory leak, if thread is terminated
  110. // between WaitFor() and this line
  111. Thread.FreeOnTerminate := True;
  112. end;
  113. end;
  114. {$ENDIF}
  115. end;
  116. procedure ShellFolderGetAttributesOfOperation(Operation: TOperation);
  117. var
  118. ErrorMode: Word;
  119. begin
  120. ErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS or SEM_NOOPENFILEERRORBOX);
  121. try
  122. Operation.ResultHResult := Operation.ShellFolder.GetAttributesOf(Operation.cidl, Operation.apidl, Operation.rgfInOut);
  123. except
  124. Operation.ResultHResult := E_FAIL;
  125. end;
  126. SetErrorMode(ErrorMode);
  127. end;
  128. function ShellFolderGetAttributesOfWithTimeout(
  129. ShellFolder: IShellFolder; cidl: UINT; apidl: PItemIDList; var rgfInOut: UINT; Timeout: Integer): HResult;
  130. var
  131. Operation: TOperation;
  132. begin
  133. Operation := TOperation.Create;
  134. Operation.ShellFolder := ShellFolder;
  135. Operation.cidl := cidl;
  136. Operation.apidl := apidl;
  137. Operation.rgfInOut := rgfInOut;
  138. if WaitForOperation(Operation, ShellFolderGetAttributesOfOperation, Timeout) then
  139. begin
  140. rgfInOut := Operation.rgfInOut;
  141. Result := Operation.ResultHResult;
  142. Operation.Free;
  143. end
  144. else
  145. begin
  146. rgfInOut := 0;
  147. Result := E_FAIL;
  148. end;
  149. end;
  150. procedure SHGetFileInfoOperation(Operation: TOperation);
  151. var
  152. pszPath: LPCWSTR;
  153. begin
  154. if Operation.uFlags and SHGFI_PIDL <> 0 then
  155. begin
  156. pszPath := LPCWSTR(Operation.PIDL);
  157. end
  158. else
  159. begin
  160. pszPath := LPCWSTR(Operation.Path);
  161. end;
  162. Operation.ResultDWordPtr :=
  163. SHGetFileInfo(pszPath, Operation.dwFileAttributes, Operation.psfi, Operation.cbFileInfo, Operation.uFlags);
  164. end;
  165. function SHGetFileInfoWithTimeout(
  166. pszPath: LPCWSTR; dwFileAttributes: DWORD; var psfi: TSHFileInfoW;
  167. cbFileInfo, uFlags: UINT; Timeout: Integer): DWORD_PTR;
  168. var
  169. Operation: TOperation;
  170. begin
  171. Operation := TOperation.Create;
  172. if uFlags and SHGFI_PIDL <> 0 then
  173. begin
  174. Operation.PIDL := PItemIDList(pszPath);
  175. end
  176. else
  177. begin
  178. Operation.Path := pszPath;
  179. end;
  180. Operation.dwFileAttributes := dwFileAttributes;
  181. Operation.psfi := psfi;
  182. Operation.cbFileInfo := cbFileInfo;
  183. Operation.uFlags := uFlags;
  184. if WaitForOperation(Operation, SHGetFileInfoOperation, Timeout) then
  185. begin
  186. psfi := Operation.psfi;
  187. Result := Operation.ResultDWordPtr;
  188. Operation.Free;
  189. end
  190. else
  191. begin
  192. FillChar(psfi, SizeOf(psfi), 0);
  193. Result := 0;
  194. end;
  195. end;
  196. procedure ShellFolderParseDisplayNameOperation(Operation: TOperation);
  197. begin
  198. Operation.ResultHResult :=
  199. Operation.ShellFolder.ParseDisplayName(
  200. Operation.hwndOwner, Operation.pbcReserved, PChar(Operation.DisplayName),
  201. Operation.pchEaten, Operation.ppidl, Operation.dwAttributes);
  202. end;
  203. function ShellFolderParseDisplayNameWithTimeout(
  204. ShellFolder: IShellFolder; hwndOwner: HWND; pbcReserved: Pointer; lpszDisplayName: POLESTR; out pchEaten: ULONG;
  205. out ppidl: PItemIDList; var dwAttributes: ULONG; Timeout: Integer): HResult;
  206. var
  207. Operation: TOperation;
  208. begin
  209. Operation := TOperation.Create;
  210. Operation.ShellFolder := ShellFolder;
  211. Operation.hwndOwner := hwndOwner;
  212. Operation.pbcReserved := pbcReserved;
  213. Operation.DisplayName := lpszDisplayName;
  214. Operation.pchEaten := 0;
  215. Operation.ppidl := nil;
  216. Operation.dwAttributes := 0;
  217. if WaitForOperation(Operation, ShellFolderParseDisplayNameOperation, Timeout) then
  218. begin
  219. ppidl := Operation.ppidl;
  220. dwAttributes := Operation.dwAttributes;
  221. Result := Operation.ResultHResult;
  222. Operation.Free;
  223. end
  224. else
  225. begin
  226. ppidl := nil;
  227. dwAttributes := 0;
  228. Result := E_FAIL;
  229. end;
  230. end;
  231. procedure DestinationListBeginListOperation(Operation: TOperation);
  232. begin
  233. Operation.ResultHResult := Operation.DestinationList.BeginList(Operation.pcMaxSlots, Operation.riid, Operation.ppv);
  234. end;
  235. function DestinationListBeginList(
  236. DestinationList: ICustomDestinationList; var pcMaxSlots: UINT; const riid: TIID; out ppv: Pointer; Timeout: Integer): HRESULT;
  237. var
  238. Operation: TOperation;
  239. begin
  240. Operation := TOperation.Create;
  241. Operation.DestinationList := DestinationList;
  242. Operation.pcMaxSlots := pcMaxSlots;
  243. Operation.riid := riid;
  244. Operation.ppv := ppv;
  245. if WaitForOperation(Operation, DestinationListBeginListOperation, Timeout) then
  246. begin
  247. pcMaxSlots := Operation.pcMaxSlots;
  248. ppv := Operation.ppv;
  249. Result := Operation.ResultHResult;
  250. Operation.Free;
  251. end
  252. else
  253. begin
  254. ppv := nil;
  255. Result := E_FAIL;
  256. end;
  257. end;
  258. end.