News:

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

What's the theory behind the "Message Pump"?

Started by CoR, January 31, 2007, 06:19:43 AM

Previous topic - Next topic

CoR

Search didn't gave any relevant result and I am still puzzled why all tutorial examples has the same message pump?
I have two major questions here. And both are logical issues.

So, here's the "message loop" as presented in all tutorial examples I have laid my eyes on:
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
...
                .WHILE TRUE
                INVOKE GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                INVOKE TranslateMessage, ADDR msg
                INVOKE DispatchMessage, ADDR msg
.ENDW
mov     eax,msg.wParam
ret
WinMain endp


First question:
Is there any specific reason to put wc, hwnd and msg as LOCAL variable on stack?
Does this makes application faster, more stable or anyway better? Why is it so good to use stack?
Because this works fine:
.DATA?
msg MSG <>
hwnd dd ?

Is there any benefit to use LOCAL to allocate those variables and structures? And is there any benefit if you avoid using stack?

Second question:
What is the reason to use ADDR msg in every INVOKE?
Does WinXP somehow moves msg STRUCT without our knowledge so we have to use ADDR always?
Because this also works:

.DATA?
msg MSG <>
msg1 dd ?

.CODE
lea eax,msg
mov msg1,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
...
                .WHILE TRUE
                INVOKE GetMessage, msg1,NULL,0,0
                .BREAK .IF (!eax)
                INVOKE TranslateMessage, msg1
                INVOKE DispatchMessage,  msg1
.ENDW
mov     eax,msg.wParam
ret
WinMain endp


Am I right to say that INVOKE DispatchMessage, ADDR msg will always assemble as:
lea eax,msg
push eax
call DispatchMessage


And INVOKE DispatchMessage,  msg1   will assmeble as:
push msg1
call DispatchMessage


If this works and if it looks 'nicer', where is the catch? I see that first Invoke is almost exactly like C/C++. And second is "optimized" and more like asm. So what's wrong with it in a long run?
I am newbie, but I would love to learn about theory of asm and Win OS also :)
Especially if I'll learn WHY are some things like they are  :cheekygreen:

TNick

Quote from: CoR on January 31, 2007, 06:19:43 AM
First question:
Is there any specific reason to put wc, hwnd and msg as LOCAL variable on stack?
...
Is there any benefit to use LOCAL to allocate those variables and structures? And is there any benefit if you avoid using stack?

This is a long discussion :). What is the benefit of using local variables?
Have a look at this dummy function:

MyStruct STRUCT
  FirstMember:DWORD
  SecondMember:DWORD
MyStruct ENDS

SomeDummy  PROC
LOCAL FirstVar:DWORD
LOCAL FirstStruct:MyStruct
    mov  FirstVar, 10
    mov  FirstStruct.SecondMember, eax
ret
SomeDummy  ENDP


This will generate the following code:

PUSH EBP ;preserve ebp in the stack
MOV EBP,ESP;store the stack point in ebp
ADD ESP,-0C;make place for our local variables in the stack (4 = SIZEOF FirstVar, 8 = SIZEOF MyStruct)
MOV [EBP-4],0A   ;code in our proc
MOV [EBP-8],EAX
LEAVE   ;restore esp and ebp
;mov esp, ebp
;pop ebp
RET ;return


If you use global variables (in .DATA or .DATA?), you will have 12 bytes in memory all the time when your exe is running.
The benefit is that local variables are "allocated" at run time, so your exe size will be smaller.
On the other hand, using globals will improve the speed of that function, because there's no need for

PUSH EBP
MOV EBP,ESP
ADD ESP,-0C
.
.
.
LEAVE
RET

and MOV [EBP-8],EAX will be replaced with something like mov [400012], eax, which may improve the speed, also (not shure about this one).

Quote from: CoR on January 31, 2007, 06:19:43 AM
Second question:
What is the reason to use ADDR msg in every INVOKE?
Does WinXP somehow moves msg STRUCT without our knowledge so we have to use ADDR always?
ADDR doesn't have anything to do with the version of windows that you are running. ADDR is a macro that can be used only inside INVOKE, and it computes the offset - in memory - for it's operand.
- MyVar in .DATA, .DATA, .CONST section
PUSH OFFSET MyVar
lea eax, MyVar
push eax
will work, but there's 2 instructions,and there's no need for that.
- MyVar is a local variable
lea eax, MyVar ;lea eax, [ebp-4]
push eax


As a result, your code may become:
.DATA?
msg MSG <>
msg1 dd ?

