News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Tight Loop

Started by Jimg, January 14, 2005, 02:29:07 PM

Previous topic - Next topic

Jimg

Thanks Raymond.

This is similar to what I tried initially.  I changed the PMsgLoop exactly as you show and ran the program.  When I press Quit while the tight loop is running, nothing happens.  Occasionally, I can hit the quit while the Peekmessage loop is running, and everything on the dialog disappears, only a gray dialog left.  Then if I hit the X to close the program, I get a message from the system that the program is not responding.  This is exactly what made me so frustrated before, nothing made sense to me.

I must not understand how PeekMessage works.  From reading the description, I assumed that if I used PM_REMOVE, I had to either service this message or pass it along to the system with DispatchMessage, otherwise I might be discarding something important.




Relvinian

I've been following this thread for a little bit and later today I'll post an example of a simple dialog (like what you have shown recently) along with a worker thread that does some "time consuming" calculations (including using semaphores to make sure the thread has started/terminated, etc) that maybe you can use or get ideas from.  Look for this in about 4-5 hours from now -- busy at the moment finishing something else.

Relvinian

raymond

QuoteFrom reading the description, I assumed that if I used PM_REMOVE, I had to either service this message or pass it along to the system with DispatchMessage, otherwise I might be discarding something important.

That is correct. That is why I said "The above could be expanded to also look for other messages such as the WM_CLOSE". In your case, all other WM_COMMAND messages could be discarded without any penalty.

In order to find out why you were getting inadequate results, I would have to look at your entire code. I have used that type of approach on numerous occasions.

However, Relvinian may provide you with more info. The use of a thread definitely would allow you to process all messages and discard only those which must not be processed further. You may want to use your current app to practice with that approach to become familiar with such type of programming.

Raymond
When you assume something, you risk being wrong half the time
http://www.ray.masmcode.com

Relvinian

#18
Ok,

I have attached a sample dialog with a worker thread that counts down and when reaching zero terminates the thread. There is no UI updating to let you know what the worker thread is doing but that can be easily acheived by creating some other control on the dialog, etc.  This is rough code that I just cranked out during my lunch hour. It seems to work wells but there may be bugs.

The code is heavily commented so I hope it is easy to understand. If not, don't hesitate to ask questions.

What you will notice though is that there seems like a lot of code but I have jazzed up the dialog a little to disable the "worker button" while the thread is executing and re-enable it after it is done. The text on the button also changes during execution of the thread. The OK and Cancel buttons along with the "X" will instantly close and terminate the dialog (exe). This also aborts the thread and waits for it to close.

There is a build.bat to build the exe file. I'll let you guys play with it and if you have questions, let me know.

Relvinian

PS - I just found out that my little WM_COMMAND handler currently doesn't handle hotkeys or accelerators. Simple fix but for now, just use the buttons with mouse.  ;-)


[attachment deleted by admin]

Jimg

Thank you Relvinian, I'll look it over.

Raymond-

I'd still like to figure this peekmessage stuff out.  I tried to take care of the issues you spoke about in the code below.  When I run it, it is not recognizing the quit button.  I can see the button change state, so the system is recognizing it, my test for 1003 fails in the PMsgLoop using the code you presented (unless I misunderstood or screwed something up).  Ultimately, when I hit the X to close the program, it looks like it is closed, but it is still in memory and has to be removed with task manager.

.486
.model flat, stdcall
option casemap :none
.nolist
include windows.inc
; -----------------------------------------------------------------------
literal MACRO quoted_text:VARARG
LOCAL local_text
.data
  local_text db quoted_text,0
.code
EXITM <local_text>
ENDM

SADD MACRO quoted_text:VARARG
EXITM <ADDR literal(quoted_text)>
ENDM
sadd equ SADD
GetHandle Macro MyId:Req,MyHandle:Req
    invoke GetDlgItem,hWin,MyId
    mov MyHandle,eax         ; save handle to control
endm
uselib MACRO libname
    include     libname.inc
    includelib  libname.lib
ENDM
; -----------------------------------------------------------------------
uselib user32
uselib kernel32
uselib shell32
.list

DlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data?
buf1 db 100 dup (?)
hWin dd ?
hDoit dd ?
hEdt  dd ?
hQuit dd ?
LoopCnt dd ?
.code
Program:
invoke GetModuleHandle,NULL
invoke DialogBoxParam,eax,101,0,ADDR DlgProc,0
invoke ExitProcess,eax

