Shared.FileClass.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. unit Shared.FileClass;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. TFile class
  8. Better than File and TFileStream in that does more extensive error checking
  9. and uses descriptive, localized system error messages.
  10. TTextFileReader and TTextFileWriter support ANSI and UTF8 textfiles.
  11. }
  12. interface
  13. uses
  14. Windows, SysUtils;
  15. type
  16. TFileCreateDisposition = (fdCreateAlways, fdCreateNew, fdOpenExisting,
  17. fdOpenAlways, fdTruncateExisting);
  18. TFileAccess = (faRead, faWrite, faReadWrite);
  19. TFileSharing = (fsNone, fsRead, fsWrite, fsReadWrite);
  20. TCustomFile = class
  21. private
  22. function GetCappedSize: Cardinal;
  23. protected
  24. function GetPosition: Int64; virtual; abstract;
  25. function GetSize: Int64; virtual; abstract;
  26. public
  27. class procedure RaiseError(ErrorCode: DWORD);
  28. class procedure RaiseLastError;
  29. function Read(var Buffer; Count: Cardinal): Cardinal; virtual; abstract;
  30. procedure ReadBuffer(var Buffer; Count: Cardinal);
  31. procedure Seek(Offset: Int64); virtual; abstract;
  32. procedure WriteAnsiString(const S: AnsiString);
  33. procedure WriteBuffer(const Buffer; Count: Cardinal); virtual; abstract;
  34. property CappedSize: Cardinal read GetCappedSize;
  35. property Position: Int64 read GetPosition;
  36. property Size: Int64 read GetSize;
  37. end;
  38. TFile = class(TCustomFile)
  39. private
  40. FHandle: THandle;
  41. FHandleCreated: Boolean;
  42. protected
  43. function CreateHandle(const AFilename: String;
  44. ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  45. ASharing: TFileSharing): THandle; virtual;
  46. function GetPosition: Int64; override;
  47. function GetSize: Int64; override;
  48. public
  49. constructor Create(const AFilename: String;
  50. ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  51. ASharing: TFileSharing);
  52. constructor CreateDuplicate(const ASourceFile: TFile);
  53. constructor CreateWithExistingHandle(const AHandle: THandle);
  54. destructor Destroy; override;
  55. function Read(var Buffer; Count: Cardinal): Cardinal; override;
  56. procedure Seek(Offset: Int64); override;
  57. procedure SeekToEnd;
  58. procedure Truncate;
  59. procedure WriteBuffer(const Buffer; Count: Cardinal); override;
  60. property Handle: THandle read FHandle;
  61. end;
  62. TMemoryFile = class(TCustomFile)
  63. private
  64. FMemory: Pointer;
  65. FSize: Cardinal;
  66. FPosition: Int64;
  67. function ClipCount(DesiredCount: Cardinal): Cardinal;
  68. protected
  69. procedure AllocMemory(const ASize: Cardinal);
  70. function GetPosition: Int64; override;
  71. function GetSize: Int64; override;
  72. public
  73. constructor Create(const AFilename: String);
  74. constructor CreateFromMemory(const ASource; const ASize: Cardinal);
  75. constructor CreateFromZero(const ASize: Cardinal);
  76. destructor Destroy; override;
  77. function Read(var Buffer; Count: Cardinal): Cardinal; override;
  78. procedure Seek(Offset: Int64); override;
  79. procedure WriteBuffer(const Buffer; Count: Cardinal); override;
  80. property Memory: Pointer read FMemory;
  81. end;
  82. TTextFileReader = class(TFile)
  83. private
  84. FBufferOffset, FBufferSize: Cardinal;
  85. FEof: Boolean;
  86. FBuffer: array[0..4095] of AnsiChar;
  87. FSawFirstLine: Boolean;
  88. FCodePage: Word;
  89. function DoReadLine(const UTF8: Boolean): AnsiString;
  90. function GetEof: Boolean;
  91. procedure FillBuffer;
  92. public
  93. function ReadLine: String;
  94. function ReadAnsiLine: AnsiString;
  95. property CodePage: Word write FCodePage;
  96. property Eof: Boolean read GetEof;
  97. end;
  98. TTextFileWriter = class(TFile)
  99. private
  100. FSeekedToEnd: Boolean;
  101. FUTF8WithoutBOM: Boolean;
  102. procedure DoWrite(const S: AnsiString; const UTF8: Boolean);
  103. protected
  104. function CreateHandle(const AFilename: String;
  105. ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  106. ASharing: TFileSharing): THandle; override;
  107. public
  108. property UTF8WithoutBOM: Boolean read FUTF8WithoutBOM write FUTF8WithoutBOM;
  109. procedure Write(const S: String);
  110. procedure WriteLine(const S: String);
  111. procedure WriteAnsi(const S: AnsiString);
  112. procedure WriteAnsiLine(const S: AnsiString);
  113. end;
  114. TFileMapping = class
  115. private
  116. FMemory: Pointer;
  117. FMapSize: Cardinal;
  118. FMappingHandle: THandle;
  119. public
  120. constructor Create(AFile: TFile; AWritable: Boolean);
  121. destructor Destroy; override;
  122. procedure Commit;
  123. procedure ReraiseInPageErrorAsFileException;
  124. property MapSize: Cardinal read FMapSize;
  125. property Memory: Pointer read FMemory;
  126. end;
  127. EFileError = class(Exception)
  128. private
  129. FErrorCode: DWORD;
  130. public
  131. property ErrorCode: DWORD read FErrorCode write FErrorCode;
  132. end;
  133. implementation
  134. uses
  135. WideStrUtils,
  136. UnsignedFunc,
  137. Shared.CommonFunc;
  138. const
  139. SGenericIOError = 'File I/O error %d';
  140. { TCustomFile }
  141. function TCustomFile.GetCappedSize: Cardinal;
  142. { Like GetSize, but capped at $7FFFFFFF }
  143. begin
  144. const LSize = GetSize;
  145. if LSize > High(Int32) then
  146. Result := High(Int32)
  147. else
  148. Result := Cardinal(LSize);
  149. end;
  150. class procedure TCustomFile.RaiseError(ErrorCode: DWORD);
  151. begin
  152. var S := Win32ErrorString(ErrorCode);
  153. if S = '' then begin
  154. { In case there was no text for the error code. Shouldn't get here under
  155. normal circumstances. }
  156. S := Format(SGenericIOError, [ErrorCode]);
  157. end;
  158. const E = EFileError.Create(S);
  159. E.FErrorCode := ErrorCode;
  160. raise E;
  161. end;
  162. class procedure TCustomFile.RaiseLastError;
  163. begin
  164. RaiseError(GetLastError);
  165. end;
  166. procedure TCustomFile.ReadBuffer(var Buffer; Count: Cardinal);
  167. begin
  168. if Read(Buffer, Count) <> Count then begin
  169. { Raise localized "Reached end of file" error }
  170. RaiseError(ERROR_HANDLE_EOF);
  171. end;
  172. end;
  173. procedure TCustomFile.WriteAnsiString(const S: AnsiString);
  174. begin
  175. WriteBuffer(S[1], ULength(S));
  176. end;
  177. { TFile }
  178. constructor TFile.Create(const AFilename: String;
  179. ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  180. ASharing: TFileSharing);
  181. begin
  182. inherited Create;
  183. FHandle := CreateHandle(AFilename, ACreateDisposition, AAccess, ASharing);
  184. if (FHandle = 0) or (FHandle = INVALID_HANDLE_VALUE) then
  185. RaiseLastError;
  186. FHandleCreated := True;
  187. end;
  188. constructor TFile.CreateDuplicate(const ASourceFile: TFile);
  189. begin
  190. inherited Create;
  191. var LHandle: THandle;
  192. if not DuplicateHandle(GetCurrentProcess, ASourceFile.Handle,
  193. GetCurrentProcess, @LHandle, 0, False, DUPLICATE_SAME_ACCESS) then
  194. RaiseLastError;
  195. FHandle := LHandle; { assign only on success }
  196. FHandleCreated := True;
  197. end;
  198. constructor TFile.CreateWithExistingHandle(const AHandle: THandle);
  199. begin
  200. inherited Create;
  201. FHandle := AHandle;
  202. end;
  203. destructor TFile.Destroy;
  204. begin
  205. if FHandleCreated then
  206. CloseHandle(FHandle);
  207. inherited;
  208. end;
  209. function TFile.CreateHandle(const AFilename: String;
  210. ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  211. ASharing: TFileSharing): THandle;
  212. const
  213. AccessFlags: array[TFileAccess] of DWORD =
  214. (GENERIC_READ, GENERIC_WRITE, GENERIC_READ or GENERIC_WRITE);
  215. SharingFlags: array[TFileSharing] of DWORD =
  216. (0, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE);
  217. Disps: array[TFileCreateDisposition] of DWORD =
  218. (CREATE_ALWAYS, CREATE_NEW, OPEN_EXISTING, OPEN_ALWAYS, TRUNCATE_EXISTING);
  219. begin
  220. Result := CreateFile(PChar(AFilename), AccessFlags[AAccess],
  221. SharingFlags[ASharing], nil, Disps[ACreateDisposition],
  222. FILE_ATTRIBUTE_NORMAL, 0);
  223. end;
  224. function TFile.GetPosition: Int64;
  225. begin
  226. if not SetFilePointerEx(FHandle, 0, @Result, FILE_CURRENT) then
  227. RaiseLastError;
  228. end;
  229. function TFile.GetSize: Int64;
  230. begin
  231. if not GetFileSizeEx(FHandle, Result) then
  232. RaiseLastError;
  233. end;
  234. function TFile.Read(var Buffer; Count: Cardinal): Cardinal;
  235. begin
  236. if not ReadFile(FHandle, Buffer, Count, DWORD(Result), nil) then
  237. if FHandleCreated or (GetLastError <> ERROR_BROKEN_PIPE) then
  238. RaiseLastError;
  239. end;
  240. procedure TFile.Seek(Offset: Int64);
  241. begin
  242. if not SetFilePointerEx(FHandle, Offset, nil, FILE_BEGIN) then
  243. RaiseLastError;
  244. end;
  245. procedure TFile.SeekToEnd;
  246. begin
  247. if not SetFilePointerEx(FHandle, 0, nil, FILE_END) then
  248. RaiseLastError;
  249. end;
  250. procedure TFile.Truncate;
  251. begin
  252. if not SetEndOfFile(FHandle) then
  253. RaiseLastError;
  254. end;
  255. procedure TFile.WriteBuffer(const Buffer; Count: Cardinal);
  256. var
  257. BytesWritten: DWORD;
  258. begin
  259. if not WriteFile(FHandle, Buffer, Count, BytesWritten, nil) then
  260. RaiseLastError;
  261. if BytesWritten <> Count then begin
  262. { I'm not aware of any case where WriteFile will return True but a short
  263. BytesWritten count. (An out-of-disk-space condition causes False to be
  264. returned.) But if that does happen, raise a generic-sounding localized
  265. "The system cannot write to the specified device" error. }
  266. RaiseError(ERROR_WRITE_FAULT);
  267. end;
  268. end;
  269. { TMemoryFile }
  270. constructor TMemoryFile.Create(const AFilename: String);
  271. var
  272. F: TFile;
  273. begin
  274. inherited Create;
  275. F := TFile.Create(AFilename, fdOpenExisting, faRead, fsRead);
  276. try
  277. AllocMemory(F.CappedSize);
  278. F.ReadBuffer(FMemory^, FSize);
  279. finally
  280. F.Free;
  281. end;
  282. end;
  283. constructor TMemoryFile.CreateFromMemory(const ASource; const ASize: Cardinal);
  284. begin
  285. inherited Create;
  286. AllocMemory(ASize);
  287. UMove(ASource, FMemory^, FSize);
  288. end;
  289. constructor TMemoryFile.CreateFromZero(const ASize: Cardinal);
  290. begin
  291. inherited Create;
  292. AllocMemory(ASize);
  293. UFillChar(FMemory^, FSize, 0);
  294. end;
  295. destructor TMemoryFile.Destroy;
  296. begin
  297. if Assigned(FMemory) then
  298. LocalFree(HLOCAL(FMemory));
  299. inherited;
  300. end;
  301. procedure TMemoryFile.AllocMemory(const ASize: Cardinal);
  302. begin
  303. FMemory := Pointer(LocalAlloc(LMEM_FIXED, ASize));
  304. if FMemory = nil then
  305. OutOfMemoryError;
  306. FSize := ASize;
  307. end;
  308. function TMemoryFile.ClipCount(DesiredCount: Cardinal): Cardinal;
  309. begin
  310. { First check if FPosition is already past FSize, so the subtraction below
  311. won't underflow. And to be extra safe, make sure FPosition isn't negative
  312. (even though Seek already checks for that). }
  313. if FPosition >= FSize then begin
  314. Result := 0;
  315. Exit;
  316. end;
  317. if FPosition < 0 then
  318. RaiseError(ERROR_NEGATIVE_SEEK);
  319. const BytesLeft: Cardinal = FSize - Cardinal(FPosition);
  320. if DesiredCount > BytesLeft then
  321. Result := BytesLeft
  322. else
  323. Result := DesiredCount;
  324. end;
  325. function TMemoryFile.GetPosition: Int64;
  326. begin
  327. Result := FPosition;
  328. end;
  329. function TMemoryFile.GetSize: Int64;
  330. begin
  331. Result := FSize;
  332. end;
  333. function TMemoryFile.Read(var Buffer; Count: Cardinal): Cardinal;
  334. begin
  335. Result := ClipCount(Count);
  336. if Result <> 0 then begin
  337. UMove((PByte(FMemory) + Cardinal(FPosition))^, Buffer, Result);
  338. Inc(FPosition, Result);
  339. end;
  340. end;
  341. procedure TMemoryFile.Seek(Offset: Int64);
  342. begin
  343. if Offset < 0 then
  344. RaiseError(ERROR_NEGATIVE_SEEK);
  345. FPosition := Offset;
  346. end;
  347. procedure TMemoryFile.WriteBuffer(const Buffer; Count: Cardinal);
  348. begin
  349. if ClipCount(Count) <> Count then
  350. RaiseError(ERROR_HANDLE_EOF);
  351. if Count <> 0 then begin
  352. UMove(Buffer, (PByte(FMemory) + Cardinal(FPosition))^, Count);
  353. Inc(FPosition, Count);
  354. end;
  355. end;
  356. { TTextFileReader }
  357. procedure TTextFileReader.FillBuffer;
  358. begin
  359. if (FBufferOffset < FBufferSize) or FEof then
  360. Exit;
  361. FBufferSize := Read(FBuffer, SizeOf(FBuffer));
  362. FBufferOffset := 0;
  363. if FBufferSize = 0 then
  364. FEof := True;
  365. end;
  366. function TTextFileReader.GetEof: Boolean;
  367. begin
  368. FillBuffer;
  369. Result := FEof;
  370. end;
  371. function TTextFileReader.ReadLine: String;
  372. var
  373. S: RawByteString;
  374. begin
  375. S := DoReadLine(True);
  376. if FCodePage <> 0 then
  377. SetCodePage(S, FCodePage, False);
  378. Result := String(S);
  379. end;
  380. function TTextFileReader.ReadAnsiLine: AnsiString;
  381. begin
  382. Result := DoReadLine(False);
  383. end;
  384. function TTextFileReader.DoReadLine(const UTF8: Boolean): AnsiString;
  385. var
  386. I, L: Cardinal;
  387. S: AnsiString;
  388. begin
  389. while True do begin
  390. FillBuffer;
  391. if FEof then begin
  392. { End of file reached }
  393. if S = '' then begin
  394. { If nothing was read (i.e. we were already at EOF), raise localized
  395. "Reached end of file" error }
  396. RaiseError(ERROR_HANDLE_EOF);
  397. end;
  398. Break;
  399. end;
  400. I := FBufferOffset;
  401. while I < FBufferSize do begin
  402. if FBuffer[I] in [#10, #13] then
  403. Break;
  404. Inc(I);
  405. end;
  406. L := ULength(S);
  407. if Integer(L + (I - FBufferOffset)) < 0 then
  408. OutOfMemoryError;
  409. SetLength(S, L + (I - FBufferOffset));
  410. UMove(FBuffer[FBufferOffset], S[L+1], I - FBufferOffset);
  411. FBufferOffset := I;
  412. if FBufferOffset < FBufferSize then begin
  413. { End of line reached }
  414. Inc(FBufferOffset);
  415. if FBuffer[FBufferOffset-1] = #13 then begin
  416. { Skip #10 if it follows #13 }
  417. FillBuffer;
  418. if (FBufferOffset < FBufferSize) and (FBuffer[FBufferOffset] = #10) then
  419. Inc(FBufferOffset);
  420. end;
  421. Break;
  422. end;
  423. end;
  424. if not FSawFirstLine then begin
  425. if UTF8 then begin
  426. { Handle UTF8 as requested: check for a BOM at the start and if not found then check entire file }
  427. if (Length(S) > 2) and (S[1] = #$EF) and (S[2] = #$BB) and (S[3] = #$BF) then begin
  428. Delete(S, 1, 3);
  429. FCodePage := CP_UTF8;
  430. end else begin
  431. var OldPosition := GetPosition;
  432. try
  433. var CappedSize := GetCappedSize; //can't be 0
  434. Seek(0);
  435. var S2: AnsiString;
  436. SetLength(S2, CappedSize);
  437. SetLength(S2, Read(S2[1], CappedSize));
  438. if DetectUTF8Encoding(S2) in [etUSASCII, etUTF8] then
  439. FCodePage := CP_UTF8;
  440. finally
  441. Seek(OldPosition);
  442. end;
  443. end;
  444. end;
  445. FSawFirstLine := True;
  446. end;
  447. Result := S;
  448. end;
  449. { TTextFileWriter }
  450. function TTextFileWriter.CreateHandle(const AFilename: String;
  451. ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  452. ASharing: TFileSharing): THandle;
  453. begin
  454. { faWrite access isn't enough; we need faReadWrite access since the Write
  455. method may read. No, we don't have to do this automatically, but it helps
  456. keep it from being a 'leaky abstraction'. }
  457. if AAccess = faWrite then
  458. AAccess := faReadWrite;
  459. Result := inherited CreateHandle(AFilename, ACreateDisposition, AAccess,
  460. ASharing);
  461. end;
  462. procedure TTextFileWriter.DoWrite(const S: AnsiString; const UTF8: Boolean);
  463. { Writes a string to the file, seeking to the end first if necessary }
  464. const
  465. CRLF: array[0..1] of AnsiChar = (#13, #10);
  466. UTF8BOM: array[0..2] of AnsiChar = (#$EF, #$BB, #$BF);
  467. var
  468. C: AnsiChar;
  469. begin
  470. if not FSeekedToEnd then begin
  471. const LSize = GetSize;
  472. if LSize <> 0 then begin
  473. { File is not empty. Figure out if we have to append a line break. }
  474. Seek(LSize - SizeOf(C));
  475. ReadBuffer(C, SizeOf(C));
  476. case C of
  477. #10: ; { do nothing - file ends in LF or CRLF }
  478. #13: begin
  479. { If the file ends in CR, make it into CRLF }
  480. C := #10;
  481. WriteBuffer(C, SizeOf(C));
  482. end;
  483. else
  484. { Otherwise, append CRLF }
  485. WriteBuffer(CRLF, SizeOf(CRLF));
  486. end;
  487. end else if UTF8 and not FUTF8WithoutBOM then
  488. WriteBuffer(UTF8BOM, SizeOf(UTF8BOM));
  489. FSeekedToEnd := True;
  490. end;
  491. WriteBuffer(Pointer(S)^, ULength(S));
  492. end;
  493. procedure TTextFileWriter.Write(const S: String);
  494. begin
  495. DoWrite(Utf8Encode(S), True);
  496. end;
  497. procedure TTextFileWriter.WriteLine(const S: String);
  498. begin
  499. Write(S + #13#10);
  500. end;
  501. procedure TTextFileWriter.WriteAnsi(const S: AnsiString);
  502. begin
  503. DoWrite(S, False);
  504. end;
  505. procedure TTextFileWriter.WriteAnsiLine(const S: AnsiString);
  506. begin
  507. WriteAnsi(S + #13#10);
  508. end;
  509. { TFileMapping }
  510. var
  511. _RtlNtStatusToDosError: function(Status: NTSTATUS): ULONG; stdcall;
  512. constructor TFileMapping.Create(AFile: TFile; AWritable: Boolean);
  513. const
  514. Protect: array[Boolean] of DWORD = (PAGE_READONLY, PAGE_READWRITE);
  515. DesiredAccess: array[Boolean] of DWORD = (FILE_MAP_READ, FILE_MAP_WRITE);
  516. begin
  517. inherited Create;
  518. if not Assigned(_RtlNtStatusToDosError) then
  519. _RtlNtStatusToDosError := GetProcAddress(GetModuleHandle('ntdll.dll'),
  520. 'RtlNtStatusToDosError');
  521. FMapSize := AFile.CappedSize;
  522. FMappingHandle := CreateFileMapping(AFile.Handle, nil, Protect[AWritable], 0,
  523. FMapSize, nil);
  524. if FMappingHandle = 0 then
  525. TFile.RaiseLastError;
  526. FMemory := MapViewOfFile(FMappingHandle, DesiredAccess[AWritable], 0, 0,
  527. FMapSize);
  528. if FMemory = nil then
  529. TFile.RaiseLastError;
  530. end;
  531. destructor TFileMapping.Destroy;
  532. begin
  533. if Assigned(FMemory) then
  534. UnmapViewOfFile(FMemory);
  535. if FMappingHandle <> 0 then
  536. CloseHandle(FMappingHandle);
  537. inherited;
  538. end;
  539. procedure TFileMapping.Commit;
  540. { Flushes modified pages to disk. To avoid silent data loss, this should
  541. always be called prior to destroying a writable TFileMapping instance -- but
  542. _not_ from a 'finally' section, as this method will raise an exception on
  543. failure. }
  544. begin
  545. if not FlushViewOfFile(FMemory, 0) then
  546. TFile.RaiseLastError;
  547. end;
  548. procedure TFileMapping.ReraiseInPageErrorAsFileException;
  549. { In Delphi, when an I/O error occurs while accessing a memory-mapped file --
  550. known as an "inpage error" -- the user will see an exception message of
  551. "External exception C0000006" by default.
  552. This method examines the current exception to see if it's an inpage error
  553. that occurred while accessing our mapped view, and if so, it raises a new
  554. exception of type EFileError with a more friendly and useful message, like
  555. you'd see when doing non-memory-mapped I/O with TFile. }
  556. var
  557. E: TObject;
  558. begin
  559. E := ExceptObject;
  560. if E is EExternalException then begin
  561. const ExceptionRecord = EExternalException(E).ExceptionRecord;
  562. if (ExceptionRecord.ExceptionCode = EXCEPTION_IN_PAGE_ERROR) and
  563. (Cardinal(ExceptionRecord.NumberParameters) >= Cardinal(2)) then begin
  564. const MemoryStart: PByte = PByte(FMemory);
  565. const MemoryEnd: PByte = MemoryStart + FMapSize;
  566. const FaultAddress: PByte = PByte(ExceptionRecord.ExceptionInformation[1]);
  567. if (FaultAddress >= MemoryStart) and (FaultAddress < MemoryEnd) then begin
  568. { There should be a third parameter containing the NT status code of the error
  569. condition that caused the exception. Convert that into a Win32 error code
  570. and use it to generate our error message. }
  571. if (Cardinal(ExceptionRecord.NumberParameters) >= Cardinal(3)) and
  572. Assigned(_RtlNtStatusToDosError) then
  573. TFile.RaiseError(_RtlNtStatusToDosError(NTSTATUS(ExceptionRecord.ExceptionInformation[2])))
  574. else begin
  575. { Use generic "The system cannot [read|write] to the specified device" errors }
  576. if ExceptionRecord.ExceptionInformation[0] = 0 then
  577. TFile.RaiseError(ERROR_READ_FAULT)
  578. else
  579. TFile.RaiseError(ERROR_WRITE_FAULT);
  580. end;
  581. end;
  582. end;
  583. end;
  584. end;
  585. end.