.CODE
mov msg1, OFFSET msg

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
...
                .WHILE TRUE
                INVOKE GetMessage, msg1,NULL,0,0
                .BREAK .IF (!eax)
                INVOKE TranslateMessage, msg1
                INVOKE DispatchMessage,  msg1
.ENDW
mov     eax,msg.wParam
ret
WinMain endp

but you may note that you don't need msg1 (address of msg is a constant that you know)

INVOKE is just a macro like any other macro. It is designed to help you write mode readable code. You may use it or not, it's your choice. Let's take a look at some examples:
INVOKE MyFunction, SomeVar
is same as
push SomeVar
call MyFunction

I see no need to use the longer form.

INVOKE MyFunction, SomeVar, ADDR SomeVar2, ADDR SomeStruct1
is same as
push OFFSET SomeStruct1 ;or lea eax, ... for locals
push OFFSET SomeVar2; or lea eax, ... for locals
push SomeVar
call MyFunction


here, you may do a little speed increase, if both SomeVar2 and SomeStruct1 are locals
using invoke will generate this code
lea eax, SomeStruct1
push eax
lea eax, SomeVar2
push eax


wich you may do like this
lea eax, SomeStruct1
lea ecx, SomeVar2
push eax
push ecx


I use invoke in calls to functions with many parameters, when the values that I want to pass are in registers, are constants (offsets too)
for example (presuming that esi points to the path and name):
INVOKE CreateFile,ADDR FileName,GENERIC_READ,FILE_SHARE_DELETE,NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE
to
sub esp, 6*4 ; six parameters, 4 bytes each
mov dword ptr [esp], esi
mov dword ptr [esp+4],GENERIC_READ
mov dword ptr [esp+8], FILE_SHARE_DELETE
.
.
.

but, I have to admit, the speed increase is not very big.

HTH

Regards,
Nick






hutch--

CoR,

Your question is a good one, the message loop is fundamental to a Windows GUI application and it works in an interesting way. It is fundamentally a polling loop but with an important difference, it simply stops until GetMessage() returns another message and this is very efficient in terms of processor usage. An app that is not receiving messages uses no processor power at all.

To get it all up and running in a GUI app, you first load a WNDCLASSEX structure wih all of the arguments required to create a window including the address of the message handling procedure, nominally caled the "WndProc". You register the class with Windows then with that class you create a window using CreateWindowEx(). Before the CreateWindowEx() function returns it goes to the "WndProc" once to handle the "WM_CREATE" message then returns with the identifier that is normally called a window handle. You use this handle to interact with the window after it is created.

After any other stuff that you may want to start like menus or similar, you show the window with ShowWindow() then fall into the message loop. The action with the message loop continues until GetMessage() returns ZERO which is normally a programmatically controlled event. Otherwise it polls the operating system with GetMessage() and if nothing is available, it simply sits there until another message becomes available.

Some messages are sent directly by the OS to the WndProc but typically keyboard and a few other messages are returned to the message loop where they can be processed first then dispatched to the WndProc. You will notice that an application never calls a "WndProc". It is only ever called by the OS on the basis of a message needing to be sent to the window. The "WndProc" is called a "callback because of this and it is a reasonably typical technique in Windows for various types of Windows including the ordinary controls like buttons and so on.

A "WndProc" and a control subclass are almost identical except that a control already has a default "WndProc" so when you want to process messages for a control you have to intercept the message stream and then return to the default handler.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

TomRiddle

ICZ's tutorials(I ain't gonna bother butchering his name) mostly use a WinMain procedure, if you were asking about the point of it....I quite honestly can't see one.

On the other hand I can see why he did it, in another post someone referred to their program code as "code: main", this rational is a fall-out from C users.  Me, I just skip the whole WinMain procedure and go directly to the code it uses, basically removing as much LOCAL data as possible in favor of either static(.DATA) or dynamic(.DATA?) data.  Even my WNDCLASSEX structures tend to be put in the .Data section so I don't have to bother writing commands just to update data that never changes(I.E. SizeOf WNDCLASSEX).

Not sure if that's what you were asking, but it's how I do things.

lingo

Why not a Message Loop with MMX: :lol

.data
wc dd sizeof WNDCLASSEX
dd CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW
dd offset WndProc, 0, 0, 400000h, 0, 0
dd COLOR_WINDOW +1, 0
dd offset  ClassName, 0
align 8 ;
MyStack dd      offset  TranslateMessage        ; ret address for GetMessage
dd offset  msg ; parameter for GetMessage
hWnd dd 0 ; hWnd ; parameter for GetMessage
  dd 0 ; parameter for GetMessage