DlgProc proc uses edi esi ebx hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    .if uMsg == WM_COMMAND
        mov eax,wParam
        mov edx,wParam
        shr edx,16
        .if ax==1001 ; doit button
            .If edx==BN_CLICKED
            call Doit ; go do test
           .EndIf
       .endif
    .elseif uMsg== WM_INITDIALOG
    push hWnd
    pop  hWin
    GetHandle 1001,hDoit
    GetHandle 1002,hEdt
    GetHandle 1003,hQuit   
    .elseif uMsg == WM_CLOSE
        invoke EndDialog,hWnd,0
    .else
pass:   mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
DlgProc endp

.data?
lpMsg MSG <?>
.code

Doit:
invoke EnableWindow,hDoit,FALSE ; disable doit button while working
invoke EnableWindow,hQuit,TRUE ; enable quit button while working
mov LoopCnt,1
Cont:
invoke wsprintf,addr buf1,sadd(" doing loop %li"),LoopCnt
invoke SendMessage,hEdt,WM_SETTEXT,0,addr buf1
invoke InvalidateRect, hWin, NULL, TRUE
invoke UpdateWindow, hWin
mov ebx,1000000000 ; this takes about a second on my machine
Again:
sub ebx,1 ; this represents my long calculation loop
jnz Again
inc LoopCnt
PMsgLoop:
invoke PeekMessage,addr lpMsg,hWin,0,0,PM_REMOVE ; Check for a message
or eax,eax
je Cont ; no message available
cmp lpMsg.message,WM_CLOSE
je Done
mov eax,lpMsg.wParam
cmp ax,1003
je Done
invoke DispatchMessage,addr lpMsg ; service the message I removed
jmp PMsgLoop

Done:
invoke PostMessage,lpMsg.hwnd,lpMsg.message,lpMsg.wParam,lpMsg.lParam
invoke EnableWindow,hQuit,FALSE ; done with quit button, disable it
invoke EnableWindow,hDoit,TRUE
ret
End Program

; rc-
#define Doit 1001
#define CurLoop 1002
#define Quit 1003

101 DIALOGEX 6,6,77,62
CAPTION "Test"
FONT 8,"MS Sans Serif"
STYLE 0x90cf0880
EXSTYLE 0x00000000
BEGIN
CONTROL "Doit",Doit,"Button",0x54002000,8,9,61,13,0x00000000
CONTROL "",CurLoop,"Edit",0x50010000,8,26,61,13,0x00000200
CONTROL "Quit",Quit,"Button",0x58010000,8,44,61,13,0x00000000
END


Jimg

After a little more testing, I find that the Doit procedure never gets 1003 from PeekMessage.  The only message that seems to be of value is WM_LBUTTONDOWN, which would trigger the 1003 in the main message loop.

sbrown

Quote from: Jimg on January 14, 2005, 02:29:07 PM
Newbie question here.

I'm in a tight loop doing some lengthy computations, and I am updating an editbox every million loops to show the progress.  What is the command to refresh the editbox on the screen after I do the sendmessage wm_settext to the editbox?  Likewise, I have a quit button, but while in the loop, it never gets a chance to send me a message that it was pushed.  What is the normal procedure for refreshing the state of pushbuttons so I can service it in the main dialog proc?

Edited:
Ok, I found UpdateWindow for the edit box.  Is this the best one to use?

UpdateWindow didn't work for the quit button however.

You need to (in VB) call DoEvents. Some where around here I have an assembly version of the VB DoEvents (which I think I got here on the MASM Forum before the hack). ::)

If you want I can look around for it and post it.


Scott

Jimg

Thanks Scott.  That is essentially what the Peekmessage, Dispatchmessage is doing I hope.

raymond

I spent a few hours last night with my good friend ollydbg analyzing this problem. I think that posting my findings may help and discourage others who may try something similar.

I had mentioned previously that I have often used the PeekMessage but that was with regular windows which have their own message querying code and message queue. However, a dialog box is managed by a different system which translates all messages and dispatches them immediately to the DlgProc. That DlgProc does not have to query its message queue.

