How can I get this file writing code to work with Unicode (Delphi)

I had some code before I switched to Unicode and Delphi 2009, which attached text in a line to a text file:

procedure AppendToLogFile(S: string); // this function adds our log line to our shared log file // Doing it this way allows Wordpad to open it at the same time. var F, C1 : dword; begin if LogFileName <> '' then begin F := CreateFileA(Pchar(LogFileName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0); if F <> 0 then begin SetFilePointer(F, 0, nil, FILE_END); S := S + #13#10; WriteFile(F, Pchar(S)^, Length(S), C1, nil); CloseHandle(F); end; end; end; 

But CreateFileA and WriteFile are binary processors and are not suitable for Unicode .

I need to get something that needs to be done in Delphi 2009 and be able to handle Unicode.

The reason I open and write and then close the file for each line is simply because other programs (such as WordPad) can open the file and read it while writing a log.

I experimented with TFileStream and TextWriter, but the documentation for them is very small and a few examples.

In particular, I'm not sure if they are suitable for this constant opening and closing of a file. Also, I'm not sure if they can make the file readable while it is open for writing.

Does anyone know how I can do this in Delphi 2009 or later?


Output:

Ryan's answer was the simplest, and the one that led me to my decision. With his solution, you also need to write a specification and convert the string to UTF8 (as in my comment on his answer), and then it worked fine.

But then I took another step and explored TStreamWriter. This is the equivalent of a .NET function with the same name. It understands Unicode and provides very clean code.

My last code is:

 procedure AppendToLogFile(S: string); // this function adds our log line to our shared log file // Doing it this way allows Wordpad to open it at the same time. var F: TStreamWriter; begin if LogFileName <> '' then begin F := TStreamWriter.Create(LogFileName, true, TEncoding.UTF8); try F.WriteLine(S); finally F.Free; end; end; 

Finally, another aspect that I discovered is that you add a lot of lines (for example, 1000 or more), then adding to the file takes longer and longer, and it becomes quite inefficient.

So, I ended up not re-creating or releasing the LogFile every time. Instead, I keep it open, and then it is very fast. The only thing I can not do is allow the file to be viewed with notepad during its creation.

+4
source share
4 answers

For logging purposes, why use Streams at all?

Why not use TextFiles? Here is a very simple example of one of my logging procedures.

 procedure LogToFile(Data:string); var wLogFile: TextFile; begin AssignFile(wLogFile, 'C:\MyTextFile.Log'); {$I-} if FileExists('C:\MyTextFile.Log') then Append(wLogFile) else ReWrite(wLogFile); WriteLn(wLogfile, S); CloseFile(wLogFile); {$I+} IOResult; //Used to clear any possible remaining I/O errors end; 

In fact, I have a fairly extensive logging unit, which uses critical sections to ensure thread safety, can be additionally used for internal logging through the OutputDebugString command, as well as for registering certain sections of code using section identifiers.

If anyone is interested, I would love to share the code block here.

+4
source

Char, and the string is D2009. So you should use CreateFile instead of CreateFileA!

If you are using the string you are using, use Length (s) * sizeof (Char) as the byte length, not just Length (s). due to a wide range of issues. If you want to write ansi characters, you must define s as AnsiString or UTF8String and use sizeof (AnsiChar) as a multiplier.

Why are you using the Windows API function instead of TFileStream defined in classes.pas?

+2
source

Try this little function that I whipped just for you.

 procedure AppendToLog(filename,line:String); var fs:TFileStream; ansiline:AnsiString; amode:Integer; begin if not FileExists(filename) then amode := fmCreate else amode := fmOpenReadWrite; fs := TFileStream.Create(filename,{mode}amode); try if (amode<>fmCreate) then fs.Seek(fs.Size,0); {go to the end, append} ansiline := AnsiString(line)+AnsiChar(#13)+AnsiChar(#10); fs.WriteBuffer(PAnsiChar(ansiline)^,Length(ansiline)); finally fs.Free; end; 

Also try this version of UTF8:

 procedure AppendToLogUTF8(filename, line: UnicodeString); var fs: TFileStream; preamble:TBytes; outpututf8: RawByteString; amode: Integer; begin if not FileExists(filename) then amode := fmCreate else amode := fmOpenReadWrite; fs := TFileStream.Create(filename, { mode } amode, fmShareDenyWrite); { sharing mode allows read during our writes } try {internal Char (UTF16) codepoint, to UTF8 encoding conversion:} outpututf8 := Utf8Encode(line); // this converts UnicodeString to WideString, sadly. if (amode = fmCreate) then begin preamble := TEncoding.UTF8.GetPreamble; fs.WriteBuffer( PAnsiChar(preamble)^, Length(preamble)); end else begin fs.Seek(fs.Size, 0); { go to the end, append } end; outpututf8 := outpututf8 + AnsiChar(#13) + AnsiChar(#10); fs.WriteBuffer(PAnsiChar(outpututf8)^, Length(outpututf8)); finally fs.Free; end; end; 
+1
source

If you try to use a text file or Object Pascal for typed / untyped files in a multi-threaded application, you will have a bad time.

No kidding - standard input / output (Object) Pascal uses global variables to set file mode and share. If your application runs on multiple threads (or fibers, if they still use them), using standard file operations can lead to access violations and unpredictable behavior.

Since one of the main goals of logging is to debug a multi-threaded application, consider using other file I / O: streams and the Windows API.

(And yes, I know that this is not quite the answer to the original question, but I do not want to log in, so I do not have a reputation score to comment on Ryan J. Mills practically the wrong answer.)

+1
source

Source: https://habr.com/ru/post/1299408/


All Articles