News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Couple of questions regarding a simple windows application

Started by Mike, July 03, 2012, 09:10:22 AM

Previous topic - Next topic

Mike

1) When I run this program and press the tilde/backquote key, a messagebox pops up as expected. However, if I press the hotkey again without closing the messagebox, a second messagebox appears (and a third and so forth). How can this be, given that it is a single-threaded application, and MessageBox blocks execution?

2) If I move the RegisterHotKey calls to the WM_CREATE handler of the window procedure, the hotkeys don't work, why?

3) Is this code 100% correct as far as the message loop, window procedure, etc? (other than not yet processing WM_PAINT messages)

Note: The use of hotkeys rather than simply processing WM_KEYDOWN messages is intentional.

include /masm32/include/masm32rt.inc

WinMain PROTO

hkIDBackQuote equ 0
hkIDAltF11    equ 1

    .data
szClassName db "hkhkwin", 0

    .data?
hWnd      HWND      ?
hInstance HINSTANCE ?

    .code
  start:

invoke WinMain
exit


WinMain PROC
    LOCAL msg:MSG
    LOCAL wc:WNDCLASSEX

    mov hInstance, FUNC(GetModuleHandle, NULL)

    mov wc.cbSize,        sizeof WNDCLASSEX
    mov wc.style,         0
    mov wc.lpfnWndProc,   OFFSET WndProc
    mov wc.cbClsExtra,    0
    mov wc.cbWndExtra,    0
    m2m wc.hInstance,     hInstance
    mov wc.hIcon,         NULL
    mov wc.hCursor,       FUNC(LoadCursor, NULL, IDC_ARROW)
    mov wc.hbrBackground, FUNC(CreateSolidBrush, 00FFFFFFh)
    mov wc.lpszMenuName,  NULL
    m2m wc.lpszClassName, OFFSET szClassName
    mov wc.hIconSm,       NULL
    invoke RegisterClassEx, ADDR wc

    invoke CreateWindowEx, 0, OFFSET szClassName, chr$("^^"), WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, NULL, hInstance, NULL
    mov hWnd, eax
    .IF !hWnd
        return FALSE
    .ENDIF

    invoke ShowWindow, hWnd, SW_SHOWDEFAULT
    invoke UpdateWindow, hWnd

    invoke RegisterHotKey, hWnd, hkIDBackQuote, NULL, VK_OEM_3
    invoke RegisterHotKey, hWnd, hkIDAltF11, MOD_ALT, VK_F11

    .WHILE TRUE
        invoke GetMessage, ADDR msg, NULL, 0, 0
        .IF eax == 0
            return msg.wParam
        .ENDIF
        invoke DispatchMessage, ADDR msg
    .ENDW
WinMain ENDP


WndProc PROC hwnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .IF uMsg == WM_CREATE

;;    .ELSEIF uMsg == WM_PAINT

    .ELSEIF uMsg == WM_HOTKEY
        .IF wParam == hkIDAltF11
            invoke DestroyWindow, hWnd
        .ELSEIF wParam == hkIDBackQuote
            MsgBox NULL, "backquote", "`", MB_OK
;;            invoke Sleep, 10000
        .ENDIF

    .ELSEIF uMsg == WM_DESTROY
        invoke UnregisterHotKey, hWnd, hkIDBackQuote
        invoke UnregisterHotKey, hWnd, hkIDAltF11
        invoke PostQuitMessage, 0
    .ELSE
        invoke DefWindowProc, hwnd, uMsg, wParam, lParam
        ret
    .ENDIF
    return 0
WndProc ENDP


end start

dedndave

the message loop doesn't look quite right...
maybe i am mistaken - but i see no call to TranslateMessage
i use something like this...
;message loop

    xor     edi,edi
    lea     esi,msg
    jmp short mEntry

    .while !(ZERO?)
        INVOKE  TranslateMessage,esi
        INVOKE  DispatchMessage,esi

mEntry: INVOKE  GetMessage,esi,edi,edi,edi
        inc     eax                                     ;exit only if 0 or -1
        shr     eax,1
    .endw

