Shared.FileClass.pas 19 KB

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