dd 0 ; parameter for GetMessage
dd offset  DispatchMessage ; ret address for  TranslateMessage
dd offset  msg ; parameter for  TranslateMessage
dd offset  Mess_Loop ; ret address for  DispatchMessage
dd offset  msg ; parameter for  DispatchMessage
dd offset GetMessage ;
dd 0 ; one dd more for 8 bytes allignment
msg     MSG     <>


.code
Start:
push IDI_APPLICATION
push 0
call LoadIcon
mov wc.WNDCLASSEX.hIcon, eax
mov wc.WNDCLASSEX.hIconSm, eax
push IDC_ARROW
push 0
call LoadCursor
mov wc.WNDCLASSEX.hCursor, eax
push offset wc
call RegisterClassEx ; Register Main Window Class

push 0 ; Create Main Window
push 400000h
push 0
push 0
push 680
push 800
push 2
push 180
push WS_OVERLAPPEDWINDOW
push offset CaptionText
push offset ClassName
push 0
call CreateWindowEx
mov hWnd, eax

push eax
push SW_SHOWNORMAL
push eax
call ShowWindow
call UpdateWindow
;
mov edi, dword ptr [GetMessage+2] ; edi->jmp address of GetMessage API
mov esi, offset MyStack
        mov     edi, [edi]              ; edi->real address of GetMessage API
add esp, -6*4
                movq MM3, [esi+8] ; esi-> offset MyStack
movq MM5, [esi+28]
pxor MM4, MM4
movq [esp], MM5
        movq    [esp+8],  MM3
        movq    [esp+16], MM4
        jmp     edi                  ; edi->real address of GetMessage API
;Message Loop
Align 8                              ;
Mess_Loop:                           ;
        add     esp, -9*4            ;
        movq    MM3, [esi+0*8]       ; esi-> offset MyStack
        movq    MM4, [esi+1*8]       ;
        movq    MM5, [esi+2*8]       ;
        movq    MM6, [esi+3*8]       ;
        movq    MM7, [esi+4*8]       ;
        movq    [esp+0*8],  MM3      ;
        movq    [esp+1*8],  MM4      ;
        movq    [esp+2*8],  MM5      ;
        movq    [esp+3*8],  MM6      ;
        movq    [esp+4*8],  MM7      ;
        jmp     edi                  ; edi->real address of GetMessage API
                                     ;
;Exit Message Loop
MLoopExit:                           ;
                                     ;
;FreeMemArray                        ;
mov ebx, MemArray ;
xor edi, edi ;
mov ecx, [ebx] ;
@@:                             ;
test ecx, ecx ;
        je      @f              ;
mov [ebx], edi ; edi = 0
add ebx, 4 ;
invoke VirtualFree, ecx, edi, MEM_RELEASE
mov ecx, [ebx] ;
        jmp     @b              ;
@@:                             ;
      invoke VirtualFree, hMemOrdLists, 0, MEM_RELEASE
invoke ImageList_Destroy, hImageList
Invoke HeapFree, hProcHeap, edi, MemArray
        push    edi             ; edi = 0
jmp ExitProcess ; Exit

End Start


Regards,
Lingo

CoR

@TomRiddle:
I do not know why, buy I think I am developing a fetish for .DATA?. Lingo and you have some mean wc in .DATA. I like it :)

Another question, does both .DATA and .DATA? have same access time or not? I know it's all the same memory, but does each one have advantage over the other? Eg. retains longer in cache or some weird CPU benefits in speed or addressing or something.

@hutch:
I didn't know that message loop loops (pun:) ONLY if there's message to process. It makes sense optimizing it only if application has some heavy duty message cycle. Like games!

@TNick:
Thanks for the LOCAL example ;) I fully understand it!
But I lost you in second part.

.DATA?
msg MSG <>

.CODE
...
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
...
                .WHILE TRUE
                INVOKE GetMessage, msg,NULL,0,0
                .BREAK .IF (!eax)
                INVOKE TranslateMessage, msg
                INVOKE DispatchMessage,  msg
.ENDW
mov     eax,msg.wParam
ret
WinMain endp

This code does not work. It assembles without error. But when I start it, window closes immediately. Did I made some mistake or I do need to put mov msg1, OFFSET msg even msg is in .DATA?

CoR

I have some trouble understanding wc in .DATA Little help is needed here

hutch--

CoR,

