These tests demonstrate how GoBug deals with the
various messages sent to Testbug's window procedure.
The discussion of these tests takes us into the world of arguments on the stack, stack frames, recursion in
the window procedure, and the system's records of window titles. There
is also a demonstration of user messages, the use of SendMessage,
PostMessage, PostThreadMessage, RegisterWindowMessage, WaitForInputIdle and passing messages between threads.
For each demonstration make sure single-step message break is "on",
and you will find it useful to have GoBug's sequential log pane
visible (menu item "view, sequential log").
The breakpoints are VARIOUS_MESSAGES and INTERTHREAD_MESSAGES, which can be set using GoBug's menu item "action, run to .. procedure".
   Closely watching
SETTEXT and GETTEXT messages in the WndProc
   User messages
   Using PostMessage
   Inter-thread messages
Closely watching
SETTEXT and GETTEXT messages in the WndProc
Run to the breakpoint VARIOUS_MESSAGES and be ready to single-step over the code from the breakpoint using F6.
The first message WM_SETTEXT is sent to Testbug's main window to
set the text in the window's title bar.
The very first parameter sent with this message (lParam) contains the
address of the new string. Make a note of this.
Next you will see the message in WndProc as a result of the single-step
message break. Note from the ESP stackpane that the address of the
string is now at [ESP+10h]. Press F5 to enter and execute the code.
Note that after EBP is set the address of the string is at [EBP+14h].
Follow the message into the 4 pushes just before the call to
DefWindowProc. Make a note of ESP just before the call to DefWindowProc.
This denotes the beginning of the stack frame used by the API.
Note also that the first push is [EBP+14h], which is the
orginal lParam sent to SendMessage. So, in effect, the information
given in SendMessage will be sent to DefWindowProc. Now here comes the
interesting bit. When you reach DefWindowProc press F6 to execute but
jump over it. Now - surprise, you are back in WndProc. What has happened
is that DefWindowProc (before returning) has sent the message WM_GETTEXT
to the window procedure. You can see the message in the status bar, and
note also that "recursion=1". Look at the value of ESP. See that is
is now much lower (higher in the stack) than when DefWindowProc was
called. In other words, DefWindowProc made a large stack frame for
local data before sending WM_GETTEXT. Now look at [ESP+10h], which is where lParam is. Note that this an address within the stack frame which was
made by DefWindowProc before sending WM_GETTEXT. Now according to the
SDK the GETTEXT message puts the existing title text into a buffer pointed to by lParam. In other words the existing title text will go into the stack. Presumably
DefWindowProc needs to find this text and its length before processing
the SETTEXT message. Open a data inspector ("inspect, data by address")
and look at the area of stack into the value pointed to by lParam.
Then single-step down to the DefWindowProc and press F6. Now see
that the stack contains the new title text. I suspect what
has happened here is that the existing title text was put on the stack
by DefWindowProc and its size counted when processing WM_GETTEXT, then
it was replaced by the new text which was then counted when processing
WM_SETTEXT. Finally the text was sent to memory somewhere to be kept
as a record of the window title and the title was then drawn directly.
To examine this further lets search the memory. Use the GoBug's menu item
"inspect, search promenade", write the new title text "Various messages test"
in the search control (must have capital "V"), ensure that the combo box
shows a search as an ansi string, make sure that "show search result
window" is checked and click the "search now" button. Sure enough you
will find the new title string in various places in Testbug's memory
and one of them is an area reserved for the User32 heap,
that is the area of memory created by User32.Dll, which will be where
the window titles are kept. Scroll up and down in the search result
inspectors to look at the titles of other windows currently open.
User messages
The second and third messages in the VARIOUS_MESSAGES code are what are called "user messages". These are a very useful way to pass information around your program (to and from functions in the window procedure) particularly if you are using more than one thread. This is because SendMessage does not return until the message has been dealt with. This means that
you can make sure that all user-interface is carried out by the main
thread of your program (which is good Windows practice). When any
secondary thread wants to cause changes to the user-interface, it can
send a message to the main thread. The message cannot be dealt with
by the main thread if it is busy until it has finished what it is doing.
And the secondary thread cannot continue until the main thread has
dealt with the message.
User messages are also very useful to send information and data
from one window to another, particularly where the data needs to be
dealt with by the receiving window immediately (because it may change).
User messages must be 400h. WM_USER has a value of 400h, so
user messages are expressed as WM_USER+value.
Note that some system messages are also above 400h. See GoBug's message list (menu item "action, run to .. message") for a list of messages and values. These messages would be sent by your application to common controls, so hopefully there will be no conflict with your user messages. However, sometimes you may come across such conflict. An example would be the DM_GETDEFID (value 400h) and DM_SETDEFID (value 401h) sent by the IsDialogMessage api. To avoid such conflict you can add a "signature" to the user message. In Testbug's examples, the message WM_USER+0 has the signature "JG" in lParam, and the message WM_USER+20 has the same signature in wParam. You can of course pass data to the window procedure using these two
arguments.
Using PostMessage
SendMessage is fine for sending data and instructions
when the message can or must be dealt with immediately. This is because
SendMessage does not return until the message has been dealt with.
But sometimes this is not what you want at all. You may want to give the information or instruction to the window but return to the system before the
instruction is carried out. An obvious example is when you want to
destroy a window and its class as well (a window class has its own
window procedure) and will take up memory unless it is removed when the
window is destroyed. Now you can't call UnregisterClass from within
the window to be destroyed, but you can post a message to the main
window using PostMessage asking it to call UnregisterClass. The good
thing about PostMessage is that it puts the message in the message
queue and does not send it directly to the window procedure
like SendMessage. Therefore the window procedure does not
receive the message until it is ready and the thread has returned to
the system. So whereas SendMessage calls the window procedure directly
PostMessage simply puts the message in the message queue, to be extracted
by the GetMessage/DespatchMessage loop.
To watch PostMessage in action continue to F6 down in the VARIOUS_MESSAGES code to PostMessage and note that
unlike when using SendMessage, there appears to be no reaction at all to
the call. Single-step down through the rest of the code until the
VARIOUS_MESSAGES function is completely finished, continue past
DefWindowProc in the window procedure until the very last RET which
returns control to the system. Note that the system then calls the
window procedure again several times. Yet it has still not dealt with
the PostMessage call. Only when the system is ready will it do so. You then see the message which you posted being received in the message loop by GetMessage and then appearing in the window procedure having been sent there by DispatchMessage.
PostMessage is further examined belowinter-thread messages.
Inter-thread
messages
These demonstrations use a registered message. A
registered message is unique to the system and
therefore can be used to send a message from one application to
another, however here we merely use the message to pass between threads.
Make sure the single-step message break is "on", go to the breakpoint INTERTHREAD_MESSAGES and single-step from there. You will see the registered message being established, then the new thread being made and passed the id of the current thread (this will be used later so that the new thread can send a message back to the main thread). Since you have
single-stepped over CreateThread when the new thread is made, the
panes "sweep" to show the change of thread.
Continue single-stepping
and watch the progress of the messages. As you do so, you will see
from the sweep how the system divides the processor's time between the threads.
In the code the series of XOR instructions
provide some baffle to help to demonstrate this.
As you continue to single-step you can see that the first
SendMessage appears to have no effect. Why is this? Well, in fact,
the registered message sent by the first SendMessage does get through
to the window procedure. It's just that the system does
not establish GoBug's hook (which catches the messages) for the new
thread until the first API call. Hence the first message is missed by
the hook. This is quite normal, but it is something to watch for
when making new threads under GoBug control. In fact, if it is not already loaded in Testbug's memory space, you will see GoBugSpy.dll showing as loaded there soon after the first API call in the new thread.
The next thing the new thread does is to wait for
the main thread to clear all messages arising from reaching the breakpoint.
This is done by WaitForInputIdle, which is a useful API in sequence
critical situations, or where the user-interface needs to finish its job
before the secondary thread proceeds. Here it is done to show the
passing of messages more clearly. For this reason also, Testbug's
timer was switched off before reaching the breakpoint. You will probably
see that while the new thread is in WaitForInputIdle, the main thread
is given time to complete processing of all outstanding messages.
One thing to note is that the message when using SendMessage
is received in the window procedure by the main thread. The message was sent by the new thread but received by the main thread (check this by single-stepping within the window procedure when the message is received - the sending and calling threads will appear in the log). Because the main thread is used to receive such messages, a window procedure would not need
to process messages from more than one thread at a time, at least not
messages passed by the system.
The same thing happens with PostMessage or PostThreadMessage. The
difference, however is that the message is queued and goes
through the message loop, and therefore is sent to the main thread
only when the system is ready and with nothing else to do. Because the message is queued, it appears in the log as having come from the main thread.
Here is the code:-
INTERTHREAD_MESSAGES:
CMP D[TESTMESSAGE],0 ;see if already have registered testmessage
JNZ >L30 ;yes
PUSH 'TestBug'
CALL RegisterWindowMessageA ;must be named
MOV [TESTMESSAGE],EAX ;same value until Windows restarts
L30:
CALL GetCurrentThreadId
PUSH ADDR Thread2AId
PUSH 0,EAX ;send thread id as a parameter
PUSH ADDR IMMAIN ;address for new thread to start
PUSH 0,0
CALL CreateThread
XOR EAX,EAX
XOR EAX,EAX
XOR EAX,EAX
XOR EAX,EAX
XOR EAX,EAX
XOR EAX,EAX
RET
;
IMMAIN:
PUSH 0,0,[TESTMESSAGE],[hWnd]
CALL SendMessageA
CALL GetCurrentProcess
PUSH -1,EAX ;timeout, handle to debuggee process
CALL WaitForInputIdle ;wait till messages generated by test dealt with
XOR EDX,EDX
XOR EDX,EDX
PUSH 0,0,[TESTMESSAGE],[hWnd]
CALL SendMessageA
XOR EDX,EDX
XOR EDX,EDX
XOR EDX,EDX
XOR EDX,EDX
XOR EDX,EDX
PUSH 0,0,[TESTMESSAGE],[hWnd]
CALL PostMessageA
XOR EDX,EDX
XOR EDX,EDX
XOR EDX,EDX
XOR EDX,EDX
XOR EDX,EDX
XOR EDX,EDX
PUSH 0,0,[TESTMESSAGE],[EBP+0Ch] ;message sent in wParam, ID of the thread
CALL PostThreadMessageA
XOR EDX,EDX
XOR EDX,EDX
XOR EDX,EDX
XOR EDX,EDX
XOR EDX,EDX
XOR EDX,EDX
RET