OperationWithTimeout.pas 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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. 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; var 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. apidl := Operation.apidl;
  141. rgfInOut := Operation.rgfInOut;
  142. Result := Operation.ResultHResult;
  143. Operation.Free;
  144. end
  145. else
  146. begin
  147. rgfInOut := 0;
  148. Result := E_FAIL;
  149. end;
  150. end;
  151. procedure SHGetFileInfoOperation(Operation: TOperation);
  152. var
  153. pszPath: LPCWSTR;
  154. begin
  155. if Operation.uFlags and SHGFI_PIDL <> 0 then
  156. begin
  157. pszPath := LPCWSTR(Operation.PIDL);
  158. end
  159. else
  160. begin
  161. pszPath := LPCWSTR(Operation.Path);
  162. end;
  163. Operation.ResultDWordPtr :=
  164. SHGetFileInfo(pszPath, Operation.dwFileAttributes, Operation.psfi, Operation.cbFileInfo, Operation.uFlags);
  165. end;
  166. function SHGetFileInfoWithTimeout(
  167. pszPath: LPCWSTR; dwFileAttributes: DWORD; var psfi: TSHFileInfoW;
  168. cbFileInfo, uFlags: UINT; Timeout: Integer): DWORD_PTR;
  169. var
  170. Operation: TOperation;
  171. begin
  172. Operation := TOperation.Create;
  173. if uFlags and SHGFI_PIDL <> 0 then
  174. begin
  175. Operation.PIDL := PItemIDList(pszPath);
  176. end
  177. else
  178. begin
  179. Operation.Path := pszPath;
  180. end;
  181. Operation.dwFileAttributes := dwFileAttributes;
  182. Operation.psfi := psfi;
  183. Operation.cbFileInfo := cbFileInfo;
  184. Operation.uFlags := uFlags;
  185. if WaitForOperation(Operation, SHGetFileInfoOperation, Timeout) then
  186. begin
  187. psfi := Operation.psfi;
  188. Result := Operation.ResultDWordPtr;
  189. Operation.Free;
  190. end
  191. else
  192. begin
  193. FillChar(psfi, SizeOf(psfi), 0);
  194. Result := 0;
  195. end;
  196. end;
  197. procedure ShellFolderParseDisplayNameOperation(Operation: TOperation);
  198. begin
  199. Operation.ResultHResult :=
  200. Operation.ShellFolder.ParseDisplayName(
  201. Operation.hwndOwner, Operation.pbcReserved, PChar(Operation.DisplayName),
  202. Operation.pchEaten, Operation.ppidl, Operation.dwAttributes);
  203. end;
  204. function ShellFolderParseDisplayNameWithTimeout(
  205. ShellFolder: IShellFolder; hwndOwner: HWND; pbcReserved: Pointer; lpszDisplayName: POLESTR; out pchEaten: ULONG;
  206. out ppidl: PItemIDList; var dwAttributes: ULONG; Timeout: Integer): HResult;
  207. var
  208. Operation: TOperation;
  209. begin
  210. Operation := TOperation.Create;
  211. Operation.ShellFolder := ShellFolder;
  212. Operation.hwndOwner := hwndOwner;
  213. Operation.pbcReserved := pbcReserved;
  214. Operation.DisplayName := lpszDisplayName;
  215. Operation.pchEaten := 0;
  216. Operation.ppidl := nil;
  217. Operation.dwAttributes := 0;
  218. if WaitForOperation(Operation, ShellFolderParseDisplayNameOperation, Timeout) then
  219. begin
  220. ppidl := Operation.ppidl;
  221. dwAttributes := Operation.dwAttributes;
  222. Result := Operation.ResultHResult;
  223. Operation.Free;
  224. end
  225. else
  226. begin
  227. ppidl := nil;
  228. dwAttributes := 0;
  229. Result := E_FAIL;
  230. end;
  231. end;
  232. procedure DestinationListBeginListOperation(Operation: TOperation);
  233. begin
  234. Operation.ResultHResult := Operation.DestinationList.BeginList(Operation.pcMaxSlots, Operation.riid, Operation.ppv);
  235. end;
  236. function DestinationListBeginList(
  237. DestinationList: ICustomDestinationList; var pcMaxSlots: UINT; const riid: TIID; out ppv: Pointer; Timeout: Integer): HRESULT;
  238. var
  239. Operation: TOperation;
  240. begin
  241. Operation := TOperation.Create;
  242. Operation.DestinationList := DestinationList;
  243. Operation.pcMaxSlots := pcMaxSlots;
  244. Operation.riid := riid;
  245. Operation.ppv := ppv;
  246. if WaitForOperation(Operation, DestinationListBeginListOperation, Timeout) then
  247. begin
  248. pcMaxSlots := Operation.pcMaxSlots;
  249. ppv := Operation.ppv;
  250. Result := Operation.ResultHResult;
  251. Operation.Free;
  252. end
  253. else
  254. begin
  255. ppv := nil;
  256. Result := E_FAIL;
  257. end;
  258. end;
  259. end.