I received a comment on this post: Will GetLastError ever work properly in VFP8.0?. I was consistently getting GetLastError() values that were correct in both VFP8 and VFP9. The reader comment said that he was getting an unexpected value of 0 in VFP8 even though he was expecting a failure value with an invalid password.
I knew that the problem was at least one intervening Win32API call that was resetting the GetLastError() value.
So I opened up trusty Visual Studio debugger and put a breakpoint on the call to LogonUserA . It’s a little complicated breakpoint syntax that indicates the dll name and the decorated API name:
I can see the GetLastError value in the debugger by putting this in the watch window:
This can be gleaned from stepping into the assembly code for the GetLastError function.
BTW, offset 0x24 will show you the current thread ID (*(int *)(@tib+0x24)). For further info on Thread Information Block (TIB), see Matt Pietrek’s still pertinent 10 year old article on TIB
For the address of the GetLastError value, I just precede it with the Address Of operator:
which gives me a hex value. So I put a data breakpoint on when that value changes. (I also could have set a break point on SetLastError)
When I continued after the LogonUser call, sure enough a breakpoint occurred showing code that sets the last error to 1326, as expected.
D:\>net helpmsg 1326
Logon failure: unknown user name or bad password.
This is the call stack when the GetLastError was set to 1326. You can see the parameter 1326 in the call stack.
> ntdll.dll!RtlRestoreLastWin32Error(1326) Line 239 C
advapi32.dll!BaseSetLastNTError(-1073741715) Line 56 C
advapi32.dll!LogonUserCommonW(0x00185458, 0x00184820, 0x00184838, 4, 3, 0, 0x0012d7f4, 0x00000000, 0x00000000, 0x00000000, 0x00000000) Line 1350 C
advapi32.dll!LogonUserCommonA(0x01ad6c9c, 0x01acf974, 0x01acf5e4, 4, 0, 0, 0x0012d7f4, 0x00000000, 0x00000000, 0x00000000, 0x00000000) Line 917 + 0x26 C
advapi32.dll!LogonUserA(0x01ad6c9c, 0x01acf974, 0x01acf5e4, 4, 0, 0x0012d7f4) Line 981 C
I hit F5 to continue from LogonUser, and, as expected, I saw an API call that set the value to 0: This was a call to TlsGetValue
> kernel32.dll!TlsGetValue(16) Line 2303 C
MSCTF.dll!GetSYSTHREAD() Line 169 + 0x7 C++
MSCTF.dll!SysGetMsgProc(0, 1, 1244208) Line 2598 + 0xb C++
user32.dll!DispatchHookW(196608, 1, 1244208, 0x7472c2b8) Line 395 C
user32.dll!CallHookWithSEH(0x0012fc20, 0x0012fc30, 0x0012fc4c, 0) Line 64 + 0x11 C
user32.dll!__fnHkINLPMSG(0x0012fc20) Line 4150 C
ntdll.dll!_KiUserCallbackDispatcher@12() Line 157 Asm
user32.dll!NtUserPeekMessage(1244424, 0, 0, 0, 1) Line 3899 C
user32.dll!PeekMessageA(0x0012fd08, 0x00000000, 0, 0, 1) Line 668 + 0x16 C
The VFP process was looking for any Windows messages to be processed. Why does it do that?
Suppose you had an infinite loop in VFP code. Hitting a key causes a Windows message to be sent to VFP, but it won’t be processed if there’s an infinite loop occurring. So between execution of every VFP statement, VFP will check to see if there are any pending Windows Messages to be processed. You can disable this behavior by setting _VFP.AutoYield to 0. Then the PeekMessage doesn’t happen and the expected value of 1326 is returned in VFP8
Although this will prevent the PeekMessage from occurring, there can be other API calls occurring between the execution of VFP statements and thus you cannot rely on GetLastError in VFP8. VFP internally may need to allocate memory or read from disk.or update a window.
That’s why I modified GetLastError behavior in VFP9 to be reliable for Declare DLL calls.
More about AutoYield: How does Task Manager determine if an Application is Not Responding?