IEDriveInfo.pas 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248
  1. unit IEDriveInfo;
  2. {==================================================================
  3. Component TDriveInfo / Version 2.6 / January 2000
  4. ====================================================
  5. Description:
  6. ============
  7. Central drive management class. Provides information about all
  8. installed drives, for example wether drive is valid, disk is inserted
  9. displayname or volume size.
  10. Author:
  11. =======
  12. (c) Ingo Eckel 1999
  13. Sodener Weg 38
  14. 65812 Bad Soden
  15. Germany
  16. For detailed documentation and history see the documentation in TDriveInfo.htm.
  17. {==================================================================}
  18. {Required compiler options:}
  19. {$A+,B-,X+,H+,P+}
  20. {$WARN SYMBOL_PLATFORM OFF}
  21. interface
  22. uses
  23. Windows, Registry, SysUtils, Classes, ComCtrls, ShellApi, ShlObj, CommCtrl, Forms,
  24. BaseUtils, System.Generics.Collections, Vcl.Graphics, Winapi.Messages;
  25. const
  26. {Flags used by TDriveInfo.ReadDriveStatus and TDriveView.RefreshRootNodes:}
  27. dsValid = 0; {checks only whether drive is still valid}
  28. dsImageIndex = 1; {Fetch imageindex, if not allready fetched}
  29. dsSize = 2; {Fetch disk size and serialnumber}
  30. dsDisplayName = 4; {Fetch drives displayname}
  31. dsSynchronous = dsImageIndex or dsDisplayName;
  32. dsAll = dsSynchronous or dsSize;
  33. FirstDrive = 'A';
  34. SystemDrive = 'C';
  35. LastDrive = 'Z';
  36. FirstSpecialFolder = CSIDL_DESKTOP;
  37. LastSpecialFolder = CSIDL_PRINTHOOD;
  38. WM_USER_SHCHANGENOTIFY = WM_USER + $2000 + 13;
  39. WM_DRIVEINFO_PROCESS = WM_USER + $2000 + 18;
  40. type
  41. TDriveInfoRec = class
  42. PIDL : PItemIDList; {Fully qualyfied PIDL}
  43. Init : Boolean; {Drivestatus was updated once}
  44. Valid : Boolean; {Drivestatus is valid}
  45. ValidButHiddenByDrivePolicy: Boolean;
  46. DriveReady : Boolean; {Drive is ready}
  47. DriveType : Integer; {DRIVE_REMOVABLE, DRIVE_FIXED, DRIVE_CDROM, DRIVE_RAMDISK, DRIVE_REMOTE}
  48. DisplayName : string; {Windows displayname}
  49. PrettyName : string; {Prettyfied displayname}
  50. DriveSerial : DWORD; {Serial number of the drive}
  51. Size : Int64; {Drivesize}
  52. ImageIndex : Integer; {Drive imageIndex}
  53. DriveHandle: THandle;
  54. NotificationHandle: HDEVNOTIFY;
  55. SubscribeDriveNotifications: Boolean;
  56. end;
  57. TRealDrive = char;
  58. TSpecialFolder = FirstSpecialFolder..LastSpecialFolder;
  59. PSpecialFolderRec = ^TSpecialFolderRec;
  60. TSpecialFolderRec = record
  61. Valid: Boolean;
  62. Location: string;
  63. DisplayName: string;
  64. ImageIndex: Integer;
  65. PIDL: PItemIDList;
  66. end;
  67. TDriveNotification = (dnRefresh, dnRemoving);
  68. TDriveNotificationEvent = procedure(Notification: TDriveNotification; Drive: string) of object;
  69. TDriveInfo = class(TObject)
  70. private
  71. FData: TObjectDictionary<string, TDriveInfoRec>;
  72. FNoDrives: DWORD;
  73. FNoViewOnDrive: DWORD;
  74. FDesktop: IShellFolder;
  75. FFolders: array[TSpecialFolder] of TSpecialFolderRec;
  76. FHonorDrivePolicy: Integer;
  77. FUseABDrives: Boolean;
  78. FLoaded: Boolean;
  79. FHandlers: TList<TDriveNotificationEvent>;
  80. FChangeNotify: ULONG;
  81. function GetFolder(Folder: TSpecialFolder): PSpecialFolderRec;
  82. procedure ReadDriveBasicStatus(Drive: string);
  83. procedure ResetDrive(Drive: string);
  84. procedure SetHonorDrivePolicy(Value: Integer);
  85. function GetFirstFixedDrive: Char;
  86. procedure Load;
  87. function AddDrive(Drive: string): TDriveInfoRec;
  88. function GetDriveBitMask(Drive: string): Integer;
  89. function DoAnyValidPath(DriveType: Integer; CanBeHidden: Boolean; var Path: string): Boolean;
  90. function ReadDriveMask(Reg: TRegistry; ValueName: string): DWORD;
  91. procedure ScheduleDriveRefresh;
  92. procedure CancelDriveRefresh;
  93. procedure InternalWndProc(var Msg: TMessage);
  94. procedure InvokeHandlers(DriveNotification: TDriveNotification; Drive: string);
  95. procedure UpdateDriveNotifications(Drive: string);
  96. procedure UpdateDrivesNotifications;
  97. procedure ProcessThreadResults;
  98. constructor Create;
  99. procedure ReadAsynchronous;
  100. procedure DoReadDriveStatus(Drive: string; Flags: Integer);
  101. procedure DriveRemoving(Drive: string);
  102. public
  103. function Get(Drive: string): TDriveInfoRec;
  104. property SpecialFolder[Folder: TSpecialFolder]: PSpecialFolderRec read GetFolder;
  105. procedure NeedData;
  106. function AnyValidPath: string;
  107. function GetDriveKey(Path: string): string;
  108. function GetDriveRoot(Drive: string): string;
  109. function IsRealDrive(Drive: string): Boolean;
  110. function IsFixedDrive(Drive: string): Boolean;
  111. function GetImageIndex(Drive: string): Integer;
  112. function GetSimpleName(Drive: string): string;
  113. function GetDisplayName(Drive: string): string;
  114. function GetPrettyName(Drive: string): string;
  115. procedure ReadDriveStatus(Drive: string; Flags: Integer);
  116. procedure OverrideDrivePolicy(Drive: string);
  117. property HonorDrivePolicy: Integer read FHonorDrivePolicy write SetHonorDrivePolicy;
  118. property FirstFixedDrive: Char read GetFirstFixedDrive;
  119. property UseABDrives: Boolean read FUseABDrives write FUseABDrives;
  120. destructor Destroy; override;
  121. procedure AddHandler(Handler: TDriveNotificationEvent);
  122. procedure RemoveHandler(Handler: TDriveNotificationEvent);
  123. procedure DriveRefresh;
  124. procedure SubscribeDriveNotifications(Drive: string);
  125. end;
  126. function GetShellFileName(const Name: string): string; overload;
  127. function GetShellFileName(PIDL: PItemIDList): string; overload;
  128. function GetNetWorkName(Drive: string): string;
  129. function GetNetWorkConnected(Drive: string): Boolean;
  130. function IsRootPath(Path: string): Boolean;
  131. function GetThumbnail(Path: string; Size: TSize): TBitmap;
  132. {Central drive information object instance of TDriveInfo}
  133. var
  134. DriveInfo : TDriveInfo;
  135. resourceString
  136. ErrorInvalidDrive = '%s is a invalid drive letter.';
  137. implementation
  138. uses
  139. Math, PIDL, OperationWithTimeout, PasTools, CompThread;
  140. type
  141. PRGBQuadArray = ^TRGBQuadArray; // From graphics.pas
  142. TRGBQuadArray = array[Byte] of TRGBQuad; // From graphics.pas
  143. // Globals so that we do not have to fear that thread run after DriveInfo is released
  144. var
  145. InternalWindowHandle: HWND;
  146. ThreadLock: TRTLCriticalSection;
  147. ReadyDrives: string;
  148. type
  149. TDriveInfoThread = class(TCompThread)
  150. public
  151. constructor Create(Drives: string);
  152. protected
  153. procedure Execute; override;
  154. private
  155. FDrives: string;
  156. end;
  157. constructor TDriveInfoThread.Create(Drives: string);
  158. begin
  159. inherited Create(True);
  160. FDrives := Drives;
  161. FreeOnTerminate := True;
  162. Resume;
  163. end;
  164. procedure TDriveInfoThread.Execute;
  165. var
  166. I: Integer;
  167. FreeSpace, Size: Int64;
  168. DriveRoot: string;
  169. Drive: Char;
  170. begin
  171. if Length(FDrives) = 1 then
  172. begin
  173. Drive := FDrives[1];
  174. DriveRoot := DriveInfo.GetDriveRoot(Drive);
  175. if GetDiskFreeSpaceEx(PChar(DriveRoot), FreeSpace, Size, nil) then
  176. begin
  177. EnterCriticalSection(ThreadLock);
  178. ReadyDrives := ReadyDrives + Drive;
  179. LeaveCriticalSection(ThreadLock);
  180. end;
  181. end
  182. else
  183. begin
  184. for I := 1 to Length(FDrives) do
  185. begin
  186. TDriveInfoThread.Create(FDrives[I]);
  187. Sleep(100);
  188. end;
  189. end;
  190. end;
  191. constructor TDriveInfo.Create;
  192. begin
  193. inherited;
  194. FHonorDrivePolicy := 1;
  195. FUseABDrives := True;
  196. FLoaded := False;
  197. FData := TObjectDictionary<string, TDriveInfoRec>.Create([doOwnsValues]);
  198. FHandlers := TList<TDriveNotificationEvent>.Create;
  199. FChangeNotify := 0;
  200. end; {TDriveInfo.Create}
  201. destructor TDriveInfo.Destroy;
  202. begin
  203. Assert(FHandlers.Count = 0);
  204. FHandlers.Free;
  205. FData.Free;
  206. inherited;
  207. end; {TDriveInfo.Destroy}
  208. procedure TDriveInfo.NeedData;
  209. begin
  210. if not FLoaded then
  211. begin
  212. Load;
  213. FLoaded := True;
  214. end;
  215. ProcessThreadResults;
  216. end;
  217. procedure TDriveInfo.ProcessThreadResults;
  218. var
  219. I: Integer;
  220. Drive: Char;
  221. begin
  222. EnterCriticalSection(ThreadLock);
  223. try
  224. for I := 1 to Length(ReadyDrives) do
  225. begin
  226. Drive := ReadyDrives[I];
  227. Assert(FData.ContainsKey(Drive));
  228. FData[Drive].DriveReady := True;
  229. UpdateDriveNotifications(Drive);
  230. AppLog(Format('Drive "%s" is ready', [Drive]))
  231. end;
  232. ReadyDrives := '';
  233. finally
  234. LeaveCriticalSection(ThreadLock);
  235. end;
  236. end;
  237. function TDriveInfo.DoAnyValidPath(DriveType: Integer; CanBeHidden: Boolean; var Path: string): Boolean;
  238. var
  239. Drive: TRealDrive;
  240. DriveInfoRec: TDriveInfoRec;
  241. begin
  242. for Drive := SystemDrive to LastDrive do
  243. begin
  244. DriveInfoRec := Get(Drive);
  245. if (DriveInfoRec.Valid or
  246. (CanBeHidden and DriveInfoRec.ValidButHiddenByDrivePolicy)) and
  247. (DriveInfoRec.DriveType = DriveType) and
  248. DirectoryExists(ApiPath(GetDriveRoot(Drive))) then
  249. begin
  250. Result := True;
  251. Path := GetDriveRoot(Drive);
  252. Exit;
  253. end;
  254. end;
  255. Result := False;
  256. end;
  257. function TDriveInfo.AnyValidPath: string;
  258. begin
  259. if (not DoAnyValidPath(DRIVE_FIXED, False, Result)) and
  260. (not DoAnyValidPath(DRIVE_FIXED, True, Result)) and
  261. (not DoAnyValidPath(DRIVE_REMOTE, False, Result)) then
  262. begin
  263. raise Exception.Create(SNoValidPath);
  264. end;
  265. end;
  266. function TDriveInfo.IsRealDrive(Drive: string): Boolean;
  267. begin
  268. Result := (Length(Drive) = 1);
  269. Assert((not Result) or ((Drive[1] >= FirstDrive) and (Drive[1] <= LastDrive)));
  270. end;
  271. function TDriveInfo.IsFixedDrive(Drive: string): Boolean;
  272. begin
  273. Result := True;
  274. if IsRealDrive(Drive) and (Drive[1] < FirstFixedDrive) then Result := False;
  275. end;
  276. function TDriveInfo.GetDriveKey(Path: string): string;
  277. begin
  278. Result := ExtractFileDrive(Path);
  279. if (Length(Result) = 2) and (Result[2] = DriveDelim) then
  280. begin
  281. Result := Upcase(Result[1]);
  282. end
  283. else
  284. if IsUncPath(Path) then
  285. begin
  286. Result := LowerCase(Result);
  287. end
  288. else
  289. begin
  290. raise EConvertError.Create(Format(ErrorInvalidDrive, [Path]))
  291. end;
  292. end;
  293. function TDriveInfo.GetDriveRoot(Drive: string): string;
  294. begin
  295. if IsRealDrive(Drive) then
  296. begin
  297. Result := Drive + ':\'
  298. end
  299. else
  300. begin
  301. Assert(IsUncPath(Drive));
  302. Result := IncludeTrailingBackslash(Drive);
  303. end;
  304. end;
  305. function TDriveInfo.GetFolder(Folder: TSpecialFolder): PSpecialFolderRec;
  306. var
  307. FileInfo: TShFileInfo;
  308. Path: PChar;
  309. Flags: Word;
  310. begin
  311. NeedData;
  312. Assert((Folder >= Low(FFolders)) and (Folder <= High(FFolders)));
  313. with FFolders[Folder] do
  314. begin
  315. if not Valid then
  316. begin
  317. SpecialFolderLocation(Folder, Location, PIDL);
  318. if Assigned(PIDL) then
  319. begin
  320. Path := PChar(PIDL);
  321. Flags := SHGFI_PIDL;
  322. end
  323. else
  324. begin
  325. Path := PChar(Location);
  326. Flags := 0;
  327. end;
  328. SHGetFileInfo(Path, 0, FileInfo, SizeOf(FileInfo),
  329. SHGFI_DISPLAYNAME or SHGFI_SYSICONINDEX or SHGFI_SMALLICON or Flags);
  330. ImageIndex := FileInfo.iIcon;
  331. DisplayName := FileInfo.szDisplayName;
  332. Valid := True;
  333. end;
  334. end;
  335. Result := @FFolders[Folder];
  336. end;
  337. procedure TDriveInfo.SetHonorDrivePolicy(Value: Integer);
  338. var
  339. Drive: TRealDrive;
  340. begin
  341. if HonorDrivePolicy <> Value then
  342. begin
  343. FHonorDrivePolicy := Value;
  344. if FLoaded then
  345. begin
  346. for Drive := FirstDrive to LastDrive do
  347. begin
  348. ReadDriveBasicStatus(Drive);
  349. end;
  350. end;
  351. end;
  352. end;
  353. function TDriveInfo.GetFirstFixedDrive: Char;
  354. begin
  355. if UseABDrives then Result := FirstDrive
  356. else Result := SystemDrive;
  357. end;
  358. function TDriveInfo.GetDriveBitMask(Drive: string): Integer;
  359. begin
  360. Assert(IsRealDrive(Drive));
  361. Result := (1 shl (Ord(Drive[1]) - Ord('A')));
  362. end;
  363. procedure TDriveInfo.ReadDriveBasicStatus(Drive: string);
  364. var
  365. ValidDriveType: Boolean;
  366. InaccessibleByDrivePolicy, HiddenByDrivePolicy: Boolean;
  367. DriveBitMask: Integer;
  368. begin
  369. Assert(FData.ContainsKey(Drive));
  370. with FData[Drive] do
  371. begin
  372. DriveType := Windows.GetDriveType(PChar(GetDriveRoot(Drive)));
  373. if IsRealDrive(Drive) then DriveBitMask := GetDriveBitMask(Drive)
  374. else DriveBitMask := 0;
  375. InaccessibleByDrivePolicy :=
  376. ((HonorDrivePolicy and 2) <> 0) and ((DriveBitMask and FNoViewOnDrive) <> 0);
  377. HiddenByDrivePolicy :=
  378. ((HonorDrivePolicy and 1) <> 0) and ((DriveBitMask and FNoDrives) <> 0);
  379. ValidDriveType :=
  380. (DriveType in [DRIVE_REMOVABLE, DRIVE_FIXED, DRIVE_CDROM, DRIVE_RAMDISK, DRIVE_REMOTE]) and
  381. (not InaccessibleByDrivePolicy);
  382. ValidButHiddenByDrivePolicy := ValidDriveType and HiddenByDrivePolicy;
  383. Valid := ValidDriveType and (not HiddenByDrivePolicy);
  384. end;
  385. end;
  386. procedure TDriveInfo.ResetDrive(Drive: string);
  387. begin
  388. with FData[Drive] do
  389. begin
  390. DriveReady := False;
  391. DisplayName := '';
  392. PrettyName := '';
  393. DriveSerial := 0;
  394. Size := -1;
  395. ImageIndex := 0;
  396. DriveHandle := INVALID_HANDLE_VALUE;
  397. NotificationHandle := nil;
  398. SubscribeDriveNotifications := False;
  399. end;
  400. end;
  401. function TDriveInfo.ReadDriveMask(Reg: TRegistry; ValueName: string): DWORD;
  402. var
  403. DataInfo: TRegDataInfo;
  404. begin
  405. Result := 0;
  406. if Reg.GetDataInfo(ValueName, DataInfo) then
  407. begin
  408. if (DataInfo.RegData = rdBinary) and (DataInfo.DataSize >= SizeOf(Result)) then
  409. begin
  410. Reg.ReadBinaryData(ValueName, Result, SizeOf(Result));
  411. end
  412. else
  413. if DataInfo.RegData = rdInteger then
  414. begin
  415. Result := Reg.ReadInteger(ValueName);
  416. end;
  417. end;
  418. end;
  419. procedure TDriveInfo.Load;
  420. var
  421. Drive: TRealDrive;
  422. Reg: TRegistry;
  423. Folder: TSpecialFolder;
  424. begin
  425. AppLog('Loading drives');
  426. Reg := TRegistry.Create;
  427. FNoDrives := 0;
  428. FNoViewOnDrive := 0;
  429. try
  430. if Reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
  431. begin
  432. FNoDrives := ReadDriveMask(Reg, 'NoDrives');
  433. FNoViewOnDrive := ReadDriveMask(Reg, 'NoViewOnDrive');
  434. end;
  435. finally
  436. Reg.Free;
  437. end;
  438. AppLog(Format('NoDrives mask: %d', [Integer(FNoDrives)]));
  439. AppLog(Format('NoViewOnDrive mask: %d', [Integer(FNoViewOnDrive)]));
  440. FDesktop := nil;
  441. for Drive := FirstDrive to LastDrive do
  442. begin
  443. AddDrive(Drive);
  444. end;
  445. ReadAsynchronous;
  446. for Folder := Low(FFolders) to High(FFolders) do
  447. FFolders[Folder].Valid := False;
  448. end;
  449. procedure TDriveInfo.ReadAsynchronous;
  450. var
  451. Drive: TRealDrive;
  452. Drives: string;
  453. begin
  454. for Drive := FirstDrive to LastDrive do
  455. begin
  456. // Not using Get as that would recurse into Load
  457. if FData[Drive].Valid then
  458. Drives := Drives + Drive;
  459. end;
  460. TDriveInfoThread.Create(Drives);
  461. if Length(Drives) > 0 then
  462. begin
  463. AppLog(Format('Drives to check in the background: %s', [Drives]));
  464. TDriveInfoThread.Create(Drives);
  465. end;
  466. end;
  467. function TDriveInfo.AddDrive(Drive: string): TDriveInfoRec;
  468. begin
  469. Result := TDriveInfoRec.Create;
  470. FData.Add(Drive, Result);
  471. ResetDrive(Drive);
  472. if IsFixedDrive(Drive) or (not IsRealDrive(Drive)) then // not floppy
  473. DoReadDriveStatus(Drive, dsSynchronous)
  474. else
  475. ReadDriveBasicStatus(Drive);
  476. end;
  477. function TDriveInfo.GetImageIndex(Drive: string): Integer;
  478. begin
  479. NeedData;
  480. Result := 0;
  481. if Get(Drive).Valid then
  482. begin
  483. if Get(Drive).ImageIndex = 0 then
  484. ReadDriveStatus(Drive, dsImageIndex);
  485. Result := Get(Drive).ImageIndex;
  486. end;
  487. end; {TDriveInfo.GetImageIndex}
  488. function TDriveInfo.GetDisplayName(Drive: string): string;
  489. begin
  490. if Get(Drive).Valid then
  491. begin
  492. if Length(Get(Drive).DisplayName) = 0 then
  493. ReadDriveStatus(Drive, dsDisplayName);
  494. Result := Get(Drive).DisplayName;
  495. end
  496. else
  497. begin
  498. Result := GetSimpleName(Drive);
  499. end;
  500. end; {TDriveInfo.GetDisplayname}
  501. function TDriveInfo.GetPrettyName(Drive: string): string;
  502. begin
  503. if Get(Drive).Valid then
  504. begin
  505. if Length(Get(Drive).PrettyName) = 0 then
  506. ReadDriveStatus(Drive, dsDisplayName);
  507. Result := Get(Drive).PrettyName;
  508. end
  509. else
  510. begin
  511. Result := GetSimpleName(Drive);
  512. end;
  513. end; {TDriveInfo.GetPrettyName}
  514. function TDriveInfo.GetSimpleName(Drive: string): string;
  515. begin
  516. Result := Drive;
  517. if IsRealDrive(Result) then Result := Result + ':';
  518. end;
  519. function TDriveInfo.Get(Drive: string): TDriveInfoRec;
  520. begin
  521. NeedData;
  522. // We might want to wait for FReadyDrives to be empty before returning
  523. // (or even better do that only in DriveReady getter)
  524. if not FData.TryGetValue(Drive, Result) then
  525. begin
  526. Assert(IsUncPath(Drive));
  527. Result := AddDrive(Drive);
  528. DriveRefresh;
  529. end;
  530. end; {TDriveInfo.GetData}
  531. procedure TDriveInfo.ReadDriveStatus(Drive: string; Flags: Integer);
  532. begin
  533. // Among other, this makes sure the pending drive-ready status from the background thread are collected,
  534. // before we overwrite it with fresh status here.
  535. NeedData;
  536. DoReadDriveStatus(Drive, Flags);
  537. end;
  538. procedure TDriveInfo.DoReadDriveStatus(Drive: string; Flags: Integer);
  539. var
  540. ErrorMode: Word;
  541. FileInfo: TShFileInfo;
  542. DriveRoot: string;
  543. DriveID: string;
  544. CPos: Integer;
  545. Eaten: ULONG;
  546. ShAttr: ULONG;
  547. MaxFileNameLength: DWORD;
  548. FileSystemFlags: DWORD;
  549. FreeSpace: Int64;
  550. SimpleName: string;
  551. DriveInfoRec: TDriveInfoRec;
  552. S: string;
  553. begin
  554. if not Assigned(FDesktop) then
  555. SHGetDesktopFolder(FDesktop);
  556. DriveRoot := GetDriveRoot(Drive);
  557. // When this method is called, the entry always exists already
  558. Assert(FData.ContainsKey(Drive));
  559. DriveInfoRec := FData[Drive];
  560. with DriveInfoRec do
  561. begin
  562. Init := True;
  563. ReadDriveBasicStatus(Drive);
  564. if Valid then
  565. begin
  566. if (not Assigned(PIDL)) and IsFixedDrive(Drive) then
  567. begin
  568. ShAttr := 0;
  569. if DriveType = DRIVE_REMOTE then
  570. begin
  571. ShellFolderParseDisplayNameWithTimeout(
  572. FDesktop, Application.Handle, nil, PChar(DriveRoot), Eaten, PIDL, ShAttr, 2 * MSecsPerSec);
  573. end
  574. else
  575. begin
  576. FDesktop.ParseDisplayName(Application.Handle, nil, PChar(DriveRoot), Eaten, PIDL, ShAttr);
  577. end;
  578. end;
  579. {Read driveStatus:}
  580. if (Flags and dsSize) <> 0 then
  581. begin
  582. { turn off critical errors }
  583. ErrorMode := SetErrorMode(SEM_FailCriticalErrors or SEM_NOOPENFILEERRORBOX);
  584. try
  585. DriveReady := GetDiskFreeSpaceEx(PChar(DriveRoot), FreeSpace, Size, nil);
  586. if DriveReady then
  587. begin
  588. {Access the physical drive:}
  589. if GetVolumeInformation(PChar(DriveRoot), nil, 0,
  590. @DriveSerial, MaxFileNameLength, FileSystemFlags,
  591. nil, 0) then
  592. begin
  593. end
  594. else
  595. begin
  596. DriveSerial := 0;
  597. end;
  598. end
  599. else
  600. begin
  601. DriveSerial := 0;
  602. end;
  603. // Particularly when removing drive fails (as other app has it locked), we end up with not monitoring the
  604. // drive. When the drive is visited again in panel, it calls into here, and we take the opportunity
  605. // to resume monitoring
  606. UpdateDriveNotifications(Drive);
  607. finally
  608. { restore old error mode }
  609. SetErrorMode(ErrorMode);
  610. end;
  611. end;
  612. {DisplayName:}
  613. if (Flags and dsDisplayName) <> 0 then
  614. begin
  615. {Fetch drives displayname:}
  616. SimpleName := GetSimpleName(Drive);
  617. if Assigned(PIDL) then DisplayName := GetShellFileName(PIDL)
  618. else
  619. begin
  620. // typical reason we do not have PIDL is that it took too long to
  621. // call ParseDisplayName, in what case calling SHGetFileInfo with
  622. // path (instead of PIDL) will take long too, avoiding that and using
  623. // fallback
  624. DisplayName := '(' + SimpleName + ')';
  625. end;
  626. if DriveType <> DRIVE_REMOTE then
  627. begin
  628. PrettyName := SimpleName + ' ' + DisplayName;
  629. S := ' (' + SimpleName + ')';
  630. CPos := Pos(S, PrettyName);
  631. if CPos > 0 then
  632. Delete(PrettyName, CPos, Length(S));
  633. end
  634. else
  635. if IsRealDrive(Drive) then
  636. begin
  637. DriveID := GetNetWorkName(Drive);
  638. PrettyName := Format('%s %s (%s)', [SimpleName, ExtractFileName(DriveID), ExtractFileDir(DriveID)]);
  639. end
  640. else
  641. begin
  642. Assert(IsUncPath(DriveRoot));
  643. PrettyName := SimpleName;
  644. end;
  645. end;
  646. {ImageIndex:}
  647. if (Flags and dsImageIndex) <> 0 then
  648. begin
  649. if Assigned(PIDL) then
  650. begin
  651. SHGetFileInfo(PChar(PIDL), 0, FileInfo, SizeOf(FileInfo), SHGFI_SYSICONINDEX or SHGFI_SMALLICON or SHGFI_PIDL)
  652. end
  653. else
  654. begin
  655. SHGetFileInfo(PChar(DriveRoot), 0, FileInfo, SizeOf(FileInfo), SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
  656. end;
  657. ImageIndex := FileInfo.iIcon;
  658. end;
  659. end
  660. else
  661. begin
  662. if Assigned(PIDL) then
  663. FreePIDL(PIDL);
  664. ResetDrive(Drive);
  665. end;
  666. end;
  667. end; {TDriveInfo.ReadDriveStatus}
  668. procedure TDriveInfo.OverrideDrivePolicy(Drive: string);
  669. var
  670. Mask: DWORD;
  671. begin
  672. Assert(FData.ContainsKey(Drive));
  673. Assert(FData[Drive].ValidButHiddenByDrivePolicy);
  674. Mask := (not GetDriveBitMask(Drive));
  675. FNoDrives := FNoDrives and Mask;
  676. ReadDriveStatus(Drive, dsAll);
  677. Assert(FData[Drive].Valid);
  678. DriveRefresh;
  679. end;
  680. procedure TDriveInfo.AddHandler(Handler: TDriveNotificationEvent);
  681. var
  682. ChangeNotifyEntry: TSHChangeNotifyEntry;
  683. Dummy: string;
  684. begin
  685. if not FHandlers.Contains(Handler) then
  686. begin
  687. FHandlers.Add(Handler);
  688. if FHandlers.Count = 1 then
  689. begin
  690. // Source: petr.solin 2022-02-25
  691. if SpecialFolderLocation(CSIDL_DESKTOP, Dummy, ChangeNotifyEntry.pidl) then
  692. begin
  693. ChangeNotifyEntry.fRecursive := False;
  694. FChangeNotify :=
  695. SHChangeNotifyRegister(
  696. InternalWindowHandle, SHCNRF_ShellLevel or SHCNRF_NewDelivery,
  697. SHCNE_RENAMEFOLDER or SHCNE_MEDIAINSERTED or SHCNE_MEDIAREMOVED,
  698. WM_USER_SHCHANGENOTIFY, 1, ChangeNotifyEntry);
  699. end;
  700. UpdateDrivesNotifications;
  701. end;
  702. end;
  703. end;
  704. procedure TDriveInfo.RemoveHandler(Handler: TDriveNotificationEvent);
  705. begin
  706. if (FHandlers.Remove(Handler) >= 0) and (FHandlers.Count = 0) then
  707. begin
  708. if FChangeNotify <> 0 then
  709. begin
  710. SHChangeNotifyDeregister(FChangeNotify);
  711. FChangeNotify := 0;
  712. end;
  713. UpdateDrivesNotifications;
  714. end;
  715. end;
  716. procedure TDriveInfo.InvokeHandlers(DriveNotification: TDriveNotification; Drive: string);
  717. var
  718. Handler: TDriveNotificationEvent;
  719. begin
  720. for Handler in FHandlers do
  721. Handler(DriveNotification, Drive);
  722. end;
  723. type
  724. PDevBroadcastHdr = ^TDevBroadcastHdr;
  725. TDevBroadcastHdr = record
  726. dbch_size: DWORD;
  727. dbch_devicetype: DWORD;
  728. dbch_reserved: DWORD;
  729. end;
  730. PDevBroadcastVolume = ^TDevBroadcastVolume;
  731. TDevBroadcastVolume = record
  732. dbcv_size: DWORD;
  733. dbcv_devicetype: DWORD;
  734. dbcv_reserved: DWORD;
  735. dbcv_unitmask: DWORD;
  736. dbcv_flags: WORD;
  737. end;
  738. PDEV_BROADCAST_HANDLE = ^DEV_BROADCAST_HANDLE;
  739. DEV_BROADCAST_HANDLE = record
  740. dbch_size : DWORD;
  741. dbch_devicetype : DWORD;
  742. dbch_reserved : DWORD;
  743. dbch_handle : THandle;
  744. dbch_hdevnotify : HDEVNOTIFY ;
  745. dbch_eventguid : TGUID;
  746. dbch_nameoffset : LongInt;
  747. dbch_data : Byte;
  748. end;
  749. PPItemIDList = ^PItemIDList;
  750. const
  751. DBT_DEVTYP_HANDLE = $00000006;
  752. DBT_CONFIGCHANGED = $0018;
  753. DBT_DEVICEARRIVAL = $8000;
  754. DBT_DEVICEQUERYREMOVE = $8001;
  755. DBT_DEVICEREMOVEPENDING = $8003;
  756. DBT_DEVICEREMOVECOMPLETE = $8004;
  757. DBT_DEVTYP_VOLUME = $00000002;
  758. // WORKAROUND Declaration in Winapi.ShlObj.pas is wrong
  759. function SHChangeNotification_Lock(hChange: THandle; dwProcId: DWORD;
  760. var PPidls: PPItemIDList; var plEvent: Longint): THANDLE; stdcall;
  761. external 'shell32.dll' name 'SHChangeNotification_Lock';
  762. procedure TDriveInfo.InternalWndProc(var Msg: TMessage);
  763. var
  764. DeviceType: DWORD;
  765. UnitMask: DWORD;
  766. DeviceHandle: THandle;
  767. Drive: Char;
  768. PPIDL: PPItemIDList;
  769. Event: LONG;
  770. Lock: THandle;
  771. DrivePair: TPair<string, TDriveInfoRec>;
  772. begin
  773. with Msg do
  774. begin
  775. if Msg = WM_USER_SHCHANGENOTIFY then
  776. begin
  777. Lock := SHChangeNotification_Lock(wParam, lParam, PPIDL, Event);
  778. try
  779. if (Event = SHCNE_RENAMEFOLDER) or // = drive rename
  780. (Event = SHCNE_MEDIAINSERTED) or // also bitlocker drive unlock (also sends SHCNE_UPDATEDIR)
  781. (Event = SHCNE_MEDIAREMOVED) then
  782. begin
  783. ScheduleDriveRefresh;
  784. end;
  785. finally
  786. SHChangeNotification_Unlock(Lock);
  787. end;
  788. end
  789. else
  790. // from RegisterDeviceNotification
  791. if Msg = WM_DEVICECHANGE then
  792. begin
  793. if (wParam = DBT_CONFIGCHANGED) or
  794. (wParam = DBT_DEVICEARRIVAL) or
  795. (wParam = DBT_DEVICEREMOVECOMPLETE) then
  796. begin
  797. ScheduleDriveRefresh;
  798. end
  799. else
  800. if (wParam = DBT_DEVICEQUERYREMOVE) or
  801. (wParam = DBT_DEVICEREMOVEPENDING) then
  802. begin
  803. DeviceType := PDevBroadcastHdr(lParam)^.dbch_devicetype;
  804. // This is specifically for VeraCrypt.
  805. // For normal drives, see DBT_DEVTYP_HANDLE below
  806. // (and maybe now that we have generic implementation, this specific code for VeraCrypt might not be needed anymore)
  807. if DeviceType = DBT_DEVTYP_VOLUME then
  808. begin
  809. UnitMask := PDevBroadcastVolume(lParam)^.dbcv_unitmask;
  810. Drive := FirstDrive;
  811. while UnitMask > 0 do
  812. begin
  813. if UnitMask and $01 <> 0 then
  814. begin
  815. DriveRemoving(Drive);
  816. end;
  817. UnitMask := UnitMask shr 1;
  818. Drive := Chr(Ord(Drive) + 1);
  819. end;
  820. end
  821. else
  822. if DeviceType = DBT_DEVTYP_HANDLE then
  823. begin
  824. DeviceHandle := PDEV_BROADCAST_HANDLE(lParam)^.dbch_handle;
  825. for DrivePair in FData do
  826. if DrivePair.Value.DriveHandle = DeviceHandle then
  827. begin
  828. DriveRemoving(DrivePair.Key);
  829. end;
  830. end;
  831. end;
  832. end
  833. else
  834. if Msg = WM_TIMER then
  835. begin
  836. CancelDriveRefresh;
  837. try
  838. for Drive := FirstFixedDrive to LastDrive do
  839. ReadDriveStatus(Drive, dsSynchronous);
  840. ReadAsynchronous;
  841. DriveRefresh;
  842. except
  843. Application.HandleException(Self);
  844. end;
  845. end
  846. else
  847. if Msg = WM_DRIVEINFO_PROCESS then
  848. begin
  849. ProcessThreadResults;
  850. end;
  851. Result := DefWindowProc(InternalWindowHandle, Msg, wParam, lParam);
  852. end;
  853. end;
  854. procedure TDriveInfo.DriveRemoving(Drive: string);
  855. begin
  856. FData[Drive].DriveReady := False;
  857. UpdateDriveNotifications(Drive);
  858. AppLog(Format('Removing drive "%s"', [Drive]));
  859. InvokeHandlers(dnRemoving, Drive);
  860. end;
  861. procedure TDriveInfo.CancelDriveRefresh;
  862. begin
  863. KillTimer(InternalWindowHandle, 1);
  864. end;
  865. procedure TDriveInfo.ScheduleDriveRefresh;
  866. begin
  867. CancelDriveRefresh;
  868. // Delay refreshing drives for a sec.
  869. // Particularly with CD/DVD drives, if we query display name
  870. // immediately after receiving DBT_DEVICEARRIVAL, we do not get media label.
  871. // Actually one sec does not help usually, but we do not want to wait any longer,
  872. // because we want to add USB drives asap.
  873. // And this problem might be solved now by SHChangeNotifyRegister/SHCNE_RENAMEFOLDER.
  874. SetTimer(InternalWindowHandle, 1, MSecsPerSec, nil);
  875. end;
  876. procedure TDriveInfo.UpdateDriveNotifications(Drive: string);
  877. var
  878. NeedNotifications: Boolean;
  879. Path: string;
  880. DevBroadcastHandle: DEV_BROADCAST_HANDLE;
  881. Size: Integer;
  882. DriveInfoRec: TDriveInfoRec;
  883. begin
  884. if IsFixedDrive(Drive) then
  885. begin
  886. // Not using Get to avoid recursion
  887. DriveInfoRec := FData[Drive];
  888. NeedNotifications :=
  889. (FHandlers.Count > 0) and
  890. (DriveInfoRec.DriveType <> DRIVE_REMOTE) and
  891. DriveInfoRec.DriveReady and
  892. DriveInfoRec.SubscribeDriveNotifications;
  893. if NeedNotifications <> (DriveInfoRec.DriveHandle <> INVALID_HANDLE_VALUE) then
  894. begin
  895. Path := GetDriveRoot(Drive);
  896. if NeedNotifications then
  897. begin
  898. DriveInfoRec.DriveHandle :=
  899. CreateFile(PChar(Path), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil,
  900. OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL, 0);
  901. if DriveInfoRec.DriveHandle <> INVALID_HANDLE_VALUE then
  902. begin
  903. Size := SizeOf(DevBroadcastHandle);
  904. ZeroMemory(@DevBroadcastHandle, Size);
  905. DevBroadcastHandle.dbch_size := Size;
  906. DevBroadcastHandle.dbch_devicetype := DBT_DEVTYP_HANDLE;
  907. DevBroadcastHandle.dbch_handle := DriveInfoRec.DriveHandle;
  908. DriveInfoRec.NotificationHandle :=
  909. RegisterDeviceNotification(InternalWindowHandle, @DevBroadcastHandle, DEVICE_NOTIFY_WINDOW_HANDLE);
  910. if DriveInfoRec.NotificationHandle <> nil then
  911. begin
  912. AppLog(Format('Registered drive notification for "%s"', [Path]));
  913. end
  914. else
  915. begin
  916. CloseHandle(DriveInfoRec.DriveHandle);
  917. DriveInfoRec.DriveHandle := INVALID_HANDLE_VALUE;
  918. end;
  919. end;
  920. end
  921. else
  922. begin
  923. AppLog(Format('Unregistered drive notification for "%s"', [Path]));
  924. UnregisterDeviceNotification(DriveInfoRec.NotificationHandle);
  925. DriveInfoRec.NotificationHandle := nil;
  926. CloseHandle(DriveInfoRec.DriveHandle);
  927. DriveInfoRec.DriveHandle := INVALID_HANDLE_VALUE;
  928. end;
  929. end;
  930. end;
  931. end;
  932. procedure TDriveInfo.UpdateDrivesNotifications;
  933. var
  934. Drive: string;
  935. begin
  936. for Drive in FData.Keys do
  937. UpdateDriveNotifications(Drive);
  938. end;
  939. procedure TDriveInfo.DriveRefresh;
  940. begin
  941. InvokeHandlers(dnRefresh, '');
  942. end;
  943. procedure TDriveInfo.SubscribeDriveNotifications(Drive: string);
  944. begin
  945. Get(Drive).SubscribeDriveNotifications := True;
  946. UpdateDriveNotifications(Drive);
  947. end;
  948. // ===================
  949. function GetShellFileName(const Name: string): string;
  950. var
  951. SFI: TSHFileInfo;
  952. E: Integer;
  953. begin
  954. E := SetErrorMode(SEM_FAILCRITICALERRORS);
  955. try
  956. if SHGetFileInfo(PChar(Name), 0, SFI, SizeOf(TSHFileInfo), SHGFI_DISPLAYNAME) <> 0 then
  957. Result := SFI.szDisplayName;
  958. finally
  959. SetErrorMode(E);
  960. end;
  961. end; {GetShellFileName}
  962. function GetShellFileName(PIDL: PItemIDList): string;
  963. var
  964. SFI: TSHFileInfo;
  965. E: Integer;
  966. begin
  967. E := SetErrorMode(SEM_FAILCRITICALERRORS);
  968. try
  969. if SHGetFileInfo(PChar(PIDL), 0, SFI, SizeOf(TSHFileInfo), SHGFI_PIDL or SHGFI_DISPLAYNAME) <> 0 then
  970. Result := SFI.szDisplayName;
  971. finally
  972. SetErrorMode(E);
  973. end;
  974. end; {GetShellFileName}
  975. function GetNetWorkName(Drive: string): string;
  976. var
  977. Path: string;
  978. P: array[0..MAX_PATH] of Char;
  979. MaxLen : DWORD;
  980. begin
  981. Path := ExcludeTrailingBackslash(DriveInfo.GetDriveRoot(Drive));
  982. MaxLen := MAX_PATH;
  983. if WNetGetConnection(PChar(Path), P, MaxLen) = NO_ERROR then
  984. Result := P
  985. else
  986. Result := '';
  987. end; {GetNetWorkName}
  988. type
  989. LPBYTE = ^BYTE;
  990. LMSTR = LPWSTR;
  991. NET_API_STATUS = DWORD;
  992. _USE_INFO_1 = record
  993. ui1_local: LMSTR;
  994. ui1_remote: LMSTR;
  995. ui1_password: LMSTR;
  996. ui1_status: DWORD;
  997. ui1_asg_type: DWORD;
  998. ui1_refcount: DWORD;
  999. ui1_usecount: DWORD;
  1000. end;
  1001. USE_INFO_1 = _USE_INFO_1;
  1002. PUSE_INFO_1 = ^USE_INFO_1;
  1003. LPVOID = Pointer;
  1004. const
  1005. USE_OK = 0;
  1006. USE_PAUSED = 1;
  1007. USE_SESSLOST = 2;
  1008. USE_DISCONN = 2;
  1009. USE_NETERR = 3;
  1010. USE_CONN = 4;
  1011. USE_RECONN = 5;
  1012. function NetUseGetInfo(UncServerName: LMSTR; UseName: LMSTR; Level: DWORD; var BufPtr: LPBYTE): NET_API_STATUS; stdcall; external 'netapi32.dll';
  1013. function NetApiBufferFree(Buffer: Pointer): DWORD; stdcall; external 'netapi32.dll';
  1014. function GetNetWorkConnected(Drive: string): Boolean;
  1015. var
  1016. BufPtr: LPBYTE;
  1017. NetResult: Integer;
  1018. ServerName: string;
  1019. PServerName: PChar;
  1020. Name: string;
  1021. P: Integer;
  1022. begin
  1023. Name := '';
  1024. PServerName := nil;
  1025. if DriveInfo.IsRealDrive(Drive) then
  1026. begin
  1027. Name := Drive + ':';
  1028. end
  1029. else
  1030. if IsUncPath(Drive) then
  1031. begin
  1032. Name := Copy(Drive, 3, Length(Drive) - 2);
  1033. P := Pos('\', Name);
  1034. if P > 0 then
  1035. begin
  1036. ServerName := Copy(Name, P + 1, Length(Name) - P);
  1037. PServerName := PChar(ServerName);
  1038. SetLength(Name, P - 1);
  1039. end
  1040. else
  1041. begin
  1042. Assert(False);
  1043. end;
  1044. end
  1045. else
  1046. begin
  1047. Assert(False);
  1048. end;
  1049. if Name = '' then
  1050. begin
  1051. Result := False;
  1052. end
  1053. else
  1054. begin
  1055. NetResult := NetUseGetInfo(PServerName, PChar(Name), 1, BufPtr);
  1056. if NetResult = 0 then
  1057. begin
  1058. Result := (PUSE_INFO_1(BufPtr)^.ui1_status = USE_OK);
  1059. NetApiBufferFree(LPVOID(BufPtr));
  1060. end
  1061. else
  1062. begin
  1063. // NetUseGetInfo works for DFS shares only, hence when it fails
  1064. // we suppose different share type and fallback to "connected"
  1065. Result := True;
  1066. end;
  1067. end;
  1068. end;
  1069. function IsRootPath(Path: string): Boolean;
  1070. begin
  1071. Result := SameText(ExcludeTrailingBackslash(ExtractFileDrive(Path)), ExcludeTrailingBackslash(Path));
  1072. end;
  1073. function GetThumbnail(Path: string; Size: TSize): TBitmap;
  1074. var
  1075. ImageFactory: IShellItemImageFactory;
  1076. X, Y: Integer;
  1077. Row: PRGBQuadArray;
  1078. Pixel: PRGBQuad;
  1079. Alpha: Byte;
  1080. Handle: HBITMAP;
  1081. begin
  1082. Result := nil;
  1083. SHCreateItemFromParsingName(PChar(Path), nil, IShellItemImageFactory, ImageFactory);
  1084. if Assigned(ImageFactory) then
  1085. begin
  1086. if Succeeded(ImageFactory.GetImage(Size, SIIGBF_RESIZETOFIT, Handle)) then
  1087. begin
  1088. Result := TBitmap.Create;
  1089. try
  1090. Result.Handle := Handle;
  1091. Result.PixelFormat := pf32bit;
  1092. for Y := 0 to Result.Height - 1 do
  1093. begin
  1094. Row := Result.ScanLine[Y];
  1095. for X := 0 to Result.Width - 1 do
  1096. begin
  1097. Pixel := @Row[X];
  1098. Alpha := Pixel.rgbReserved;
  1099. Pixel.rgbBlue := (Pixel.rgbBlue * Alpha) div 255;
  1100. Pixel.rgbGreen := (Pixel.rgbGreen * Alpha) div 255;
  1101. Pixel.rgbRed := (Pixel.rgbRed * Alpha) div 255;
  1102. end;
  1103. end;
  1104. except
  1105. Result.Free;
  1106. raise;
  1107. end;
  1108. end;
  1109. ImageFactory := nil; // Redundant?
  1110. end;
  1111. end;
  1112. initialization
  1113. InitializeCriticalSection(ThreadLock);
  1114. if not Assigned(DriveInfo) then
  1115. begin
  1116. DriveInfo := TDriveInfo.Create;
  1117. InternalWindowHandle := Classes.AllocateHWnd(DriveInfo.InternalWndProc);
  1118. end;
  1119. finalization
  1120. if Assigned(DriveInfo) then
  1121. begin
  1122. EnterCriticalSection(ThreadLock);
  1123. Classes.DeallocateHWnd(InternalWindowHandle);
  1124. InternalWindowHandle := 0;
  1125. DriveInfo.Free;
  1126. DriveInfo := nil;
  1127. LeaveCriticalSection(ThreadLock);
  1128. end;
  1129. DeleteCriticalSection(ThreadLock);
  1130. end.