在我转移到Unicode和Delphi 2009之前我有一些代码,它一次将一些文本附加到日志文件中:
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;
但CreateFileA和WriteFile是binary file handlers and are not appropriate for Unicode。
我需要在Delphi 2009下获得相同的功能并能够处理Unicode。
我打开和写入然后关闭每行的文件的原因很简单,其他程序(如写字板)可以打开文件并在写入日志时读取它。
我一直在尝试使用TFileStream和TextWriter,但是关于它们的文档很少,只有很少的例子。
具体来说,我不确定它们是否适合这种不断打开和关闭的文件。此外,我不确定他们是否可以在打开文件时让文件可供阅读。
有没有人知道如何在Delphi 2009或更高版本中执行此操作?
结论:
Ryan的答案是最简单的,也是导致我解决问题的答案。使用他的解决方案,您还必须编写BOM并将字符串转换为UTF8(如我的评论中的答案),然后就可以了。但后来我更进一步研究了TStreamWriter。这相当于同名的.NET函数。它理解Unicode并提供非常干净的代码。
我的最终代码是:
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;
最后,我发现的另一个方面是,如果你追加很多行(例如1000或更多),那么附加到文件的时间会越来越长,效率也会非常低。
所以我最终没有重新创建和释放LogFile。相反,我保持开放,然后非常快。我唯一不能做的就是允许在创建文件时用记事本查看文件。
答案 0 :(得分:4)
为了记录目的,为什么要使用Streams?
为什么不使用TextFiles?这是我的一个日志记录例程的 very 简单示例。
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;
我实际上有一个相当广泛的日志记录单元,它使用关键部分来保证线程安全,可以选择通过OutputDebugString命令用于内部日志记录,也可以通过使用分区标识符来记录指定的代码段。
如果有人有兴趣,我很乐意在这里分享代码单元。
答案 1 :(得分:2)
自D2009以来,字符串和字符串都很宽。因此,您应该使用CreateFile而不是CreateFileA!
如果你使用字符串,你可以使用Length(s)* sizeof(Char)作为字节长度,而不仅仅是Length(s)。因为widechar问题。如果你想编写ansi字符,你应该将s定义为AnsiString或UTF8String,并使用sizeof(AnsiChar)作为乘数。
为什么使用Windows API函数而不是classes.pas?
中定义的TFileStream答案 2 :(得分:1)
尝试一下我为你准备的这个小功能。
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;
另外,试试这个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;
答案 3 :(得分:1)
如果您尝试在多线程应用程序中使用文本文件或Object Pascal类型/非类型文件,那么您将度过难关。
不开玩笑 - (Object)Pascal标准文件I / O使用全局变量来设置文件模式和共享。如果您的应用程序在多个线程(或光纤,如果有人仍然使用它们)中运行,则使用标准文件操作可能会导致访问冲突和不可预测的行为。
由于日志记录的主要目的之一是调试多线程应用程序,因此请考虑使用其他文件I / O方法:Streams和Windows API。
(是的,我知道这不是原来问题的答案,但我不想登录 - 因此我没有评论Ryan J. Mills的实际错误答案的声誉评分。)< / p>