How To Disassemble A Windows Program
After we've found and analyzed WinMain() (-> lesson 1), the next
places to inspect when you crack a program are the windows procedures
and dialog procedures (this is true only for Windows *programs*; for
DLL, on the countrary, the cracking procedures are different and the
relvant techniques will be discussed in another lesson).
These WndProcs and DialogProcs are "callback" procedures: they are
*exported* from Windows executables, almost as the program were a DLL,
so that Windows can call them.
And -hear, hear!- beacuse they are exported these crucial procedures
have *names* (almost always useful) that are accessible to any decent
Windows disassembler. In Taskman.lst, for example, WCB clearly
identifies TASKMANDLGPROC:
Exported names by location:
1:007B 1 TASKMANDLGPROC <- It's a DialogProc !
It works out well that the WndProcs and DialogProcs show up so nicely
in the disassembled listings, because, as we know from Windows
programming, these subroutines are "where the action is" in event
driven Windows applications... or at least where the action begins.
Furthermore we know that these subroutines will be most likely little
more than (possibly very large) message handling switch/case
statements. These usually look something like this: long FAR PASCAL
_export WndProc(HWND hWnd, WORD message, WORD wParam, LONG lPAram)
long FAR PASCAL _export WndProc(HWND hWnd, WORD message, WORD
wParam, LONG lPAram)
{ ...
switch (message)
{
case WM_CREATE:
//... handle WM_CREATE message
break;
case WM_COMMAND:
//... handle WM_COMMAND message
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
Wow! Yes! As you already guessed this means that... that we get
immediately 4 parameters for EACH exported WndProc or DlgProc!
Actually there's no rule that states that a Windows WndProc or DlgProc
has to look like this... it's just that they almost always do!
Here is how the parameters to the WndProc or DialogProc will appear in
the assembly language listing (after the function prologue):
long FAR PASCAL _export WndOrDialogProc(HWND hwnd, WORD
message, WORD wParam, LONG lParam);
lParam = dword ptr [bp+6]
wParam = word ptr [bp+0Ah]
message = word ptr [bp+0Ch]
hWnd or hWndDlg = word ptr [bp+0Eh]
With this knowledge, we can replace an otherwise meaningless [bp+0Ch]
with a label such as "message", a [bp+0Eh] with a "hwnd" or "hwndDlg",
and so on in *ANY* DialogProc and WndProc in *ANY* Windows program.
The boilerplate nature of Windows programming greatly simplifies
cracking. For example, here is part of our Taskman exported:
The problem here, of course, is what to make of all these magic
numbers: 0064, OO1C, 00F4 and so on... how are we going to figure out
what these mean?
DialogProc: TASKMANDLGPROC:
1.007B ; TASKMANDLGPROC
... (function prologue)
1.008A 8B760E mov si, hWndDlg ;[bp+0E]
1.008D 56 push si
1.008E 6A64 push 0064
1.0090 9AFFFF0000 call USER.GETDLGITEM
1.0095 8BF8 mov di, ax
1.0097 8B460C mov ax, message ;[bp+0C]
1.009A 2D1C00 sub ax, 001C
1.009D 7416 je 00B5
1.009F 2DF400 sub ax, 00F4
1.00A2 7436 je 00DA
1.00A4 48 dec ax
1.00A5 7503 jne 00AA
1.00A7 E98301 jmp 022D
1.00AA >2D5303 sub ax, 0353
1.00AD 7503 jne 00B2
1.00AF E9D602 jmp 0388
1.00B2 >E9C801 jmp 027D
1.00B5 >837E0A00 cmp word ptr wParam, 0 ;[bp+0A]
1.00B9 7403 je 00BE
1.00BB E9BF01 jmp 027D
...
When examined via disassembled listings, Windows programs tend to
contain a lot of "magic numbers". Of course the actual source code
would be :
* #include '<'windows.h'>' and
* #define numeric constants for the various resources (menus,
strings, dialog controls, etc.) that it uses.
Given a disassembled listing, it should be possible to turn a lot of
these seemingly senseless numbers back into something understandable.
Let's start with the number 001C in TaskManDlgProc():
1.0097 8B460C mov ax, message ;[bp+0C]
1.009A 2D1C00 sub ax, 001C
1.009D 7416 je 00B5
If AX holds the *message* parameter to TaskManDlgProc() (line
1.0097)... then the value 001C must be a Windows WM_ message number
(one of those you can breakpoint to with WINICE's BMSG command, by the
way). Looking in WINDOWS.H, we find that 0x1C is WM_ACTIVATEAPP.
TaskManDlgProc() is subtracting this value from AX and then jumping
somewhere (let's call it ON_ACTIVATEAPP) if the result is zero... i.e.
if it is WM_ACTIVATEAPP.
This is an odd way to test whether (message == WM_ACTIVATEAPP): if the
test fails, and we do not take the jump to ON_ACTIVATEAPP, the message
number has 1C subtracted from it... and this value must be taken
account of by the next switch statement:
1.009F 2DF400 sub ax, 00F4 ; (+1C=110=WM_INITDIALOG)
1.00A2 7436 je 00DA ; jump to ON_INITDIALOG
1.00A4 48 dec ax ; (110+1=111=WM_COMMAND)
1.00A5 7503 jne 00AA ; no, go elsewhere
1.00A7 E98301 jmp 022D ; yes, jump to ON_COMMAND
Other WndProcs & DialogProcs will contain straightforward tests,
rather than testing via subtraction... is a matter of compiler choice.
In any case, a WndProc or DialogProc generally contains a collection
of handlers for different messages.
In the case of TaskManDlgProc(), we can see that's handling
WM_ACTIVATEAPP, WM_INITDIALOG and WM_COMMAND. By itself, this
information is rather boring... however, it tells us what is happening
*elsewhere* in the function: 1.00B5 must be handling WM_ACTIVATEAPP
messages (therefore let's call it ON_ACTIVATEAPP), 1.00DA must be
handling WM_INITDIALOG, and 1.022D must be handling WM_COMMAND
messages.
Write it down! This same basic technique -find where the [bp+0Ch]
"message" parameter to the WndProc or DialogProc is being rested, and
from that identify the locations that handle various messages- can be
used in *ANY* Windows program.
Because handling messages is mostly what Windows applications do, once
we know where the message handling is, we pretty much can have our way
with the disassembled listing.
Let's look now at TaskManDlgProc():
TASKMANDLGPROC proc far
...
DISPATCH_ON_MSG:
1.0097 8B460C mov ax, message ;[bp+0C]
1.009A 2D1C00 sub ax, WM_ACTIVATEAPP ;001C
1.009D 7416 je ON_ACTIVATEAPP
1.009F 2DF400 sub ax, 00F4 ; (+1C=110=WM_INITDIALOG)
1.00A2 7436 je ON_INITDIALOG
1.00A4 48 dec ax ;(110+1=111=WM_COMMAND)
1.00A5 7503 jne DEFAULT
1.00A7 E98301 jmp ON_COMMAND
DEFAULT:
1.00AA >2D5303 sub ax, 0353 ;(111+353=464=WM_USER+64
1.00AD 7503 jne ON_PRIVATEMSG ;00B2= some private msg
1.00AF E9D602 jmp 0388
ON_PRIVATEMSG:
1.00B2 >E9C801 jmp 027D
ON_ACTIVATEAPP:
1.00B5 >837E0A00 cmp word ptr wParam, 0 ;[bp+0A]
... ; code to handle WM_ACTIVATEAPP
ON_INITDIALOG:
... ; code to handle WM_INITDIALOG
ON_COMMAND:
... ; code to handle WM_COMMAND
1.022D >8B460A mov ax, wParam ;[bp+0A]
1.0230 3D6800 cmp ax, 0068 ; ? What's this ?
1.0233 7503 jne 0238
1.0235 E93301 jmp 036B
...
This is starting to look pretty reasonable. In particular, once we
know where WM_COMMAND is being handled, we are well on the way to
understand what the application does.
WM_COMMAND is *very* important for understanding an application
behavior because the handler for WM_COMMAND is where it deals with
user commands such as Menu selections and dialog push button clicks...
a lot of what makes an application unique.
If you click on "Cascade" in Task manager, for instance, it comes as a
WM_COMMAND, the same occurs if you click on "Tile" or "Switch To" or
"End Task".
An application can tell which command a user has given it by looking
in the wParam parameter to the WM_COMMAND message.
This is what we started to see at the ned of the TaskManDlgProc()
exerpt:
; We are handling WM_COMMAND, therefore wParam is here idItem,
; i.e. a control or menu item identifier
1.022D >8B460A mov ax, wParam ;[bp+0A]
1.0230 3D6800 cmp ax, 0068 ;ID number for a dialog control
1.0233 7503 jne 0238
1.0235 E93301 jmp 036B
1.0238 >7603 jbe 023D
1.023A E96001 jmp 039D
1.023D >FEC8 dec al ;1
1.023F 7420 je 0261 ;if wParam==1 goto 1.0261
1.0241 FEC8 dec al ;1+1=2
1.0243 7503 jne 0248
1.0245 E94701 jmp 038F ;if wParam==2 goto 1.038F
1.0248 >2C62 sub al, 62 ;2+62=64
1.024A 742A je 0276
1.024C FEC8 dec al ;64+1=65
1.024E 7432 je 0282
1.0250 2C01 sub al, 01 ;65+1=66
1.0252 7303 jnb 0257
1.0254 E94601 jmp 039D
1.0257 >2C01 sub al, 01 ;66+1=67
1.0259 7703 ja 025E
1.025B E9D200 jmp 0330
It's clear that wParam is being compared (in an odd subtraction way)
to valus 1,2,65,66 and 67. What's going on?
The values 1 and 2 are standard dialog button IDs:
#define IDOK 1
#define IDCANCEL 2
Therefore we have here the two "classical" push buttons:
1.023D >FEC8 dec al ; 1 = OK
1.023F 7420 je ON_OK ; If 1 goto 1.0261= ON_OK
1.0241 FEC8 dec al ; 1+1=2= CANCEL
1.0243 7503 jne NOPE ; goto neither OK nor CANCEL
1.0245 E94701 jmp ON_CANCEL ; if 2 goto 1.038F= ON_CANCEL
The numbers 65, 66 etc are specific to TaskManager however, we will
not find them inside WINDOWS.H... so there is no home to find the
names of the commands to which these magic number correspond, unless
we happen to have a debug version of the program true? NO! FALSE!
One of the notable things about Windows is that remarkably little
information is lost or thrown away compiling the source code. These
magic numbers seem to correspond in some way to the different Task
Manager push buttons... it's pretty obvious that there must be a way
of having applications tell Windows what wParam they want sent when
one of their buttons is clicked or when one of their menu items is
selected.
Applications almost always provide Windows with this information in
their resources (they could actually define menus and controls
dynamycally, on the fly, but few applications take advantage of this).
These resources are part of the NE executable and are available for
our merry snooping around.
This inspections of the resources in an EXE file is carried out by
means of special utilities, like RESDUMP, included with Windows source
(-> in my tool page). For example (I am using "-verbose" mode):
DIALOG 10 (0Ah), "Task List" [ 30, 22,160,107]
FONT "Helv"
LISTBOX 100 (64h), "" [ 3, 3,154, 63]
DEFPUSHBUTTON 1 (01h), "&Switch To" [ 1, 70, 45, 14]
PUSHBUTTON 101 (65h), "&End Task" [ 52, 70, 45, 14]
PUSHBUTTON 2 (02h), "Cancel" [103, 70, 55, 14]
STATIC 99 (63h), "" [ 0, 87,160, 1]
PUSHBUTTON 102 (66h), "&Cascade" [ 1, 90, 45, 14]
PUSHBUTTON 103 (67h), "&Tile" [ 52, 90, 45, 14]
PUSHBUTTON 104 (68h), "&Arrange Icons" [103, 90, 55, 14]
YEAH! It's now apparent what the numbers 64h, 65h etc. mean. Imagine
you would write Taskmanager yourself... you would write something on
these lines:
#define IDD_SWITCHTO IDOK
#define IDD_TASKLIST 0x64
#define IDD_ENDTASK 0x65
#define IDD_CASCADE 0x66
#define IDD_TILE 0x67
#define IDD_ARRANGEICONS 0x68
Let's look back at the last block of code... it makes now a lot more
sense:
ON_COMMAND:
; We are handling WM_COMMAND, therefore wParam is here idItem,
; i.e. a control or menu item identifier
1.022D >8B460A mov ax, wParam ;[bp+0A]
1.0230 3D6800 cmp ax, 0068 ;is it the ID 68h?
...
1.023D >FEC8 dec al ;1=IDOK=IDD_SWITCHTO
1.023F 7420 je ON_SWITCHTO ;0261
1.0241 FEC8 dec al ;1+1=2=ID_CANCEL
1.0243 7503 jne neither_OK_nor_CANCEL ;0248
1.0245 E94701 jmp ON_CANCEL ;038F
neither_OK_nor_CANCEL:
1.0248 >2C62 sub al, 62 ;2+62=64= IDD_TASKLIST
1.024A 742A je ON_TASKLIST ;0276
1.024C FEC8 dec al ;64+1=65= IDD_ENDTASK
1.024E 7432 je ON_ENDTASK ;0282
1.0250 2C01 sub al, 01 ;65+1=66= IDD_CASCADE
1.0252 7303 jnb check_for_TILE ;0257
1.0254 E94601 jmp 039D ;something different
check_for_TILE:
1.0257 >2C01 sub al, 01 ;66+1=67= IDD_TILE
1.0259 7703 ja 025E ;it's something else
1.025B E9D200 jmp ON_TILE_or_CASCADE ;0330
In this way we have identified location 0330 as the place where
Taskman's "Cascade" and "Tile" buttons are handled... we have renaimed
it ON_TILE_or_CASCADE... let's examine its code and ensure it makes
sense:
ON_TILE_or_CASCADE:
1.0330 >56 push hwndDlg ;si
1.0331 6A00 push 0000
1.0333 9A6F030000 call USER.SHOWWINDOW
1.0338 9A74030000 call USER.GETDESKTOPWINDOW
1.033D 8BF8 mov di, ax ;hDesktopWnd
1.033F 837E0A66 cmp word ptr wParam, 0066 ;IDD_CASCADE
1.0343 750A jne ON_TILE ;034F
1.0345 57 push di ;hDesktopWnd
1.0346 6A00 push 0000
1.0348 9AFFFF0000 call USER.CASCADECHILDWINDOWS
1.034D EB2F jmp 037E
ON_TILE:
1.034F >57 push di
1.0350 6A10 push 0010
1.0352 9AFFFF0000 call USER.GETKEYSTATE
1.0357 3D0080 cmp ax, 8000
1.035A 7205 jb 0361
1.035C B80100 mov ax, 0001 ;1= MDITILE_HORIZONTAL
1.035F EB02 jmp 0363
1.0361 >2BC0 sub ax, ax ;0= MDITILE_VERTICAL
1.0363 >50 push ax
1.0364 9AFFFF0000 call USER.TILECHILDWINDOWS
1.0369 EB13 jmp 037E
Yes, it makes a lot of sense: We have found that the "Cascade" option
in Tile manager, after switching through the usual bunch of
switch/case loops, finally ends up calling an undocumented Windows API
function: CascadeChildWindows()... similarly, the "Tile" routine ends
up calling TileChildWindow().
One thing screams for attention in the disassembled listing of
ON_TILE: the call to GetKeyState().
As an example of the kind of information you should be able to gather
for each of these functions, if you are serious about cracking, I'll
give you now here, in extenso, the definition from H. Schildt's
"General purpose API functions", Osborne's Windows Programming Series,
Vol. 2, 1994 edition (I found both this valuable book and its
companion: volume 3: "Special purpose API functions", in a second hand
shop, in february 1996, costing the equivalent of a pizza and a
beer!). Besides this function is also at times important for our
cracking purposes, and represents therefore a good choice. Here the
description from pag.385:
void GetKeyState(int iVirKey)
Use GetKeyState() to determine the up, down or toggled status of
the specified virtual key. iVirKey identifies the virtual key. To
return the status of a standard alphanumeric character in the
range A-Z, a-z or 0-9, iVirKey must be set equal to its ANSI
ASCII value. All other key must use their related virtual key
codes. The function returns a value indicating the status of the
selected key. If the high-order bit of the byte entry is 1, the
virtual key is pressed (down); otherwise it is up. If you examine
a byte emlement's low-order bit and find it to be 1, the virtual
key has been toggled. A low-order bit of 0 indicates that the key
is untoggled.
Under Windows NT/Win32, this function returns type SHORT.
Usage:
If your application needs to distinguish wich ALT, CTRL, or SHIFT
key (left or right) has been pressed, iVirKey can be set equal to
one of the following:
VK_LMENU VK_RMENU
VK_LCONTROL VK_RCONTROL
VK_LSHIFT VK_RSHIFT
Setting iVirKey equal to VK_MENU, VK_CONTROL or VK_SHIFT
instructs GetKeyState() to ignore left and right, and only to
report back the status of teh virtual key category. This ability
to distinguish among virtual-key states is only available with
GetKeyState() and the related functions listed below.
The following fragment obtains the state of the SHIFT key:
if(GetKeyState(VK_SHIFT) {
...
}
Related Functions:
GetAsyncKeyState(), GetKeyboardState(), MapVirtualKey(),
SetKeyboardState()
Ok, let's go on... so we have in our code a "funny" call to
GetKeyState(). Because the Windows USer's Guide says nothing about
holding down a "state" (shift/ctrl/alt) key while selecting a button,
this sounds like another undocumented "goodie" hidden inside TASKMAN.
Indeed, if you try it out on the 3.1 Taskman, you'll see that clicking
on the Tile button arranges all the windows on the desktop side by
side, but if you hold down the SHIFT key while clicking on the Tile
button, the windows are arranged in a stacked formation.
To summarize, when the 3.1. Taskman Tile button is selected, the code
that runs in response looks like this:
Tile:
ShowWindow(hWndDlg, SW_HIDE); // hide TASKMAN
hDesktopWnd = GetDesktopWindow();
if (GetKeyState(VK_SHIFT) == 0x8000)
TileChildWindows(hDesktopWnd, MDITILE_HORIZONTAL);
else
TileChildWindows(hDesktopWnd, MDITILE_VERTICAL);
Similarly, the CASCADE option in 3.1. TASKMAN runs the following code:
Cascade:
ShowWindow(hWndDlg, SW_HIDE); // hide TASKMAN
CAscadeChildWindows(GetDesktopWindow(), 0);
We can then proceed through each TASKMAN option like this, rendering
the assembly language listing into more concise C.
The first field to examine in TASKMAN is the Task List itself: how is
the "Task List" Listbox filled with the names of each running
application?
What the List box clearly shows is a title bar for each visible top
level window, and the title bar is undoubtedly supplied with a call to
GetWindowText()... a function that obtains a copy of the specified
window handle's title.
But how does TASKMAN enumerate all the top-level Windows? Taskman
exports TASKMANDLGPROC, but does not export any enumeration procedure.
Most of the time Windows programs iterate through all existing windows
by calling EnumWindows(). Usually they pass to this function a pointer
to an application-supplied enumeration function, which therefore MUST
be exported. This callback function must have following prototype:
BOOL CALLBACK EnumThreadCB(HWND hWnd, LPARAM lParam)
Of course, the name a programmer chooses for such an exported function
is arbitrary. hWnd will receive the handle of each thread-associated
window.lParam receives lAppData, a 32-bit user- defined value. This
exported function must return non-zero to receive the next enumerated
thread-based window, or zero to stop the process.
But here we DO NOT have something like TASKMANENUMPROC in the list of
exported functions... what's going on? Well... for a start TASKMAN IS
NOT calling EnumWindows()... Taskman uses a GetWindow() loop to fill
the "Task List" list box, study following C muster, sipping a good
cocktail and comparing it with the disassembled code you have printed:
Task List:
listbox = GetDlgItem(hWndDlg, IDD_TASKLIST);
hwnd = GetWindow(hwndDlg, GW_HWNDFIRST);
while (hwnd)
{ if ((hwnd != hwndDlg) && //excludes self from list
IsWindowVisible(hwnd) &&
GetWindow(hwnd, GW_OWNER))
{ char buf[0x50];
GetWindowText(hwnd, buf, 0x50); // get titlebar
SendMessage(listbox, LB_SETITEMDATA,
SendMessage(listbox, LB_ADDSTRING, 0, buf),
hwnd); // store hwnd as data to go
} // with the titlebar string
hwnd = GetWindow(hwnd, GW_HWNDNEXT);
}
SendMessage(lb, LB_SETCURSEL, 0, 0); // select first item
The "End Task" opton in Taskman just sends a WM_CLOSE message to the
selected window, but only if it's not a DOS box. TASKMAN uses the
undocumented IsWinOldApTask() function, in combination with the
documented GetWindowTask() function, to determine if a given HWND
corresponds to a DOS box:
End Task:
... // boring details omitted
if(IsWinOldApTask(GetWindowTask(hwndTarget)))
MaybeSwitchToSelecetedWindow(hwndTarget);
if(IsWindow(hwndTarget) &&
(! (GetWindowLong(hwndTarget, GWL 5STYLE) & WS_DISABLED))
{
PostMessage(hwndTarget, WM_CLOSE, 0, 0);
}
The "Arrange Icons" option simply runs the documented
ARrangeIconicWindows() function:
Arrange Icons:
Showwindow(hWndDlg, SW_HIDE);
ArrangeIconiCWindows(GetDesktopWindow());
The "Switch To" option in TASKMAN is also interesting. Like "Tile" and
"Cascade", this too it's just a user-interface covering an
undocupented Windows API function, in this case SwitchToThisWindow().
Let's walk through the process of deciphering a COMPLETELY unlabelled
Windows disassembly listing, that will be most of the time your
starting situation when you crack, and let's turn it into a labelled C
code.
By the way, there does exist an interesting school of research, that
attempts to produce an "EXE_TO_C" automatical converter. The only
cracked version of this program I am aware of is called E2C.EXE, is
198500 bytes long, has been developed in 1991 by "The Austin Code
Works and Polyglot International" in Jerusalem (Scott Guthery:
guthery@acw.com), and has been boldly brought to the cracking world by
Mithrandir/AlPhA/MeRCeNarY. Try to get a copy of this tool... it can
be rather interesting for our purposes ;-)
Here is the raw WCB disassembled code for a subroutine within TASKMAN,
called from the IDD_SWITCHTO handling code in TaskManDlgProc():
1.0010 >55 push bp
1.0011 8BEC mov bp, sp
1.0013 57 push di
1.0014 56 push si
1.0015 FF7604 push word ptr [bp+04]
1.0018 681A04 push 041A
1.001B FF7604 push word ptr [bp+04]
1.001E 680904 push 0409
1.0021 6A00 push 0000
1.0023 6A00 push 0000
1.0025 6A00 push 0000
1.0027 9A32000000 call USER.SENDMESSAGE
1.002C 50 push ax
1.002D 6A00 push 0000
1.002F 6A00 push 0000
1.0031 9AEF010000 call USER.SENDMESSAGE
1.0036 8BF8 mov di, ax
1.0038 57 push di
1.0039 9A4C000000 call USER.ISWINDOW
1.003E 0BC0 or ax, ax
1.0040 742A je 006C
1.0042 57 push di
1.0043 9AFFFF0000 call USER.GETLASTACTIVEPOPUP
1.0048 8BF0 mov si, ax
1.004A 56 push si
1.004B 9AA4020000 call USER.ISWINDOW
1.0050 0BC0 or ax, ax
1.0052 7418 je 006C
1.0054 56 push si
1.0055 6AF0 push FFF0
1.0057 9ACD020000 call USER.GETWINDOWLONG
1.005C F7C20008 test dx, 0800
1.0060 750A jne 006C
1.0062 56 push si
1.0063 6A01 push 0001
1.0065 9AFFFF0000 call USER.SWITCHTOTHISWINDOW
1.006A EB07 jmp 0073
1.006C >6A00 push 0000
1.006E 9ABC020000 call USER.MESSAGEBEEP
1.0073 >5E pop si
1.0074 5F pop di
1.0075 8BE5 mov sp, bp
1.0077 5D pop bp
1.0078 C20200 ret 0002
The RET 0002 at the end tells us that this is a near Pascal function
that expects one WORD parameter, which appears as [bp+4] at the top of
the code.
Because [bp+4] is being used as the first parameter to SendMessage(),
it must be an HWND of some sort.
Here is the muster for SendMessage(): LRESULT SendMessage(HWND hWnd,
UINT uMsg, WPARAM wMsgParam1, LPARAM lMsgParam2), where hWnd
identifies the Window receiving the message, uMsg identifies the
message being sent, wMsgParam1 & lMsgParam2 contain 16 bits and 32
bits of message-specific information.
Finally, we don't see anything being moved into AX or DX near the end
of the function, so it looks as if this function has no return value:
void near pascal some_func(HWND hwnd)
Let's look once more at it... the function starts off with two nested
calls to SendMessage (using the message numbers 41Ah and 409h). These
numbers are greater than 400h, they must therefore be WM_USER+XX
values. Windows controls such as edit, list and combo boxes all use
WM_USER+XX notification codes.
The only appropriate control in TASKMAN is the list box, so we can
just look at the list of LB_XXX codes in WINDOWS.H. 1Ah is 26 decimal,
therefore 41Ah is WM_USER+26, or LB_GETITEMDATA. Let's see what
Osborne's "Special Purpose API functions" says about it (pag.752):
LB_GETITEMDATA
When sent: To return the value associated with a list-box item.
wParam: Contains the index to the item in question
lParam: Not used, must be 0
Returns: The 32-bit value associated with the item
Similarly, 409h is WM_USER+9, which in the case of a list box means
LB_GETCURSEL. We saw earlier that TASKMAN uses LB_SETITEMDATA to store
each window title's associated HWND. LB_GETITEMDATA will now retrive
this hwnd:
hwnd = SendMessage(listbox, LB_GETITEMDATA,
SendMessage(listbox, LB_GETCURSEL, 0, 0), 0);
Notice that now we are caling the parameter to some_func() a listbox,
and that the return value from LB_GETITEMDATA is an HWND.
How would we know it's an hwnd without our references? We can see the
LB_GETITEMDATA return value (in DI) immediatly being passed to
IsWindow() at line 1.0039:
; IsWindow(hwnd = SendMessage(...));
1.0031 9AEF010000 call far ptr SENDMESSAGE
1.0036 8BF8 mov di, ax
1.0038 57 push di
1.0039 9A4C000000 call far ptr ISWINDOW
Next, the hwnd is passed to GetLastActivePopup(), and the HWND that
GetLastActivePopup() returns is then checked with IsWindow()...
IsWindow() returns non-zero if the specified hWnd is valid, and zero
if it is invalid:
; IsWindow(hwndPopup = GetLastActivePopup(hwnd));
1.0042 57 push di
1.0043 9AFFFF0000 call USER.GETLASTACTIVEPOPUP
1.0048 8BF0 mov si, ax ; save hwndPopup in SI
1.004A 56 push si
1.004B 9AA4020000 call USER.ISWINDOW
Next, hwndPopup (in SI) is passed to GetWindowLong(), to get
informations about this window. Here is time to look at WINDOWS.H to
figure out what 0FFF0h at line 1.055 and 800h at line 1.005C are
supposed to mean:
; GetWindowLong(hwndPopup, GWL_STYLE) & WS_DISABLED
1.0054 56 push si ;hwndPopup
1.0055 6AF0 push GWL 5STYLE ;0FFF0h = -16
1.0057 9ACD020000 call USER.GETWINDOWLONG
1.005C F7C20008 test dx, 0800 ;DX:AX= 800:0= WS_DISABLED
Finally, as the whole point of this exercise, assuming this checked
window passes all its tests, its last active popup is switched to:
; SwitchToRhisWindow(hwndPopup, TRUE)
1.0062 56 push si ;hwndPopup
1.0063 6A01 push 0001
1.0065 9AFFFF0000 call USER.SWITCHTOTHISWINDOW
It's here that all possible questions START: SwitchToThisWindow is not
documented... therefore we do not know the purpose of its second
parameter, apparently a BOOL. We cannot even tell why
SwitchToThisWindow() is being used... when SetActiveWindow(),
SetFocus() or BringWindowToTop() might do the trick. And why is the
last active popup and not the window switched to?
But let's resume for now our unearthed mysterious function, that will
switch to the window selected in the Task List if the window meets all
the function's many preconditions:
void MaybeSwitchToSelectedWindow(HWND listbox)
{
HWND hwnd, hwndPopup;
// first figure out wich window was selected in the Task List
if (IsWindow(hwnd = SendMessage(listbox, LB_GETITEMDATA,
SendMessage(listbox, LB_GETCURSEL, 0, 0), 0)))
{
if (IsWindow(hwndPopup = GetLastActivePopup(hwnd)))
{
if (! (GetWindowLong(hwndPopup, GWL_STYLE) & WS_DISABLED))
{
SwitchToThisWindow(hwndPopup, TRUE);
return;
}
}
MessageBeep(0); //Still here... error!
}
Now we have a good idea of what TASKMAN does (it sure took a long time
to understand those 3K bytes of code!). In the next lessons we'll use
what we have learned to crack together some common Windows programs.
(->lesson 3)
FraVia