How to properly work with FileRead, FileWrite, buffers (or with TFileStream).
I need to read a whole text file to a String, then to write back a String to this file
Here are two functions doing what you want:
function StringFromFile(const FileName: TFileName): RawByteString;
var F: THandle;
Size: integer;
begin
result := '';
if FileName='' then
exit;
F := FileOpen(FileName,fmOpenRead or fmShareDenyNone);
if PtrInt(F)>=0 then begin
{$ifdef LINUX}
Size := FileSeek(F,0,soFromEnd);
FileSeek(F,0,soFromBeginning);
{$else}
Size := GetFileSize(F,nil);
{$endif}
SetLength(result,Size);
if FileRead(F,pointer(Result)^,Size)<>Size then
result := '';
FileClose(F);
end;
end;
function FileFromString(const Content: RawByteString; const FileName: TFileName;
FlushOnDisk: boolean=false): boolean;
var F: THandle;
L: integer;
begin
result := false;
F := FileCreate(FileName);
if PtrInt(F)<0 then
exit;
if pointer(Content)<>nil then
L := FileWrite(F,pointer(Content)^,length(Content)) else
L := 0;
result := (L=length(Content));
{$ifdef MSWINDOWS}
if FlushOnDisk then
FlushFileBuffers(F);
{$endif}
FileClose(F);
end;
They use low-level FileOpen/FIleSeek/FileRead/FileWrite functions.
And you can specify any fmShare*
option you need.
It uses a RawByteString
type, so it expects the text to be handled in a bytes-oriented way. It won't work with Unicode text file, but with Ansi Text. You'll have to set the appropriate code page if you want to interact with it using the string type since Delphi 2009.
Before Delphi 2009, just define:
type
RawByteString = AnsiString;
Added requested example of working with FileXXX function family too.
Note on using RawByteString
and file I/O - it is perfectly valid.
Another note: for brevity in the low-level code i omitted some error-checking assertions for less likely occurring errors. Caveat emptor!
const
FileName = 'Unit14.pas'; // this program is a stuntmaster,
// reads and writes its own source
procedure TForm14.FormClick(Sender: TObject);
var
Stream: TFileStream;
Buffer: RawByteString;
begin
Stream := TFileStream.Create(FileName, fmOpenReadWrite or fmShareExclusive);
// read entire file into string buffer
SetLength(Buffer, Stream.Size);
Stream.ReadBuffer(Buffer[1], Stream.Size);
// do something with string
OutputDebugString(PChar(Format('Buffer = "%s"', [Buffer])));
// prepare to write
Stream.Position := 0; // rewind file pointer
Stream.Size := 0; // truncate the file
// write entire string into the file
Stream.WriteBuffer(Buffer[1], Length(Buffer));
Stream.Free;
end;
// on right click - do exactly the same but using low-level FileXXX calls
procedure TForm14.FormContextPopup(Sender: TObject; MousePos: TPoint; var
Handled: Boolean);
var
Handle: Integer;
Size: Cardinal;
Buffer: RawByteString;
Transferred: Integer;
begin
Handle := FileOpen(FileName, fmOpenReadWrite or fmShareExclusive);
Assert(Handle >= 0);
// read entire file into string buffer
Size := GetFileSize(Handle, nil);
Assert(Size <> INVALID_FILE_SIZE);
SetLength(Buffer, Size);
Transferred := FileRead(Handle, Buffer[1], Size);
Assert(not (Transferred < Size));
// do something with string
OutputDebugString(PChar(Format('Buffer = "%s"', [Buffer])));
// prepare to write
FileSeek(Handle, 0, 0); // rewind file pointer
SetEndOfFile(Handle); // truncate the file
// write entire string into the file
Transferred := FileWrite(Handle, Buffer[1], Length(Buffer));
Assert(not (Transferred < Length(Buffer)));
FileClose(Handle);
end;
TStringList
is what you want if you need to deal with the file on a per-line basis.
If you just want to treat it as a single string blob then there is TStringStream
.
Stream := TStringStream.Create('', TEncoding.UTF8);
Try
Stream.LoadFromFile('c:\desktop\in.txt');
ShowMessage(Stream.DataString);
Stream.Clear;
Stream.WriteString('Greetings earthlings!');
Stream.SaveToFile('c:\desktop\out.txt');
Finally
Stream.Free;
End;
The simplest, most fool-proof way to read a file into a string is to use a TStringList like so:
sl := TStringList.Create;
try
sl.LoadFromFile('C:\myfile.txt');
//String can be read and modified using the Text property:
oldString := sl.Text;
sl.Text := 'foo bar';
//Text can be written back to the file using:
sl.WriteToFile('C:\myfile.txt');
finally
sl.Free;
end;
As has been noted in the comments below your question, this could end up transforming line breaks within your string - so depending on what you're trying to read / do, you will need to look out for this.
Some of my utility routines (you can download the code in full from my web site)...
//------------------------------------------------------------------------------
// CsiStrToBytes
//
// Convert pInStr to an array of bytes using the string encoding
// pStringEncoding (one of automatic, Ansi, UTF-16, or UTF-8) and optionally
// include the byte order mark according to the pIncludeBom flag
//------------------------------------------------------------------------------
function CsiStrToBytes(const pInStr: string;
pStringEncoding: TECsiStringEncoding;
pIncludeBom: Boolean): TByteDynArray;
var
{$IFDEF UNICODE}
lStringEncoding: TECsiStringEncoding;
lStringStream: TStringStream;
lPreambleBytes: TBytes;
lStringBytes: TBytes;
lPreambleLen: Integer;
lStringLen: Integer;
{$ENDIF}
lLen: Integer;
{$IFDEF UNICODE}
lIndex: Integer;
{$ENDIF}
begin
if pInStr <> '' then begin
{$IFDEF UNICODE}
if pStringEncoding = seAuto then
lStringEncoding := CsiGetPreferredEncoding(pInStr)
else
lStringEncoding := pStringEncoding;
// UTF-8 and UTF-16 encoding can be handled by the TStringStream class
if (lStringEncoding = seUtf8) or (lStringEncoding = seUtf16) then begin
if lStringEncoding = seUtf8 then
lStringStream := TStringStream.Create(pInStr, TEncoding.Utf8)
else
lStringStream := TStringStream.Create(pInStr, TEncoding.Unicode);
try
// add the UTF-8 or UTF-16 byte order mark to the start of the array of
// bytes if required
if pIncludeBom then
lPreambleBytes := lStringStream.Encoding.GetPreamble
else
SetLength(lPreambleBytes, 0);
lStringBytes := lStringStream.Bytes;
lPreambleLen := Length(lPreambleBytes);
lStringLen := Length(lStringBytes);
SetLength(Result, lPreambleLen + lStringLen);
if lPreambleLen > 0 then
Move(lPreambleBytes[0], Result[0], lPreambleLen);
if lStringLen > 0 then
Move(lStringBytes[0], Result[lPreambleLen], lStringLen);
finally
lStringStream.Free;
end;
end else begin
{$ENDIF}
// Ansi encoding must be handled manually
lLen := Length(pInStr);
SetLength(Result, lLen);
{$IFDEF UNICODE}
for lIndex := 1 to lLen do
Result[lIndex - 1] := Ord(pInStr[lIndex]) and $00ff;
{$ELSE}
Move(pInStr[1], Result[0], lLen);
{$ENDIF}
{$IFDEF UNICODE}
end;
{$ENDIF}
end else
SetLength(Result, 0);
end;
//------------------------------------------------------------------------------
// CsiSaveToFile
//
// Saves pData, an array of bytes, to pFileName
//------------------------------------------------------------------------------
procedure CsiSaveToFile(const pData: TByteDynArray; const pFileName: string);
var
lFileStream: TFileStream;
lLen: Integer;
begin
lFileStream := TFileStream.Create(pFileName, fmCreate);
try
lLen := Length(pData);
if lLen > 0 then
lFileStream.WriteBuffer(pData[0], lLen);
finally
lFileStream.Free;
end;
end;
//------------------------------------------------------------------------------
// CsiSaveToFile
//
// Saves pText to pFileName using the string encoding pStringEncoding, which is
// one of automatic, Ansi, UTF-16, or UTF-8
//------------------------------------------------------------------------------
procedure CsiSaveToFile(const pText: string; const pFileName: string;
pStringEncoding: TECsiStringEncoding);
begin
CsiSaveToFile(CsiStrToBytes(pText, pStringEncoding), pFileName);
end;