7

I am trying to record the session log from other applications (Proxifier) to a Memo. I've tried using the command :

procedure TForm1.TimerTimer(Sender: TObject);
begin
  Memo1.Lines.LoadFromFile('C:\PMASSH\Proxyfier\Profiles\Log.txt');
end;

but at certain times I get an error

enter image description here

Can you help my problem above ? I would really appreciate of all the answers.

Thanks

TLama
  • 75,147
  • 17
  • 214
  • 392

4 Answers4

7

The other program has opened the file with a sharing mode that does not allow other processes to read it. Typically this happens when the other application is writing to the file.

There's not a whole lot you can do about this. This is perfectly normal behaviour, and is to be expected. You can try detecting the error, waiting for a short period of time, and re-trying.

Since you are already running this on a timer, the re-try will just happen. So perhaps you just need to suppress those exceptions:

procedure TForm1.TimerTimer(Sender: TObject); 
begin
  try
    Memo1.Lines.LoadFromFile(...);
  except
    on EFOpenError do
      ; //swallow this error
  end;
end;

Note that detecting EFOpenError is perhaps a little crude. Perhaps there are other failure modes that lead to that error. However, as a first pass, the code above is a decent start.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • or is there another way that I can remove the error message? because when I close the error window is still running normally. Thanks – Pujangga Adhitya Nov 23 '12 at 17:45
  • 1
    In the try...except block you would want to handle "EFOpenError" specifically (and avoid the pitfall of silencing all exceptions by not being specific). Depending on your UI, you might want to give the user feedback depending on how many unsuccessful attempts, or how long it's been since the log was successfully opened. – Argalatyr Nov 23 '12 at 17:48
  • Can you help command what can I use? I am just a beginner trying to learn to program using Delphi. Thanks :) – Pujangga Adhitya Nov 23 '12 at 17:53
  • @David Heffernan Thanks, You saved my day (y) – Pujangga Adhitya Nov 23 '12 at 18:20
5

David's answer is correct. I just want to clarify why this is happening.

The answer lies in the code:

procedure TStrings.LoadFromFile(const FileName: string);
var
  Stream: TStream;
begin
  Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
end;

as you can see, the file is accessed for sharing but no writing is allowed. you can solve this by creating the filestream yourself:

Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);

and then use the Lines.LoadFromStream() method to load the contents into the memo

Please note that the problem may subsist in cases where the other application has opened the file in exclusive mode (i.e. no sharing), so proper Exception management like in David's answer is still needed.

whosrdaddy
  • 11,720
  • 4
  • 50
  • 99
  • I'm not saying his answer is wrong, and of course there should be an exeption management ;) But as far as i experimented, even if i created a file with share mode at writer side, i could not be able to open it for reading with LoadFromFile method. That's why i suggested WinAPI directly. – Hasan Manzak Nov 23 '12 at 20:44
  • @The which is why the code in this answer does not call LoadFromFile. – David Heffernan Nov 23 '12 at 21:38
  • @The_aLiEn that is exactly the point of my answer, don't use the LoadFromFile method... – whosrdaddy Nov 23 '12 at 22:06
2

You can try your luck with ReadFile WinAPI. On a shared read open mode, you'll be able to sneak and read the contents of the file at last file buffer flush. If that another application (Proxifier) opened the file with CreateFile WinAPI with FILE_SHARE_READ share mode then you'll be able to open it for reading, as long as you use ReadFile API. Standart LoadFromFile method won't work here if it still was opened for share, and you'll get the same 'lock' error.

But here's the catch.. You'll have to deal with buffers, sizes and handles... You'll have to assing a handle to file for reading, get the file size with that handle, set an array with that size, do read to that array and assign, add (whatever) that array to the memo.. Pure usage of WinAPI. Some job for a simple task...

Here is a basic example of how to deal with files with WinAPI:

The key assumption of that other application's file open process:

var
  Form1: TForm1;
  logfile: Textfile;
  h: THandle;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin    
  // AssignFile(logfile, 'c:\deneme.txt');
  // Rewrite(logfile);

  h := CreateFile('C:\deneme.txt', GENERIC_WRITE, FILE_SHARE_READ, nil,
    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);

  Timer1.enabled := true;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Timer1.enabled := false;

  // CloseFile(logfile);
  CloseHandle(h);
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  str: AnsiString;
  p: pointer;
  buf: array of ansichar;
  written: cardinal;
begin
  // Writeln(logfile, 'denemeStr');

  str := 'denemeStr' + #13#10;
  p := pansichar(str);

  SetLength(buf, length(str));
  move(p^, buf[0], length(str));

  WriteFile(h, buf[0], length(buf), written, nil);
  FlushFileBuffers(h);
end;

And if it's been shared for reading, this is how you can read from it:

procedure TForm1.Button1Click(Sender: TObject);
var
  h: THandle;
  buf: array of ansichar;
  size, read: cardinal;
begin
  Memo1.Lines.Clear;
  // Memo1.Lines.LoadFromFile('c:\deneme.txt');

  h := CreateFile('C:\deneme.txt', GENERIC_READ, FILE_SHARE_READ, nil,
    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  size := GetFileSize(h, nil);
  SetLength(buf, size);

  ReadFile(h, buf[0], size, read, nil);
  CloseHandle(h);
  Memo1.Lines.Add(pansichar(buf));
end;

Hope this'd help...

Hasan Manzak
  • 663
  • 6
  • 14
-1
procedure TForm1.TimerTimer(Sender: TObject);
begin
  Memo1.Lines.LoadFromFile('C:\PMASSH\Proxyfier\Profiles\Log.txt');//// read file path error if file notfound
// if trying to record 
  Memo1.Lines.SaveToFile(Path...);
end;
tomrozb
  • 25,773
  • 31
  • 101
  • 122