2010-01-26 36 views
4

我有一些代码之前,我搬到了Unicode和德尔福2009年,在一次追加一些文本到一个日志文件中的行:我怎样才能得到这个文件编写代码使用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, 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

我测试了你的AppendToLogFile和我的两个(非UTF和UTF),并没有看到一个显着的蠕变,因为日志被追加到更大。我想知道为什么。有任何想法吗?一个10-20字节的字符串10K增加大约需要2.4秒,我尝试了所有三个实现。 – 2010-01-27 14:19:52

+0

如果我保留全局TStreamWriter而不是创建并释放它,我的2.4秒会下降到93毫秒。哇。好吧,从现在开始我就是这么做的。好的提示。 – 2010-01-27 14:26:18

+0

@ lkessler-另请参阅此替代方法:http://stackoverflow.com/questions/35710087/how-to-save-classic-delphi-string-to-disk-and-read-them-back/36106740#36106740 – Ampere 2016-03-19 20:45:20

回答

4

进行日志记录,为什么在所有使用流?

为什么不使用TextFiles?这是我的一个日志程序的简单例子。

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的命令的内部记录以及代码记录指定部分。

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

+2

我在Vista和Windows 7上遇到了TextFile问题。关于权限。例如,如何使用文本文件设置共享模式?我认为这可能是全球性的。 – 2010-01-26 17:10:47

+0

@Warren P:FileMode是设置读/写/读写能力的变量,如果我理解正确,也可以用于指定共享兼容性,在调用Append/Reset/Rewrite文件命令之前设置它。 随着Vista和7,你只能在哪里写文件。例如,Windows会阻止你在Program Files文件夹下编写程序,这是一个权限问题。它不一定与使用TextFiles相关,而是OS的一个功能。你确定问题出在TextFiles上吗? – 2010-01-26 20:32:55

+0

这是一个很好的答案。我试过了,但我发现它不会像原来那样编写Unicode。我不得不使用Bob博士在Unicode文本文件输出上所说的内容并写入BOM,然后将S更改为UTFString(S)。请参阅:http://www.bobswart.nl/weblog/Blog.aspx?RootId=5:2975 – lkessler 2010-01-27 03:01:19

2

自D2009以来,字符和字符串都很宽。因此你应该使用CreateFile而不是CreateFileA!

如果你使用字符串,你应该使用Length(s)* sizeof(Char)作为字节长度,而不仅仅是Length(s)。由于widechar问题。如果你想编写ansi字符,你应该将s定义为AnsiString或UTF8String,并使用sizeof(AnsiChar)作为乘数。

为什么使用Windows API函数而不是在classes.pas中定义的TFileStream?

+0

I当我在Delphi 2和Delphi 4之间编写代码时,我不确定TFileStream是否在那里。我必须找到一些使用Windows API例程的代码,并且自从它们工作以后,我就使用它了。 – lkessler 2010-01-26 07:42:48

+0

他可能不想将UTF16写入日志文件。 – 2010-01-26 13:32:54

+0

@Warren:我的新程序现在是Unicode,所以我需要能够将Unicode字符写入日志文件。这是以UTF-16还是UTF-8完成是另一回事,但它不能再是Ans​​i。 – lkessler 2010-01-26 17:06:43

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; 
+0

顺便说一下,我倾向于直接避免使用win32文件API如CreateFile。您在评论中陈述的目标可以使用文件流而非CreateFile轻松实现。 – 2010-01-26 13:51:44

1

如果您尝试在多线程应用程序中使用文本文件或Object Pascal类型化/非类型化文件,则会遇到不好的时间。

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

由于记录的主要目的之一是调试多线程应用程序,因此可以考虑使用其他文件I/O方式:Streams和Windows API。

(是的,我知道这不是对原始问题的回答,但我不希望登录 - 因此,我没有信誉评分来评论Ryan J. Mills实际上错误的答案。)

相关问题