Shared.FileClass.pas 19 KB

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