§ ¶Another common controls V6 bug
Here is a stack trace of a Win32 program being debugged under WinDbg. Can you guess what the bug is from this stack trace?
ChildEBP RetAddr Args to Child
0018f470 70582d28 00000000 00000000 00000001 USER32!NtUserInvalidateRect+0x1
0018f48c 7054ff2f 069faf50 00000000 00000004 COMCTL32!Tab_OnHScroll+0x4e
0018f4cc 705284ec 069faf50 00000001 0018f524 COMCTL32!Tab_UpdateArrows+0xe4
0018f4dc 705284cb 069faf50 00000000 7052723b COMCTL32!Tab_Size+0x17
0018f524 766362fa 00a6096a 00000005 00000000 COMCTL32!Tab_WndProc+0x18f
0018f550 76636d3a 7052723b 00a6096a 00000005 USER32!InternalCallWinProc+0x23
0018f5c8 766390c9 00000000 7052723b 00a6096a USER32!UserCallWinProcCheckWow+0x109
0018f658 76636a8c 05569460 00000000 7052723b USER32!RealDefWindowProcWorker+0x622
0018f678 76636ae9 00a6096a 00000047 00000000 USER32!RealDefWindowProcW+0x4a
0018f6c0 7052734c 00a6096a 00000047 00000000 USER32!DefWindowProcW+0x6f
0018f714 766362fa 00a6096a 00000047 00000000 COMCTL32!Tab_WndProc+0x399
0018f740 76636d3a 7052723b 00a6096a 00000047 USER32!InternalCallWinProc+0x23
0018f7b8 76636de8 00000000 7052723b 00a6096a USER32!UserCallWinProcCheckWow+0x109
0018f814 76638fa7 05569460 00000000 00000047 USER32!DispatchClientMessage+0xe0
0018f850 7793010a 0018f868 00000000 0018f9c4 USER32!__fnINLPWINDOWPOS+0x2c
0018f864 05569460 00000000 00000047 00000000 ntdll!KiUserCallbackDispatcher+0x2e
The bug is that the tab control in the common controls library (comctl32) is invalidating the desktop window via InvalidateRect(NULL, NULL, TRUE), which causes the entire screen to redraw and flicker. This is noticeable with Visual Studio's slow redraw, and one of the first things I do to track it down is to set a conditional breakpoint for InvalidateRect(NULL). In this case, it immediately pointed to the tab control as the culprit.
From what I've been able to tell, this happens if you create a tab control at 0x0 size, add tabs to it, select a tab, and then resize it to a non-empty size. This appears to be yet another comctl32 v6 bug since removing the manifest and using v5.80 makes the bug go away. I'm beginning to regret enabling visual styles since this is about the fourth v6-specific bug that I've hit.
If you're wondering about the reason for such a weird chain of events, it was because of some deferred layout code. When bringing up a complex UI or handling resizing, it's best to try to do all window operations at once to avoid redundant redraws, which slows down the relayout and can add unnecessary flickering. This means using dirty flags to avoid redundant layout operations, using DeferWindowPos() in Win32 to batch window operations and keeping windows hidden until they're in the correct position. Unfortunately, in this case it triggered the tab control bug by delaying the first resize until after tabs had already been added. The workaround is simple: create the tab control as 1x1 instead of 0x0.
What versions of Windows did you see this bug on? On the other hand, comctl32 v5 have it's own share of bugs:
Yuhong Bao (link) - 05 02 13 - 08:34
I wouldn't call this a bug, I would call it wrong usage. A control without size isn't really a control.
Unless you use those hidden controls like the good old visual basic inet-control - but these can't be resized, can they?
McMurmel - 06 02 13 - 08:03
In my experience, window controls have to handle arbitrarily bad sizes. They may be non-functional in that size and they may have severe drawing artifacts, but they can't blow up because they get temporarily sized to 1x1 or 0x0. If they do, they make layout fragile, as the layout system simply can't guarantee that the minimum requested size is always given. For instance, what happens if the desktop resolution suddenly gets lowered to 320x200 because a game launches, and the docked panels no longer fit, or a gridding algorithm has to squeeze everything down to a teeny size?
More specific to Win32 is the problem that you _cannot_ prevent a window from being resized to an arbitrary size, period. Intercepting WM_WINDOWPOSCHANGING sort of works, but in my experience Aero Snap breaks it and can override size bounding or aspect ratio logic that you try to implement. Window controls simply must degrade gracefully.
Finally, even if a control isn't intended to work at 0x0, I can't think of any good reason why a control should invalidate the desktop window via a null handle. That's simply broken.
Phaeron - 06 02 13 - 14:51
> More specific to Win32 is the problem that you _cannot_ prevent a window from being resized to an arbitrary size, period.
For mainwindows you can use WM_SETMINMAXINFO - works for minimum and maximum sizes.
In all other cases I use dialog-designers like in VB6/VBA/C# - they just don't let you set a size to 0x0 unless your create your controls dynamically in code...
McMurmel - 07 02 13 - 05:25
I had a pretty good idea what was going on from the stack dump. My first reaction was "Oh, that should be easy to solve." because these days I'm working with an enormous MFC program, parts of which date back over 10 years, using every conceivable weird way of doing things, plus no end of grotesquely-written third-party MFC code (is there any other kind?), and multiple overlapping implementations of pretty much everything the application does.
But compared to what I imagine the video juju that goes on underneath to be, I'm guessing this kind of thing isn't so bad.
p.s. Dialog designers are for wimps. Real programmers edit the RC file by hand. ;-)
ConceptJunkie (link) - 22 02 13 - 13:46
I'm sure being experienced with MFC does make you rather accustomed to pain....
I have edited .RC files directly on occasion, and currently I need to maintain split .rc files in my projects since the Visual Studio resource editors are too lame to handle 24-bit images and icons (c'mon, seriously?). Still, I draw the line at trying to do visual layout in a text file unless it's in ASCII art.
Phaeron - 23 02 13 - 08:30
I have noticed that a lot of programs are not really remote desktop friendly. Mostly because they force a ton of redraws. Willingly or otherwise.
Igor Levicki (link) - 12 05 13 - 16:45