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 ?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With