代码之家  ›  专栏  ›  技术社区  ›  lkessler

如何让这个文件编写代码与Unicode(Delphi)一起工作?

  •  4
  • lkessler  · 技术社区  · 15 年前

    在转到Unicode和Delphi2009之前,我有一些代码,它们一次在日志文件的一行中附加了一些文本:

    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 .

    我需要在Delphi2009下做一些类似的工作,并且能够处理Unicode。

    我为每一行打开并写入然后关闭文件的原因很简单,其他程序(如Wordpad)可以在写入日志时打开并读取文件。

    我一直在尝试tfilestream和textwriter,但它们的文档很少,示例也很少。

    具体地说,我不确定它们是否适合这个不断打开和关闭的文件。另外,我不确定他们是否可以在打开文件进行写入时使其可供读取。

    有人知道我如何在Delphi2009或更高版本中做到这一点吗?


    结论:

    Ryan的回答是最简单的,也是引导我找到解决方案的答案。使用他的解决方案,您还必须编写BOM并将字符串转换为utf8(如我对他的答案的评论中所述),然后这就可以正常工作了。

    但后来我又进一步调查了特斯特里默。这相当于同名的.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行或更多行),那么附加到文件的时间会越来越长,而且效率会变得非常低。

    所以每次都没有重新创建和释放日志文件。相反,我打开它,然后它很快。我唯一不能做的就是允许在创建文件时使用记事本查看文件。

    4 回复  |  直到 12 年前
        1
  •  4
  •   lkessler    15 年前

    出于日志记录的目的,为什么要使用流?

    为什么不使用文本文件?这里是一个 非常 我的一个日志记录例程的简单示例。

    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;
    

    实际上,我有一个相当广泛的日志记录单元,它使用关键部分来保证线程安全,还可以选择通过outputdebugtring命令用于内部日志记录,以及通过使用部分标识符来记录指定的代码部分。

    如果有人感兴趣,我很乐意在这里分享代码单元。

        2
  •  2
  •   Ritsaert Hornstra    15 年前

    字符和字符串从d2009开始就很宽。因此,您应该使用createfile而不是createfilea!

    如果是字符串,则应使用长度*sizeof(char)作为字节长度,而不仅仅是长度。因为宽字符问题。如果要编写ansi字符,则应将s定义为ansistring或utf8string,并使用sizeof(ansichar)作为乘数。

    为什么要使用windows api函数而不是classes.pas中定义的tfilestream?

        3
  •  1
  •   Warren P    15 年前

    试试我为你准备的这个小功能。

    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;
    
        4
  •  1
  •   mg30rg    12 年前

    如果您试图在多线程应用程序中使用文本文件或对象pascal类型化/非类型化文件,那么您将度过一段糟糕的时光。

    不开玩笑-Pascal标准文件I/O使用 全局变量 设置文件模式和共享。如果应用程序运行在多个 线 (或) 纤维 如果仍有人使用它们)使用标准文件操作可能导致访问冲突和不可预测的行为。

    由于日志记录的主要目的之一是调试多线程应用程序,请考虑使用文件I/O的其他方法:流和Windows API。

    (是的,我知道这不是对最初问题的回答,但我不想登录-因此我没有声誉评分来评论Ryan J.Mills的错误答案。)