Joc02
Joc02

Reputation: 345

Delphi GetLastError with execute packages

When I execute this code without runtime packages, I have a 32 code error, that is correct. But when I activite runtime packages (for exemple just with "FireDACASADriver;YmagControlDB") the error code is always "0"

procedure TForm1.Button1Click(Sender: TObject);
Var
   Stream: TStream;
   iError : integer;
begin
   Stream := nil;
   iError := -1;
   try
      try
         Stream := TFileStream.Create('d:\toto.docx', fmOpenRead);
      except
         begin
            iError := GetLastError;
         end;
      end;
   finally
      if Assigned(Stream) then
         Stream.Free;
   end;
   showmessage('Erreur : ' + inttostr(iError));
end;

How I can fix the GetLastError with runtime packages ?

Upvotes: 0

Views: 385

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 598319

The act of raising an exception can reset the calling thread's error code. It is simply not appropriate to call GetLastError() inside an exception handler.

That being said, if TFileStream fails to open the file, an exception is raised that contains a system-provided error message (but not the actual error code), eg:

procedure TForm1.Button1Click(Sender: TObject);
var
  Stream: TStream;
begin
  try
    Stream := TFileStream.Create('d:\toto.docx', fmOpenRead);
    try
      // use Stream as needed
    finally
      Stream.Free;
    end;
  except
    on E: Exception do
      ShowMessage('Erreur : ' + E.Message);
  end;
end;

If you need access to the error code, you can't use TFileStream, you will have to use CreateFile() directly instead:

procedure TForm1.Button1Click(Sender: TObject);
var
  hFile: THandle;
  iError: DWORD;
begin
  hFile := CreateFile('d:\toto.docx', GENERIC_READ, 0, nil, OPEN_EXISTING, 0, 0);
  if hFile <> INVALID_HANDLE_VALUE then
  begin
    try
      // use hFile as needed...
      //
      // if you need to access the file as a TStream, you can
      // instantiate a THandleStream passing hFile to its constructor...
      //
    finally
      CloseHandle(hFile);
    end;
  end else
  begin
    iError := GetLastError;
    ShowMessage('Erreur : ' + IntToStr(iError));
    if iError = ERROR_SHARING_VIOLATION then
    begin
      // do something...
    end;
  end;
end;

Alternatively:

procedure TForm1.Button1Click(Sender: TObject);
var
  hFile: THandle;
begin
  hFile := CreateFile('d:\toto.docx', GENERIC_READ, 0, nil, OPEN_EXISTING, 0, 0);
  try
    Win32Check(hFile <> INVALID_HANDLE_VALUE);
    try
      // use hFile as needed...
    finally
      CloseHandle(hFile);
    end;
  except
    on E: EOSError do
    begin
      ShowMessage('Erreur : ' + IntToStr(E.ErrorCode));
      if E.ErrorCode = ERROR_SHARING_VIOLATION then
      begin
        // do something...
      end;
    end;
  end;
end;

Upvotes: 1

David Heffernan
David Heffernan

Reputation: 613572

It is simply not appropriate to call GetLastError there. You are mixing two different error handling models.

Call GetLastError immediately after an API call fails, if the documentation says to do so. When you call it, something other function could very well have called SetLastError and reset the value.

So it is wrong to call GetLastError since you aren't using Win32 functions, and should remove the call to GetLastError. Your code should be:

procedure TForm1.Button1Click(Sender: TObject);
var
  Stream: TStream;
begin
  Stream := TFileStream.Create('d:\toto.docx', fmOpenRead);
  try
    // ....    
  finally
    Stream.Free;
  end;
end;

If there is an error, an exception will be raised which will be reported by the top level exception handler.

Runtime packages should have no bearing on how this code executes.

Possible causes of an error are that the file does not exist, or that it is locked.

You wrote:

if Assigned(Stream) then
  Stream.Free;

That is always pointless since the Free method also checks for the object reference being nil. In fact your code is equivalent to:

if Assigned(Stream) then
  if Assigned(Stream) then
    Stream.Destroy;

So it is cleaner to rely on test inside Free and simply write:

Stream.Free;

In the comments you state that you actually want to test whether or not the file is locked. Don't use a file stream for that. Instead do the following:

  • Call CreateFile to open the file.
  • Check the returned handle against INVALID_HANDLE_VALUE to detect error.
  • In case of error use GetLastError to find out the cause of error.
  • Otherwise close the handle with CloseHandle.

However, this is not to be recommended. You might use this approach to determine that the file is not locked, but by the time you try to read it, it has been locked. There is an inherent race condition.

As a general guideline it is better to ask forgiveness than permission.

Upvotes: 5

Related Questions