Comctl32 components interact with the outside world via messages. Many applications that use comctl32 components expect messages to arrive in a certain order. For some applications, missing, incorrect, or wrongly ordered messages can result in unexpected behavior and lead to crashes under Wine.
We test message sequences by intercepting messages to the controls and recording them. Then we compare the recorded sequence of messages to the expected sequence and report the difference.
+---------------+ messages +----------+ messages +-----------+
| | ----------> | recorder | -----------> | |
| | +----------+ | |
| Parent Window | | Comctl32 |
| | | Component |
| | +----------+ | |
| | <---------- | recorder | <----------- | |
+---------------+ messages +----------+ messages +-----------+
+----------+ messages +---------+
| recorder | ----------> | |
+----------+ | | results
| message | ---------->
+----------+ messages | checker |
| expected | ----------> | |
| messages | | |
+----------+ +---------+
The comctl32 message sequence testing code got checked in on 2007/02/21. The message testing code got factored out into msg.c and msg.h. Run git fetch; git rebase origin or cg update on your source tree to get the new code.
To initialize the sequence testing code, first tell it how many objects we are testing. For instance, with the updown control, there are three components:
#define NUM_MSG_SEQUENCES 3 #define PARENT_SEQ_INDEX 0 #define EDIT_SEQ_INDEX 1 #define UPDOWN_SEQ_INDEX 2 static struct msg_sequence *sequences[NUM_MSG_SEQUENCES];
Other comctl32 components may be simpler because the only components are the parent window and the common control.
Now that we know how many components we have, we can call:
init_msg_sequences(sequences, NUM_MSG_SEQUENCES);
in START_TEST()
Let's look at the create_updown_control function in updown.c:
01 static HWND create_updown_control()
02 {
03 struct subclass_info *info;
04 HWND updown;
05 RECT rect;
06
07 info = HeapAlloc(GetProcessHeap(), 0, sizeof(struct subclass_info));
08 if (!info)
09 return NULL;
10
11 GetClientRect(parent_wnd, &rect);
12 updown = CreateUpDownControl(WS_CHILD | WS_BORDER | WS_VISIBLE | UDS_ALIGNRIGHT,
13 0, 0, rect.right, rect.bottom, parent_wnd, 1, GetModuleHandleA(NULL), edit,
14 100, 0, 50);
15 if (!updown)
16 {
17 HeapFree(GetProcessHeap(), 0, info);
18 return NULL;
19 }
20
21 info->oldproc = (WNDPROC)SetWindowLongA(updown, GWL_WNDPROC,
22 (LONG)updown_subclass_proc);
23 SetWindowLongA(updown, GWL_USERDATA, (LONG)info);
24
25 return updown;
Earlier, we defined struct subclass_info, which is a structure with one member: oldproc.
On line 7, we allocate some memory for our struct from the heap using HeapAlloc. Then we created our updown control as we normally would.
Every window has a Window procedure to handle messages it receives. When no window procedure is specified, the Default window procedure handles the message.
On line 21/22, we use SetWindowLong to assign a new window procedure called updown_subclass_proc to the updown control. SetWindowLong returns the old window procedure which we save in info->oldproc. We then save info to the window's GWL_USERDATA attribute.
updown_subclass_proc(), defined earlier in the code, is the function that records the messages. It fills in a struct message and saves it using add_message(). (in msg.c) Then it calls the control's original window procedure (saved in info->oldproc) to pass on the message. Thsi window procedure will also display the message sequence using trace() messages.
For other controls, the new window procedure will look like updown_subclass_proc. The only things that need to be changed are trace(), and add_message().
+---------------------------------------------------------------+ | message sequence | | | | +---------+ +---------+ +---------+ +---------+ | | | struct | | struct | | struct | | struct | | | | message | | message | | message | | message | | | +---------+ +---------+ +---------+ +---------+ | | | flags: | | flags: | ... | flags: | | flags: | | | | .... | | .... | | .... | | .... | | | +---------+ +---------+ +---------+ +---------+ | | | +---------------------------------------------------------------+
An expected message sequence is a struct message array. For every message in the sequence, specify the message and the message flags. The message flags are:
typedef enum
{
sent = 0x1,
posted = 0x2,
parent = 0x4,
wparam = 0x8,
lparam = 0x10,
defwinproc = 0x20,
beginpaint = 0x40,
optional = 0x80,
hook = 0x100,
winevent_hook =0x200
} msg_flags_t;
multiple flags can be specified by ORing them together. The message flags specify how/what to test. For our testing purposes we only use the following flags:
static const struct message my_msg_seq[] = {
{ WM_WINDOWPOSCHANGING, sent },
{ WM_NCCALCSIZE, sent|wparam, TRUE }, /* check wparam == TRUE */
{ WM_WINDOWPOSCHANGED, sent },
{ WM_SIZE, sent|wparam|defwinproc, SIZE_RESTORED }, /* check wparam == SIZE_RESTORED, this message is sent by DefWindowProc() */
{ WM_IME_SETCONTEXT, sent|lparam|optional, 0, 1 }, /* check lparam == 1, this message may not get sent */
{ 0 } /* indicates end of array */
};
We can use control spy to observe the messages being sent for a given control. When the test executes, any window with our custom window procedure will also display the message sequence and any unexpected messages that might have been missed.
Check the wParam and lParam only when they're consistent and always reproducible. Simply changing the Windows theme can cause some values to vary. Changing the theme from "Windows classic" to the modern XP these will even result in some new messages getting send. (Label these optional)
To verify the message sequences, first we take some action, like creating the control or sending it a message. Then we use ok_sequence() to verify the recorded sequence. A call to ok_sequence() looks like:
ok_sequence(seq, index, expected, context, todo)
where:
Afterwards, we may want to use flush_sequences() to clear the recording before recording the next sequence.
/~leiz/software/wine/cs130_msgseq.php last updated on Tue Feb 27 2007