Как я могу получить этот код для записи файлов для работы с Unicode (Delphi) - PullRequest
4 голосов
/ 26 января 2010

У меня был какой-то код, прежде чем я перешел на 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 являются обработчиками двоичных файлов и не подходят для Unicode .

Мне нужно что-то сделать, чтобы сделать эквивалент в Delphi 2009 и уметь обрабатывать Unicode.

Причина, по которой я открываю и пишу, а затем закрываю файл для каждой строки, заключается просто в том, что другие программы (например, WordPad) могут открывать файл и читать его во время записи журнала.

Я экспериментировал с TFileStream и TextWriter, но по ним очень мало документации и мало примеров.

В частности, я не уверен, подходят ли они для этого постоянного открытия и закрытия файла. Также я не уверен, смогут ли они сделать файл доступным для чтения, пока они открыты для записи.

Кто-нибудь знает, как я могу сделать это в Delphi 2009 или более поздней версии?


Вывод:

Ответ Райана был самым простым и тот, который привел меня к моему решению. С его решением вы также должны написать спецификацию и преобразовать строку в 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 ]

4 голосов
/ 26 января 2010

Для целей регистрации зачем вообще использовать Streams?

Почему бы не использовать 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 голосов
/ 26 января 2010

Символ и строка широки с D2009. Таким образом, вы должны использовать CreateFile вместо CreateFileA!

Если вы пишете строку, вы должны использовать Length (s) * sizeof (Char) в качестве длины байта, а не только Length (s). из-за проблемы с широкими чарами. Если вы хотите написать ansi-символы, вы должны определить s как AnsiString или UTF8String и использовать sizeof (AnsiChar) в качестве множителя.

Почему вы используете функцию Windows API вместо TFileStream, определенной в classes.pas?

1 голос
/ 25 июня 2013

Если вы попытаетесь использовать текстовые файлы или файлы, напечатанные на языке Object Pascal / нетипизированные в многопоточном приложении, у вас будет плохое время.

Без шуток - стандартный файловый ввод / вывод (Object) Pascal использует глобальные переменные для установки режима файла и общего доступа. Если ваше приложение работает в более чем одном потоке (или fiber , если кто-то еще их использует), использование стандартных файловых операций может привести к нарушениям доступа и непредсказуемому поведению.

Поскольку одной из основных целей ведения журнала является отладка многопоточного приложения, рассмотрите возможность использования других средств файлового ввода-вывода: потоков и API Windows.

(И да, я знаю, что это не совсем ответ на первоначальный вопрос, но я не хочу входить в систему - поэтому у меня нет репутации, чтобы комментировать практически неправильный ответ Райана Дж. Миллса.)

1 голос
/ 26 января 2010

Попробуйте эту маленькую функцию, которую я выбрал только для вас.

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;
...