;exit program

    INVOKE  ExitProcess,[esi].MSG.wParam


at any rate...
while the MessageBox function is a blocking function, i think the WndProc will continue to recieve keyboard messages

Mike

I omitted the call to TranslateMessage as the application won't be needing WM_CHAR or similar messages.

Quote from: dedndave
while the MessageBox function is a blocking function, i think the WndProc will continue to recieve keyboard messages

How can it? My understanding is that the window procedure is called by DispatchMessage, which is being called by the main (and only) thread of the program, and does not return until WndProc returns.

Since MessageBox blocks, and WndProc therefore cannot return, DispatchMessage should not return and therefore no more messages should be processed until the message box is dismissed. However  messages are still being processed. If I uncomment the Sleep call, press backquote five times, then close the main window, the process takes about 50 seconds to terminate, indicating the Sleeps are executing sequentially.

Does MessageBox check for input in the calling threads message queue, and call DispatchMessage if a message is waiting (resulting in recursive calls to WndProc)?

I use Win7 if it makes a difference.

Antariy

Quote from: Mike on July 03, 2012, 01:20:56 PM
How can it? My understanding is that the window procedure is called by DispatchMessage, which is being called by the main (and only) thread of the program, and does not return until WndProc returns.

MessageBox creates a dialog box, after that it creates its own message pump loop with Peek/Translate/DispatchMessage calls. the hWnd parameter passed to a PeekMessage is 0, and it returns messages for any window from queue of the thread, otherwice MessageBox will be true blocking function but at the same time it will mess displaying of the other windows in the thread, because they will not receive re-painting messages etc.

I.e., code backtrace in your case looks like:


# WinMain
## DispatchMessage
### WndProc
#### MessageBox
##### DispatchMessage
###### WndProc
...



Also, just a note not relying to this case: the messages arrive WndProc function via DispathMessage only if they grabbed from message queue, i.e. if the message was posted into window. If you use SendMessage function, then WndProc receives the message "directly", not via DispathMessage.

NoCforMe

I haven't totally digested this yet, but I think this is an important subject for all us Windows programmers to understand. Good question (and some good answers).

mywan

I didn't initially digest Antariy's response, at least until I thought through it from what I know. Note that this MsgBox is being triggered by a system level hotkey via the RegisterHotKey API. Hence this message cannot be blocked by WndProc of the app. Note that if you stack a MsgBox pair that the second will not spawn till you close the first. Hence Antariy's warning that this would not occur if you tried use the SendMessage function to trigger a second one from within the program. But since it is a system level hotkey it still receives message by way through the pump loop created  by the message box itself. This is what Antariy meant by:
QuoteMessageBox creates a dialog box, after that it creates its own message pump loop with Peek/Translate/DispatchMessage calls.

You program execution has been blocked. I once seen this behavior under different circumstances and foolishly thought I could trick a single threaded app into multithreaded behavior by using hotkeys. Any code execution triggered by the hotkeys will wait on the previous code to return, even when the second execution was triggered while the first had the thread blocked.

If you need to avoid this behavior don't use the RegisterHotKey API. Use GetAsyncKeyState to trigger your MsgBox instead. This way your program cannot query GetAsyncKeyState again while the thread is blocked by a MsgBox from the last call. Unlike a OS level hotkey which cannot be blocked just because your programs thread is.

qWord

Hi,
MessageBox will not create additional threads. The trick is, that the MsgBox-function implements it own message loop, which also feed our window. Whenever you call MsgBox, you create and enter one of them. The current message loop is blocked (-> we are currently in response to DispatchMessage). In other word, we have nested message loops.
You can test this: add a print-statement after the MsgBox -> if you close them in reversed creation-order, you will immediately see reply in the console. If you close them out of order, you will see no text in the console -> these messages appear if you close all later created MsgBoxs. The reason is, that the corresponding message loop is blocked, thus the result can't be returned.
MREAL macros - when you need floating point arithmetic while assembling!

mywan

Precisely. That's why I thought it important to provide a full quote box, rather than a simple paraphrase, of Antariy's words about the message loop provided by the MsgBox. Your print-statement example was interesting, never thought of that one.