FileClass.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. unit FileClass;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2024 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 only.
  11. }
  12. {$I VERSION.INC}
  13. interface
  14. uses
  15. Windows, SysUtils, Int64Em;
  16. type
  17. TFileCreateDisposition = (fdCreateAlways, fdCreateNew, fdOpenExisting,
  18. fdOpenAlways, fdTruncateExisting);
  19. TFileAccess = (faRead, faWrite, faReadWrite);
  20. TFileSharing = (fsNone, fsRead, fsWrite, fsReadWrite);
  21. TCustomFile = class
  22. private
  23. function GetCappedSize: Cardinal;
  24. protected
  25. function GetPosition: Integer64; virtual; abstract;
  26. function GetSize: Integer64; virtual; abstract;
  27. public
  28. class procedure RaiseError(ErrorCode: DWORD);
  29. class procedure RaiseLastError;
  30. function Read(var Buffer; Count: Cardinal): Cardinal; virtual; abstract;
  31. procedure ReadBuffer(var Buffer; Count: Cardinal);
  32. procedure Seek(Offset: Cardinal);
  33. procedure Seek64(Offset: Integer64); virtual; abstract;
  34. procedure WriteAnsiString(const S: AnsiString);
  35. procedure WriteBuffer(const Buffer; Count: Cardinal); virtual; abstract;
  36. property CappedSize: Cardinal read GetCappedSize;
  37. property Position: Integer64 read GetPosition;
  38. property Size: Integer64 read GetSize;
  39. end;
  40. TFile = class(TCustomFile)
  41. private
  42. FHandle: THandle;
  43. FHandleCreated: Boolean;
  44. protected
  45. function CreateHandle(const AFilename: String;
  46. ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  47. ASharing: TFileSharing): THandle; virtual;
  48. function GetPosition: Integer64; override;
  49. function GetSize: Integer64; override;
  50. public
  51. constructor Create(const AFilename: String;
  52. ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  53. ASharing: TFileSharing);
  54. constructor CreateWithExistingHandle(const AHandle: THandle);
  55. destructor Destroy; override;
  56. function Read(var Buffer; Count: Cardinal): Cardinal; override;
  57. procedure Seek64(Offset: Integer64); 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: Integer64;
  67. FPosition: Integer64;
  68. function ClipCount(DesiredCount: Cardinal): Cardinal;
  69. protected
  70. procedure AllocMemory(const ASize: Cardinal);
  71. function GetPosition: Integer64; override;
  72. function GetSize: Integer64; 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 Seek64(Offset: Integer64); 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. FUTF8NoPreamble: 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 UTF8NoPreamble: Boolean read FUTF8NoPreamble write FUTF8NoPreamble;
  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. CmnFunc2;
  138. const
  139. SGenericIOError = 'File I/O error %d';
  140. { TCustomFile }
  141. function TCustomFile.GetCappedSize: Cardinal;
  142. { Like GetSize, but capped at $7FFFFFFF }
  143. var
  144. S: Integer64;
  145. begin
  146. S := GetSize;
  147. if (S.Hi = 0) and (S.Lo and $80000000 = 0) then
  148. Result := S.Lo
  149. else
  150. Result := $7FFFFFFF;
  151. end;
  152. class procedure TCustomFile.RaiseError(ErrorCode: DWORD);
  153. var
  154. S: String;
  155. E: EFileError;
  156. begin
  157. S := Win32ErrorString(ErrorCode);
  158. if S = '' then begin
  159. { In case there was no text for the error code. Shouldn't get here under
  160. normal circumstances. }
  161. S := Format(SGenericIOError, [ErrorCode]);
  162. end;
  163. E := EFileError.Create(S);
  164. E.FErrorCode := ErrorCode;
  165. raise E;
  166. end;
  167. class procedure TCustomFile.RaiseLastError;
  168. begin
  169. RaiseError(GetLastError);
  170. end;
  171. procedure TCustomFile.ReadBuffer(var Buffer; Count: Cardinal);
  172. begin
  173. if Read(Buffer, Count) <> Count then begin
  174. { Raise localized "Reached end of file" error }
  175. RaiseError(ERROR_HANDLE_EOF);
  176. end;
  177. end;
  178. procedure TCustomFile.Seek(Offset: Cardinal);
  179. var
  180. I: Integer64;
  181. begin
  182. I.Hi := 0;
  183. I.Lo := Offset;
  184. Seek64(I);
  185. end;
  186. procedure TCustomFile.WriteAnsiString(const S: AnsiString);
  187. begin
  188. WriteBuffer(S[1], Length(S));
  189. end;
  190. { TFile }
  191. constructor TFile.Create(const AFilename: String;
  192. ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  193. ASharing: TFileSharing);
  194. begin
  195. inherited Create;
  196. FHandle := CreateHandle(AFilename, ACreateDisposition, AAccess, ASharing);
  197. if (FHandle = 0) or (FHandle = INVALID_HANDLE_VALUE) then
  198. RaiseLastError;
  199. FHandleCreated := True;
  200. end;
  201. constructor TFile.CreateWithExistingHandle(const AHandle: THandle);
  202. begin
  203. inherited Create;
  204. FHandle := AHandle;
  205. end;
  206. destructor TFile.Destroy;
  207. begin
  208. if FHandleCreated then
  209. CloseHandle(FHandle);
  210. inherited;
  211. end;
  212. function TFile.CreateHandle(const AFilename: String;
  213. ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
  214. ASharing: TFileSharing): THandle;
  215. const
  216. AccessFlags: array[TFileAccess] of DWORD =
  217. (GENERIC_READ, GENERIC_WRITE, GENERIC_READ or GENERIC_WRITE);
  218. SharingFlags: array[TFileSharing] of DWORD =
  219. (0, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE);
  220. Disps: array[TFileCreateDisposition] of DWORD =
  221. (CREATE_ALWAYS, CREATE_NEW, OPEN_EXISTING, OPEN_ALWAYS, TRUNCATE_EXISTING);
  222. begin
  223. Result := CreateFile(PChar(AFilename), AccessFlags[AAccess],
  224. SharingFlags[ASharing], nil, Disps[ACreateDisposition],
  225. FILE_ATTRIBUTE_NORMAL, 0);
  226. end;
  227. function TFile.GetPosition: Integer64;
  228. begin
  229. Result.Hi := 0;
  230. Result.Lo := SetFilePointer(FHandle, 0, @Result.Hi, FILE_CURRENT);
  231. if (Result.Lo = $FFFFFFFF) and (GetLastError <> 0) then
  232. RaiseLastError;
  233. end;
  234. function TFile.GetSize: Integer64;
  235. begin
  236. Result.Lo := GetFileSize(FHandle, @Result.Hi);
  237. if (Result.Lo = $FFFFFFFF) and (GetLastError <> 0) then
  238. RaiseLastError;
  239. end;
  240. function TFile.Read(var Buffer; Count: Cardinal): Cardinal;
  241. begin
  242. if not ReadFile(FHandle, Buffer, Count, DWORD(Result), nil) then
  243. if FHandleCreated or (GetLastError <> ERROR_BROKEN_PIPE) then
  244. RaiseLastError;
  245. end;
  246. procedure TFile.Seek64(Offset: Integer64);
  247. begin
  248. if (SetFilePointer(FHandle, Integer(Offset.Lo), @Offset.Hi,
  249. FILE_BEGIN) = $FFFFFFFF) and (GetLastError <> 0) then
  250. RaiseLastError;
  251. end;
  252. procedure TFile.SeekToEnd;
  253. var
  254. DistanceHigh: Integer;
  255. begin
  256. DistanceHigh := 0;
  257. if (SetFilePointer(FHandle, 0, @DistanceHigh, FILE_END) = $FFFFFFFF) and
  258. (GetLastError <> 0) then
  259. RaiseLastError;
  260. end;
  261. procedure TFile.Truncate;
  262. begin
  263. if not SetEndOfFile(FHandle) then
  264. RaiseLastError;
  265. end;
  266. procedure TFile.WriteBuffer(const Buffer; Count: Cardinal);
  267. var
  268. BytesWritten: DWORD;
  269. begin
  270. if not WriteFile(FHandle, Buffer, Count, BytesWritten, nil) then
  271. RaiseLastError;
  272. if BytesWritten <> Count then begin
  273. { I'm not aware of any case where WriteFile will return True but a short
  274. BytesWritten count. (An out-of-disk-space condition causes False to be
  275. returned.) But if that does happen, raise a generic-sounding localized
  276. "The system cannot write to the specified device" error. }
  277. RaiseError(ERROR_WRITE_FAULT);
  278. end;
  279. end;
  280. { TMemoryFile }
  281. constructor TMemoryFile.Create(const AFilename: String);
  282. var
  283. F: TFile;
  284. begin
  285. inherited Create;
  286. F := TFile.Create(AFilename, fdOpenExisting, faRead, fsRead);
  287. try
  288. AllocMemory(F.CappedSize);
  289. F.ReadBuffer(FMemory^, FSize.Lo);
  290. finally
  291. F.Free;
  292. end;
  293. end;
  294. constructor TMemoryFile.CreateFromMemory(const ASource; const ASize: Cardinal);
  295. begin
  296. inherited Create;
  297. AllocMemory(ASize);
  298. Move(ASource, FMemory^, FSize.Lo);
  299. end;
  300. constructor TMemoryFile.CreateFromZero(const ASize: Cardinal);
  301. begin
  302. inherited Create;
  303. AllocMemory(ASize);
  304. FillChar(FMemory^, FSize.Lo, 0);
  305. end;
  306. destructor TMemoryFile.Destroy;
  307. begin
  308. if Assigned(FMemory) then
  309. LocalFree(HLOCAL(FMemory));
  310. inherited;
  311. end;
  312. procedure TMemoryFile.AllocMemory(const ASize: Cardinal);
  313. begin
  314. FMemory := Pointer(LocalAlloc(LMEM_FIXED, ASize));
  315. if FMemory = nil then
  316. OutOfMemoryError;
  317. FSize.Lo := ASize;
  318. end;
  319. function TMemoryFile.ClipCount(DesiredCount: Cardinal): Cardinal;
  320. var
  321. BytesLeft: Integer64;
  322. begin
  323. { First check if FPosition is already past FSize, so the Dec6464 call below
  324. won't underflow }
  325. if Compare64(FPosition, FSize) >= 0 then begin
  326. Result := 0;
  327. Exit;
  328. end;
  329. BytesLeft := FSize;
  330. Dec6464(BytesLeft, FPosition);
  331. if (BytesLeft.Hi = 0) and (BytesLeft.Lo < DesiredCount) then
  332. Result := BytesLeft.Lo
  333. else
  334. Result := DesiredCount;
  335. end;
  336. function TMemoryFile.GetPosition: Integer64;
  337. begin
  338. Result := FPosition;
  339. end;
  340. function TMemoryFile.GetSize: Integer64;
  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(Pointer(Cardinal(FMemory) + FPosition.Lo)^, Buffer, Result);
  349. Inc64(FPosition, Result);
  350. end;
  351. end;
  352. procedure TMemoryFile.Seek64(Offset: Integer64);
  353. begin
  354. if Offset.Hi and $80000000 <> 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, Pointer(Cardinal(FMemory) + FPosition.Lo)^, Count);
  364. Inc64(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 IsUTF8String(S2) 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. UTF8Preamble: array[0..2] of AnsiChar = (#$EF, #$BB, #$BF);
  478. var
  479. I: Integer64;
  480. C: AnsiChar;
  481. begin
  482. if not FSeekedToEnd then begin
  483. I := GetSize;
  484. if (I.Lo <> 0) or (I.Hi <> 0) then begin
  485. { File is not empty. Figure out if we have to append a line break. }
  486. Dec64(I, SizeOf(C));
  487. Seek64(I);
  488. ReadBuffer(C, SizeOf(C));
  489. case C of
  490. #10: ; { do nothing - file ends in LF or CRLF }
  491. #13: begin
  492. { If the file ends in CR, make it into CRLF }
  493. C := #10;
  494. WriteBuffer(C, SizeOf(C));
  495. end;
  496. else
  497. { Otherwise, append CRLF }
  498. WriteBuffer(CRLF, SizeOf(CRLF));
  499. end;
  500. end else if UTF8 and not FUTF8NoPreamble then
  501. WriteBuffer(UTF8Preamble, SizeOf(UTF8Preamble));
  502. FSeekedToEnd := True;
  503. end;
  504. WriteBuffer(Pointer(S)^, Length(S));
  505. end;
  506. procedure TTextFileWriter.Write(const S: String);
  507. begin
  508. DoWrite(UTF8Encode(S), True);
  509. end;
  510. procedure TTextFileWriter.WriteLine(const S: String);
  511. begin
  512. Write(S + #13#10);
  513. end;
  514. procedure TTextFileWriter.WriteAnsi(const S: AnsiString);
  515. begin
  516. DoWrite(S, False);
  517. end;
  518. procedure TTextFileWriter.WriteAnsiLine(const S: AnsiString);
  519. begin
  520. WriteAnsi(S + #13#10);
  521. end;
  522. { TFileMapping }
  523. type
  524. NTSTATUS = Longint;
  525. var
  526. _RtlNtStatusToDosError: function(Status: NTSTATUS): ULONG; stdcall;
  527. constructor TFileMapping.Create(AFile: TFile; AWritable: Boolean);
  528. const
  529. Protect: array[Boolean] of DWORD = (PAGE_READONLY, PAGE_READWRITE);
  530. DesiredAccess: array[Boolean] of DWORD = (FILE_MAP_READ, FILE_MAP_WRITE);
  531. begin
  532. inherited Create;
  533. if not Assigned(_RtlNtStatusToDosError) then
  534. _RtlNtStatusToDosError := GetProcAddress(GetModuleHandle('ntdll.dll'),
  535. 'RtlNtStatusToDosError');
  536. FMapSize := AFile.CappedSize;
  537. FMappingHandle := CreateFileMapping(AFile.Handle, nil, Protect[AWritable], 0,
  538. FMapSize, nil);
  539. if FMappingHandle = 0 then
  540. TFile.RaiseLastError;
  541. FMemory := MapViewOfFile(FMappingHandle, DesiredAccess[AWritable], 0, 0,
  542. FMapSize);
  543. if FMemory = nil then
  544. TFile.RaiseLastError;
  545. end;
  546. destructor TFileMapping.Destroy;
  547. begin
  548. if Assigned(FMemory) then
  549. UnmapViewOfFile(FMemory);
  550. if FMappingHandle <> 0 then
  551. CloseHandle(FMappingHandle);
  552. inherited;
  553. end;
  554. procedure TFileMapping.Commit;
  555. { Flushes modified pages to disk. To avoid silent data loss, this should
  556. always be called prior to destroying a writable TFileMapping instance -- but
  557. _not_ from a 'finally' section, as this method will raise an exception on
  558. failure. }
  559. begin
  560. if not FlushViewOfFile(FMemory, 0) then
  561. TFile.RaiseLastError;
  562. end;
  563. procedure TFileMapping.ReraiseInPageErrorAsFileException;
  564. { In Delphi, when an I/O error occurs while accessing a memory-mapped file --
  565. known as an "inpage error" -- the user will see an exception message of
  566. "External exception C0000006" by default.
  567. This method examines the current exception to see if it's an inpage error
  568. that occurred while accessing our mapped view, and if so, it raises a new
  569. exception of type EFileError with a more friendly and useful message, like
  570. you'd see when doing non-memory-mapped I/O with TFile. }
  571. var
  572. E: TObject;
  573. begin
  574. E := ExceptObject;
  575. if (E is EExternalException) and
  576. (EExternalException(E).ExceptionRecord.ExceptionCode = EXCEPTION_IN_PAGE_ERROR) and
  577. (Cardinal(EExternalException(E).ExceptionRecord.NumberParameters) >= Cardinal(2)) and
  578. (Cardinal(EExternalException(E).ExceptionRecord.ExceptionInformation[1]) >= Cardinal(FMemory)) and
  579. (Cardinal(EExternalException(E).ExceptionRecord.ExceptionInformation[1]) < Cardinal(Cardinal(FMemory) + FMapSize)) then begin
  580. { There should be a third parameter containing the NT status code of the error
  581. condition that caused the exception. Convert that into a Win32 error code
  582. and use it to generate our error message. }
  583. if (Cardinal(EExternalException(E).ExceptionRecord.NumberParameters) >= Cardinal(3)) and
  584. Assigned(_RtlNtStatusToDosError) then
  585. TFile.RaiseError(_RtlNtStatusToDosError(EExternalException(E).ExceptionRecord.ExceptionInformation[2]))
  586. else begin
  587. { Use generic "The system cannot [read|write] to the specified device" errors }
  588. if EExternalException(E).ExceptionRecord.ExceptionInformation[0] = 0 then
  589. TFile.RaiseError(ERROR_READ_FAULT)
  590. else
  591. TFile.RaiseError(ERROR_WRITE_FAULT);
  592. end;
  593. end;
  594. end;
  595. end.