Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does GetLastError return 0 when it's called in a DLL library?

Assume I'm having a DLL library with this pseudo-code:

var
  LastError: DWORD;

procedure DoSomethingWrong; stdcall;
var
  FileStream: TFileStream;
begin
  try
    FileStream := TFileStream.Create('?', fmCreate);
  except
    on EFCreateError do
      LastError := GetLastError; // <- why does GetLastError return 0 here ?
  end;
end;

Why does GetLastError function return 0 when it's used in a DLL library like shown above ? Is there a way to get the last error code for this case ?

like image 855
TLama Avatar asked Mar 15 '13 17:03

TLama


Video Answer


2 Answers

Your call to GetLastError returns 0 because there are other APIs called after CreateFile returns, and your exception code executes.

The error code returned by GetLastError a thread local variable and is shared between all code that runs in your thread. So in order to capture the error code you need to call GetLastError immediately after the failed function returns.

The documentation explains it like this:

Functions executed by the calling thread set this value by calling the SetLastError function. You should call the GetLastError function immediately when a function's return value indicates that such a call will return useful data. That is because some functions call SetLastError with a zero when they succeed, wiping out the error code set by the most recently failed function.

If you are using TFileStream.Create then the framework doesn't give you an opportunity to call GetLastError at the suitable moment. If you really want to get that information you will have to call CreateFile yourself and use a THandleStream instead of TFileStream.

The idea there is that with THandleStream you are responsible for the synthesis of the file handle which you pass to the constructor of THandleStream. That gives you the opportunity to capture the error code in case of failure.

like image 117
David Heffernan Avatar answered Oct 22 '22 11:10

David Heffernan


At a higher level, the real issue with this code is that it is mixing models. You're attempting to create or open a file with one system (VCL TStream system) but you're testing for errors produced by a different system (Win32 API).

The only way you can rely on the Win32 GetLastError result is if you are calling Win32 functions yourself. Why? Because that's the only way to ensure that there are no other calls to Win32 functions between your Win32 function call and your call to GetLastError. Every Win32 API call has the potential to (re)set GetLastError.

Even though VCL sits on top of Win32, there is plenty of opportunity for some other Win32 API call to occur between when the error occurs and when the exception reaches your handler. Even if things worked fine today, some future change in the VCL implementation could easily disrupt the happy coincidence that is the current situation.

The best way to avoid this "hang time" where the data you need is vulnerable to being overwritten is to have the GetLastError value captured as close to the point of failure as possible and incorporated into a property of the VCL exception object. This would all but eliminate the risk of some innocent interloper between your exception handler and the point of failure obliterating the GetLastError global state.

like image 43
dthorpe Avatar answered Oct 22 '22 12:10

dthorpe