Therefore, trying to peek into such Dlg messages and trying to re-dispatch them may create havoc with the entire dialog box managing system while the app is trying to perform some computation in a loop external to the DlgProc. For example, all mouse movement messages do get sent through the dialog box procedure before being handled by the default system if they are not intercepted for specific reasons.

Using thread(s) for such operations should therefore be the only safe route to use with dialog boxes (or any other type of window which does not have its own message querying code). This then allows the DlgProc to safely respond to all incoming messages while maintaining some communication with the running thread.

You will notice in the following modified code using such a route that enabling/disabling controls is not a necessity if conditions are checked before taking action. The app now works perfectly as you had intended. (You will only have to modify your resource script to enable the "Quit" button.)

Your header, macros and includes have not been reproduced in the modified code.

DlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
LoopProc PROTO :DWORD

.data?

buf1 db 100 dup (?)
hWin dd ?
hDoit dd ?
hEdt  dd ?
hQuit dd ?
LoopCnt dd ?
computing   dd    ?     ;flag 0=thread not active, 1=active
compstop    dd    ?     ;0=thread must abort, 1=OK to continue
threadID    dd    ?

.code

Program:
invoke GetModuleHandle,NULL
      push eax
      invoke InitCommonControls
      pop   eax
invoke DialogBoxParam,eax,101,0,ADDR DlgProc,0
invoke ExitProcess,eax

DlgProc proc hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    .if uMsg == WM_COMMAND
        mov eax,wParam
        mov edx,wParam
        shr edx,16
        .if ax==1001 ; doit button
            .If edx==BN_CLICKED
                  .if   computing == 0
                        call Doit ; go do test
                  .endif
           .EndIf
       .elseif ax==1003 ; quit button
            .If edx==BN_CLICKED
                  .if computing != 0       ;if thread active
                        mov   compstop,0  ;advise thread to terminate
                     @@:
                        cmp   computing,0
                        jnz   @B          ;wait until thread terminates
                  .endif
            invoke EndDialog,hWnd,0
           .EndIf
       .endif
    .elseif uMsg== WM_INITDIALOG
    push hWnd
    pop  hWin
    GetHandle 1001,hDoit
    GetHandle 1002,hEdt
    GetHandle 1003,hQuit
            mov eax,TRUE
            ret   
    .elseif uMsg == WM_CLOSE
                  .if computing != 0       ;if thread active
                        mov   compstop,0  ;advise thread to terminate
                     @@:
                        cmp   computing,0
                        jnz   @B          ;wait until thread terminates
                  .endif
        invoke EndDialog,hWnd,0
    .else
pass:   mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
DlgProc endp

.data?
lpMsg MSG <?>
.code

Doit:
      mov   compstop,1        ;set up communication flags
      mov   computing,1
      invoke CreateThread,0,0,addr LoopProc,0,0,ADDR threadID
      invoke CloseHandle,eax  ;saving handle not usually needed
      ret         ;return to main app to process messages

;the computing thread has full access to global data

LoopProc proc a:DWORD
mov LoopCnt,1
Cont:
invoke wsprintf,addr buf1,sadd(" doing loop %li"),LoopCnt
invoke SendMessage,hEdt,WM_SETTEXT,0,addr buf1
invoke InvalidateRect, hWin, NULL, TRUE
invoke UpdateWindow, hWin
mov ebx,1000000000 ; this takes about a second on my machine
Again:
sub ebx,1 ; this represents my long calculation loop
jnz Again
inc LoopCnt

      cmp   compstop,0  ;is thread told to terminate
jnz Cont          ;continue otherwise

Done:
      mov   computing,0 ;advises main window thread has terminated
ret               ;close thread
LoopProc endp

End Program


Raymond

When you assume something, you risk being wrong half the time
http://www.ray.masmcode.com

Jimg

Thank you for your patience, Raymond.  If it's gotta be threads, then I'll use threads, but I don't hafta like it :wink

I've tried many things myself and found out what you are saying is true, Doit isn't getting many of the messages using peekmessage, but the main loop is.  All I really needed was some way for doit to say call me back after processing the messages just before doing a return.  I played a lot with postmessage thinking it would put a message after all the other messages which would call doit back when clear, but the order of processing messages is not nearly as linear (first in, first out) as the documentation leads one to believe.

Thanks again, I'll just move on now......