With the WNDCLASSEX structure, it is used to hold the arguments for the RegisterClassEx() call that follows it. This is a common technique in windows to load a structure then call a function passing the address of the structure instead of a long list of parameters. You can create the structure either locally on the stack or if its only a demo you can put it in the .DATA section with preloaded values. There is no advantage in the .DATA section method.

With the message loop, its operation is defined by the operating system so the GetMessage(), TranslateMessage() if used and DispatchMessage() calls are not subject to optimisation but if you are processing code from the message loop it is subject to normal code optimisation techniques.

Games comonly hog a large amount of processor time as they do not have to worry about processor slice sharing so they will tend to use the message loop more like an old fashioned polling loop rather than a Windows GUI app that must co-exist with other running applications.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

CoR

Hutch, man, now you are scaring me  :wink  What do you mean by "...if its only a demo you can put it in the .DATA section with preloaded values" ?

Will wc. defined in .DATA work same as wc. defined as LOCAL or not? On every comp with Win OS?

What about lingo's wc defined as wc dd param1, param2, ...

You said that there's no advantage to put wc. in .DATA. Is there any downfall or reason not to do things like that?
edit: Same question for "to use or not to use" WinMain? Wrom your experience  is there any downfall of writing direct code and skipping WinMain and stack allocatons?

To me it all looks the same. And I deduce that that code should be 100% same in computers eyes. And we all know how deduction can be wrong :lol  So better to be save than sorry  :wink

I am trying to make some mental picture of what I can and what I can not do with asm. Luckily I decided to make little project and learn things as project develops. Otherwise I would be more than lost. I mean, I am pretty lost right now, but at least I know where am I going and what my next step will be  :bg

TNick

Huge mistake there ... Wasn't careful! Sorry!
This is a complete example of what I am using to start a new project.

[EDIT]
Now, about your last post...

I had explained in my first post what is the difference between a LOCAL variable and one in the data section. Major difference: you can give a initial value for the one in data section. A variable located in the stack will have an random initial value (depending on what vase on the stack before at that position). From this point of view, it's similar to variables in .DATA? section. Second difference is the reference method. When you write
mov  eax,  Var01
and Var01 is a local one, the generated code will look like this:
mov  eax,  [EBP--4]
If it's a Global one (in .DATA section, in .DATA? section or even in .CONST section), it will look like this
mov  eax,  [04040010]
As I said before, I'm not shore which method is faster.

About using or not WinMain, as you can see in the attached template, I don't use it and I didn't use it at all. Icz tutorial uses this form to make it look like a C program, I think. WinMain is not a callback function and, therefore, Windows doesn't care how you're creating your windows. WndProc, on the other hand, can have any name, but it has to have that particular number of parameters, because it's a callback function.

[/EDIT]

Regards,
Nick

[attachment deleted by admin]

sinsi

Here's my version of registering a window:

    sub ebx,ebx
    mov esi,32512

    push ebx
    call GetModuleHandle
    mov hinst,eax
    mov edi,eax

    push ebx                ;hIconSm
    push OFFSET frmclass    ;lpszClassName
    push ebx                ;lpszMenuName
    push 1                  ;hbrBackground
    push esi
    push ebx
    call LoadCursor
    push eax                ;hCursor
    push esi
    push ebx
    call LoadIcon
    push eax                ;hIcon
    push edi                ;hInstance
    push ebx                ;cbWndExtra
    push ebx                ;cbClsExtra
    push OFFSET frmproc     ;lpfnWndProc
    push 3                  ;style
    push 48                 ;cbSize
    push esp
    call RegisterClassEx
    add esp,48
Light travels faster than sound, that's why some people seem bright until you hear them.

zooba

I'm going back to the original post because there seems to be little in the way of actual answers in the follow ups. TNick gave a good summary of the differences between .DATA and LOCAL and Hutch gave some good general info on message loops so between them you should be right. I'll give my take on your direct, original, questions.

Quote from: CoR on January 31, 2007, 06:19:43 AM
I am still puzzled why all tutorial examples has the same message pump?

Because Microsoft has suggested this format as the best way to write it. Since they're the ones writing the APIs themselves (and hence constantly benchmarking and testing them), they probably know best.

Quote from: CoR on January 31, 2007, 06:19:43 AM
First question:
Is there any specific reason to put wc, hwnd and msg as LOCAL variable on stack?
Does this makes application faster, more stable or anyway better? Why is it so good to use stack?
...
Is there any benefit to use LOCAL to allocate those variables and structures? And is there any benefit if you avoid using stack?

