Post by Chris Iverson on Jun 21, 2021 12:26:49 GMT -5
This is something I've stumbled across by accident, and I now suspect that it may be the cause of other headaches we've previously had in LB when dealing with callbacks.
In particular, I suspect that this is the reason that we get stack overflow errors when attempting to use LB callbacks for window procedures.
We've long thought it was because LB was too slow, and the rate of window messages coming in simply overloaded LB. I've had my doubts about that for a while, simply due to the way the message pump in Windows works: your window procedure doesn't get new messages until you ask for them to be dispatched. The message queue is handled by Windows itself, not by your program. In the standard message loop, you call GetMessage() to retrieve the next message from the window queue, and then DispatchMessage() to call your windowproc with that message. With that design, it shouldn't be possible for a program to overload itself simply with window messages, since you don't get the next top-level message from your queue until you ask for it.
There ARE messages that bypass the queue, but they shouldn't cause a stack overflow.
I was doing some debugging around an unexpected crash I was getting with some callback-based code, when I realized something: LB callbacks are not re-entrant. This means, they CANNOT be safely called while one is currently executing.
THIS is what leads to the windowproc crash; there are instances in window message processing that would result in the windowproc being called again, and it's at that point that things break down.
I've done a simple test to prove this; I set up a basic loop of a callback and a DLL function that would recurse for a short bit before returning.
And the DLL this is calling(not full source):
So, with this setup, what I'm expecting is this.
Base level LB calls the function performAdd() for the first time, with the value 1.
performAdd(in the DLL) adds one to the value(now 1), and calls the LB callback with that value.
The LB callback gets the value 2, prints that on the screen, and doing the check, calls performAdd() again, with the new value.
performAdd() gets the value 2, adds 1 to it, and calls the LB callback with the value 3.
LB callback sees the value 3, prints it, and returns it without calling performAdd() again, since it's reached the limit we set.
That three bubbles back up through all the functions, being returned each time. The final value that would be printed is "3".
That's how it SHOULD work, and in fact, testing the DLL with a main program written in C/++ confirms this; the program behaves exactly like that.
C++ console results:
However, in LB, this causes an infinite loop that results in a stack overflow, due to the function calls happening over and over again.
LB console results:
Context is being lost when the callback is called again, which is causing things to fail.
I don't know if this is something you'll actually be able to fix; but I certainly hope so.
I can provide executable versions of the code above for testing, if needed.
EDIT: Correcting the LB code, there was a bug in my code that covered part of the issue.
In particular, I suspect that this is the reason that we get stack overflow errors when attempting to use LB callbacks for window procedures.
We've long thought it was because LB was too slow, and the rate of window messages coming in simply overloaded LB. I've had my doubts about that for a while, simply due to the way the message pump in Windows works: your window procedure doesn't get new messages until you ask for them to be dispatched. The message queue is handled by Windows itself, not by your program. In the standard message loop, you call GetMessage() to retrieve the next message from the window queue, and then DispatchMessage() to call your windowproc with that message. With that design, it shouldn't be possible for a program to overload itself simply with window messages, since you don't get the next top-level message from your queue until you ask for it.
There ARE messages that bypass the queue, but they shouldn't cause a stack overflow.
I was doing some debugging around an unexpected crash I was getting with some callback-based code, when I realized something: LB callbacks are not re-entrant. This means, they CANNOT be safely called while one is currently executing.
THIS is what leads to the windowproc crash; there are instances in window message processing that would result in the windowproc being called again, and it's at that point that things break down.
I've done a simple test to prove this; I set up a basic loop of a callback and a DLL function that would recurse for a short bit before returning.
global lpAddFunc
callback lpAddFunc, doAdd(ulong), ulong
open "LBCallbackAdd\Debug\LBCallbackAdd" for DLL as #lbcba
print "attempting to call function"
CallDLL #lbcba, "performAdd",_
lpAddFunc as ulong,_
1 as long,_
ret as long
print "Returned value: ";ret
close #lbcba
Function doAdd(a)
print "received value: ";a
if a < 3 then
print "calling again"
CallDLL #lbcba, "performAdd",_
lpAddFunc as ulong, a as long,_
ret as long
doAdd = ret
else
print "returning current value"
doAdd = a
end if
print "returning"
End Function
And the DLL this is calling(not full source):
typedef DWORD (__stdcall *LPAddFunc)(DWORD a);
EXTERN_C DWORD __declspec(dllexport) __stdcall performAdd(LPAddFunc addFunc, int a)
{
return addFunc(a+1);
}
So, with this setup, what I'm expecting is this.
Base level LB calls the function performAdd() for the first time, with the value 1.
performAdd(in the DLL) adds one to the value(now 1), and calls the LB callback with that value.
The LB callback gets the value 2, prints that on the screen, and doing the check, calls performAdd() again, with the new value.
performAdd() gets the value 2, adds 1 to it, and calls the LB callback with the value 3.
LB callback sees the value 3, prints it, and returns it without calling performAdd() again, since it's reached the limit we set.
That three bubbles back up through all the functions, being returned each time. The final value that would be printed is "3".
That's how it SHOULD work, and in fact, testing the DLL with a main program written in C/++ confirms this; the program behaves exactly like that.
C++ console results:
Attempting to call function...
Received value: 2
Calling again
Received value: 3
Result: 3
C:\Users\cjnoo\source\repos\CallbackAddTest\Debug\CallbackAddTest.exe (process 2784) exited with code 0.
However, in LB, this causes an infinite loop that results in a stack overflow, due to the function calls happening over and over again.
LB console results:
attempting to call function
received value: 2
calling again
received value: 3
returning current value
returning from function
returning from function
received value: 2
calling again
received value: 3
returning current value
returning from function
returning from function
received value: 2
calling again
received value: 3
returning current value
returning from function
returning from function
...
Context is being lost when the callback is called again, which is causing things to fail.
I don't know if this is something you'll actually be able to fix; but I certainly hope so.
I can provide executable versions of the code above for testing, if needed.
EDIT: Correcting the LB code, there was a bug in my code that covered part of the issue.