OperationWithTimeout.pas 8.0 KB

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