如何让这个文件编写代码与Unicode(Delphi)一起使用

时间:2010-01-26 07:01:23

标签: delphi logging unicode file-io

在我转移到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。相反,我保持开放,然后非常快。我唯一不能做的就是允许在创建文件时用记事本查看文件。

4 个答案:

答案 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>