Yes, though in most cases it has no effect. Hutch mentioned that using the .DATA section will work fine for demos and this is true. However, if you are writing a complicated program involving multiple threads each with their own message loop (aka pump), you will need local variables to ensure that the threads don't overwrite each other's data. The .DATA/.DATA? section is common to all threads belonging to the same process (note that you can't have two message loops on the same thread since each will block).

In terms of speed, the APIs being called involve transistions to kernel mode. There's absolutely nothing you can do to slow these down any more. As soon as GUI is involved, performance becomes largely irrelevant. (Disclaimer: You can obviously block the thread somewhere else waiting for something, ie. an event/semaphore/mutex/file copy/long operation/etc., however, this has nothing to do with where your message loop variables are stored.)

Quote from: CoR on January 31, 2007, 06:19:43 AM
Second question:
What is the reason to use ADDR msg in every INVOKE?
Does WinXP somehow moves msg STRUCT without our knowledge so we have to use ADDR always?
...
If this works and if it looks 'nicer', where is the catch? I see that first Invoke is almost exactly like C/C++. And second is "optimized" and more like asm. So what's wrong with it in a long run?

There's no catch. You can get the address of the structure at the start, store it in another variable (or even ESI/EDI/EBX, which are preserved during API calls) and use that instead. In the long run, the net gain is zero. The API calls are the bottlenecks and there's nothing you can do to speed them up. (In reality, the biggest bottleneck in GUI programming is the user, and NOTHING can speed them up :bg ) If you put the structures in the .DATA/.DATA? section, you can use OFFSET which will assemble to push DWORD PTR [00410000h] but again, there is nothing to be gained by this when compared to the other performance issues. In general, I believe most people will find ADDR msg more readable than pMsg or something similar. (In general, I believe most people never read the code inside a message loop anyway.)

Quote from: CoR on January 31, 2007, 06:19:43 AM
I am newbie, but I would love to learn about theory of asm and Win OS also :)
Especially if I'll learn WHY are some things like they are  :cheekygreen:

Good. Do you read Raymond Chen's blog? He very often writes about the history of Win32, compatibility issues which MS deal with, reasons for why things are as they are and has been known to flex some pretty awesome debugging muscles (even in assembly).

Cheers,

Zooba :U

CoR

#12
Ok Nic, I think I've got full understanding of local and global variables now. On first glance it looks like C but when you see how LOCAL and .DATA are made... ASM really makes a difference.
Though I'll have to read this thread from time to time. Just to plant it in my mind firmly ;) Thanks man :)

So WinMain or not it'll work nice everywhere, any time. That's nice to hear. Zooba, thanks for untangling a lot of my noob problems.

And as far as wc. goes is it OK to say that there's more safety if U allocate it as LOCAL. And nothing else?

p.s. Raymond Chen's blog is nice one  :wink

zooba

Quote from: CoR on February 02, 2007, 07:52:29 PM
And as far as wc. goes is it OK to say that there's more safety if U allocate it as LOCAL. And nothing else?

Nothing else of note. You also avoid the problems associated with global variables, you get to use the variable names in other places without breaking your message loop, the size of your executable is smaller (by a few bytes, but some people care about those things) if you don't use the .DATA section (.DATA? is okay) and you are able to use the same message loop for different threads (because each thread has their own stack, but there's only one .DATA/.DATA? section for all threads).

Cheers,

Zooba :U

TomRiddle

Something I didn't like much about the tutorials was that he used a WinMain procedure, I just usually skip it in favor of having it all in the main Code region.

Here, btw, is my message pump

L_StartLoop:
  Invoke GetMessage, Addr St_Msg, Null, 0, 0
  JumpIfRegisterIsZero Eax, L_ExitLoop
  Invoke TranslateMessage, Addr St_Msg
  Invoke DispatchMessage, Addr St_Msg
  Jump L_StartLoop
L_ExitLoop:


[EDIT]
On a separate note, re-reading the thread, I get this view:

QuoteLate in the afternoon, CoR is driving to a friends house when he suddenly notices he is lost.  Lost?  Whatever can he do, when off in the distance like a beacon of hope looms a sign "Masm32 Forum".  Pulling to the side of the road a grizzly man in a full length beard appears.

"What can we do for you stranger?", he says adjusting some un-named tool in his hand.

CoR replies, "Well, I was on my way to a friends house, but I got kinda lost, was hoping you could help me."

"Lost eh?", says another patron of the old time Masm32 station, "Well now, let's get you found again."

"Whose lost?", yells a man from behind the pumps.

Words are a great thing aren't they. :eek
[/EDIT]