The MASM Forum Archive 2004 to 2012

Project Support Forums => The GeneSys Development System => Topic started by: Vortex on May 25, 2007, 06:41:44 PM

Title: Custom message box
Post by: Vortex on May 25, 2007, 06:41:44 PM
Based on an article in catch22.net, I coded a message box having a customized OK button.

The article with the original demo coded with C :

http://www.catch22.net/tuts/msgbox.asp


.386
.model  flat, stdcall
option  casemap:none

include \GeneSys\include\windows.inc
include \GeneSys\include\kernel32.inc
include \GeneSys\include\user32.inc

includelib \GeneSys\lib\kernel32.lib
includelib \GeneSys\lib\user32.lib

CustomMsgBox PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
msg1 db 'This is a customized message box',0
capt db 'Hello!',0
btext db 'Click here',0

.data?
hMboxHook dd ?
pButtonText dd ?

.code

start:

invoke CustomMsgBox,0,ADDR msg1,ADDR capt,ADDR btext
invoke ExitProcess,0

mboxCBTProc PROC nCode:DWORD,wParam:DWORD,lParam:DWORD

cmp nCode,0
jb @f
cmp nCode,HCBT_ACTIVATE
jne @f
invoke GetDlgItem,wParam,IDOK
invoke SetWindowText,eax,pButtonText
xor eax,eax
ret
@@:
invoke CallNextHookEx,hMboxHook,nCode,wParam,lParam
ret

mboxCBTProc ENDP

CustomMsgBox PROC hWnd:DWORD,message:DWORD,caption:DWORD,buttontext:DWORD

mov eax,buttontext
mov pButtonText,eax
invoke GetCurrentThreadId
invoke SetWindowsHookEx,WH_CBT,ADDR mboxCBTProc,NULL,eax
mov hMboxHook,eax
invoke MessageBox,0,message,caption,MB_OK
invoke UnhookWindowsHookEx,hMboxHook
ret

CustomMsgBox ENDP

END start

[attachment deleted by admin]
Title: Re: Custom message box
Post by: Vortex on October 01, 2008, 11:31:00 AM
Here is another version of the customized message box displaying a menu.

[attachment deleted by admin]
Title: Re: Custom message box
Post by: BlackVortex on October 01, 2008, 07:19:50 PM
Haha, nice stuff, this goes in my "cool stuff to check out" folder


--> SetWindowsHookEx <--
Don't you just love that API ? I use it for keyboard hooks in trainers.

Title: Re: Custom message box
Post by: Vortex on October 01, 2008, 07:38:56 PM
Thanks for your kind words. SetWindowsHookEx, it's this API doing the job.
Title: Re: Custom message box
Post by: jj2007 on October 03, 2008, 12:25:11 AM
Very cute indeed. Do you need the first branch?

   cmp      nCode,0
   jb      GetOut

   cmp      nCode,HCBT_ACTIVATE
   jne      GetOut
Title: Re: Custom message box
Post by: jdoe on October 03, 2008, 01:05:18 AM
Quote from: jj2007 on October 03, 2008, 12:25:11 AM
Very cute indeed. Do you need the first branch?

   cmp      nCode,0
   jb      GetOut

   cmp      nCode,HCBT_ACTIVATE
   jne      GetOut



In this case probably needed because of the WindowProc. Without it nCode never gets a negative value.

One problem using this hook with a MessageBox is that it is not thread-safe because hMboxHook could be changed by another call somewhere else (if the function is in a library for example).

Title: Re: Custom message box
Post by: jj2007 on October 03, 2008, 01:51:14 AM
Quote from: jdoe on October 03, 2008, 01:05:18 AM
Quote from: jj2007 on October 03, 2008, 12:25:11 AM
Very cute indeed. Do you need the first branch?

   cmp      nCode,0
   jb      GetOut

   cmp      nCode,HCBT_ACTIVATE
   jne      GetOut



In this case probably needed because of the WindowProc. Without it nCode never gets a negative value.

I am a bit confused here. First, nCode seems to be unsigned:
mboxCBTProc PROC nCode:DWORD,wParam:DWORD,lParam:DWORD
Since jb is a test for an unsigned value, I don't understand for which values of nCode the jump will be taken.
Second, HCBT_ACTIVATE equals +5, and this is tested in the second half. If nCode is not +5, there will be a jump to GetOut. That holds true for all negative values, too.
Title: Re: Custom message box
Post by: jdoe on October 03, 2008, 02:18:26 AM

jj2007,

You're absolutely right... Should be jl instead of jb. CBTProc is "supposed" to return the returned value of CallNextHookEx when nCode is less than zero.

:clap:

Title: Re: Custom message box
Post by: Vortex on October 03, 2008, 10:24:54 AM
jj,

Thanks for the heads-up. Bug fixed. Updated GSmbox.zip (http://www.masm32.com/board/index.php?action=dlattach;topic=7375.0;id=5503) at the top.

jdoe,

You would probably create a thread-safe dialog box which is more easy to handle. It was interesting to combine hooking with subclassing as regular message boxes are not providing much resources for customizations.
Title: Re: Custom message box
Post by: jj2007 on October 03, 2008, 12:14:56 PM
Quote from: Vortex on October 03, 2008, 10:24:54 AM
jj,

Thanks for the heads-up. Bug fixed. Updated GSmbox.zip (http://www.masm32.com/board/index.php?action=dlattach;topic=7375.0;id=5503) at the top.


Not really a bug, but the first comparison is simply not needed - the jne is enough.

   cmp      nCode,0
   jl      exit
   cmp      nCode,HCBT_ACTIVATE
   jne      exit   ; implies that it will also jump to exit for all negative values... :wink
Title: Re: Custom message box
Post by: Vortex on October 03, 2008, 12:26:10 PM
Hi jj,

Your comment is OK. That's fine, thanks.

Another interesting article by MS :

How To Position a MsgBox Using a Windows Hook Procedure (http://support.microsoft.com/kb/180936)
Title: Re: Custom message box
Post by: jdoe on October 03, 2008, 01:26:57 PM
Quote from: Vortex on October 03, 2008, 10:24:54 AM
jdoe,

You would probably create a thread-safe dialog box which is more easy to handle. It was interesting to combine hooking with subclassing as regular message boxes are not providing much resources for customizations.

Vortex,

I did made thread-safe dialogs for the one having their own callback procedure like BrowseForFolder, GetOpenFileName or GetSaveFileName, but in the case of a MessageBox, I tried hard but never been able to do it.

In other words, we are on the same boat for this one   :P

Regards

jdoe

Title: Re: Custom message box
Post by: jj2007 on October 03, 2008, 03:09:48 PM
I have a suggestion and a question:
- Suggestion: reset flag on exit for multiple uses:

.elseif nCode==HCBT_DESTROYWND
   xor eax, eax
   mov flag, eax

EDIT: No good here, as it causes the "internal" About Messagebox to adopt the same design. Put it here:
   invoke UnhookWindowsHookEx, hMboxHook
   m2m flag, 0   ; needed for multiple uses

- Question: Would it be good practice to proceed with CallNextHookEx after handling HCBT_ACTIVATE, instead of returning 0? It works, but I am not sure if there are drawbacks.

mboxCBTProc proc uses esi nCode:DWORD, wParam:DWORD, lParam:DWORD
LOCAL rc:RECT
  .if nCode==HCBT_ACTIVATE ; HCBT_CREATEWND is too early
.if flag==0
lea esi, rc
invoke GetWindowRect, wParam, esi
mov eax, RECT.right[esi] ; same as mov eax, [esi.RECT.right]
sub eax, RECT.left[esi]
mov ecx, RECT.bottom[esi]
sub ecx, RECT.top[esi]
add ecx, 24
invoke MoveWindow, wParam, RECT.left[esi], RECT.top[esi], eax, ecx, TRUE

invoke GetDlgItem, wParam, IDOK
invoke SetWindowText, eax, pButtonText
invoke GetModuleHandle, 0
invoke LoadMenu, eax, 100
invoke SetMenu, wParam, eax

invoke SetWindowLong, wParam, GWL_WNDPROC, addr MsgboxProc
mov pOldProc, eax
mov flag, 1
.endif
; xor eax, eax ; needed, or can we proceed
; ret ; with CallNextHookEx?

  ; .elseif nCode==HCBT_DESTROYWND ********** see comment above
  ; xor eax, eax
  ; mov flag, eax ; needed for multiple uses
; ret ; can we pass on to CallNextHookEx?
  .endif

  invoke CallNextHookEx, hMboxHook, nCode, wParam, lParam
  ret
mboxCBTProc endp
Title: Re: Custom message box
Post by: Vortex on October 03, 2008, 05:42:00 PM
m2m flag, 0

That's a nice idea and that was exactly what I added to the SolAsm translation of this demo. About the usage of CallNextHookEx, terminating the procedure with this function instead of xor eax,eax can work as you suggested but care must be taken when dealing with multiple hook setups. From win32.hlp :

QuoteChaining to the next hook procedure (that is, calling the CallNextHookEx function) is optional. An application or library can call the next hook procedure either before or after any processing in its own hook procedure. Although chaining to the next hook is optional, it is highly recommended; otherwise, the other applications that have installed hooks will not receive hook notifications and may behave incorrectly as a result.
Title: Re: Custom message box
Post by: Vortex on October 03, 2008, 06:17:19 PM
QuoteI did made thread-safe dialogs for the one having their own callback procedure like BrowseForFolder, GetOpenFileName or GetSaveFileName, but in the case of a MessageBox, I tried hard but never been able to do it.

In other words, we are on the same boat for this one

Jdoe,

What I mean is that you can create a dialog box simulating a message box. It's always easier to customize a dialog box.
Title: Re: Custom message box
Post by: Vortex on October 06, 2008, 07:44:24 PM
Here is another trial. This time, the message box is moved to a thread and a loop to get the handle with EnumWindows and GetWindowText runs until the message box in the thread is drawn on the screen. GetWindowsText is inserted here to "wait" for the display of the message box.

.386
.model flat,stdcall
option casemap:none

include MBox.inc
   
.data
capt        db "Hello!",0
msg         db 'Customized message box',0
msg2        db 'Message box test',0
ButtonText  db 'Click here',0
bclass      db 'Button',0
   
.data?
   
pOldProc dd ?
   
.code
   
start:
   
    invoke  CustMsgBox,0,ADDR msg,ADDR capt,ADDR ButtonText
    invoke  ExitProcess,0
   
CustMsgBox  PROC uses esi handle:DWORD,message:DWORD,caption:DWORD,button:DWORD
   
LOCAL rc:RECT
LOCAL ThreadID:DWORD
LOCAL hWnd:DWORD
LOCAL hThread:DWORD
LOCAL buffer[100]
   
    lea     edx,[caption]
    invoke  CreateThread,0,0,ADDR ThreadProc,edx,0,ADDR ThreadID
    mov     hThread,eax
@@:
    invoke  EnumWindows,ADDR EnumWndProc,ADDR ThreadID
    invoke  GetWindowText,hWnd,ADDR buffer,100
    invoke  StrCmpA,ADDR buffer,caption
    test    eax,eax
    jz      @b
   
    invoke  FindWindowEx,hWnd,0,ADDR bclass,0
    invoke  SetWindowText,eax,button
   
    lea     esi,[rc]
    invoke  GetWindowRect,hWnd,esi
    mov     eax,RECT.right[esi]
    sub     eax,RECT.left[esi]
    mov     ecx,RECT.bottom[esi]
    sub     ecx,RECT.top[esi]
    add     ecx,20
    invoke  MoveWindow,hWnd,RECT.left[esi],RECT.top[esi],eax,ecx,TRUE
   
    invoke  GetModuleHandle,0
    invoke  LoadMenu,eax,100
    invoke  SetMenu,hWnd,eax
    invoke  SetWindowLong,hWnd,GWL_WNDPROC,ADDR MsgboxProc
    mov     pOldProc,eax
   
    invoke  WaitForSingleObject,hThread,INFINITE
    invoke  CloseHandle,hThread
    ret   
   
CustMsgBox ENDP
   
ThreadProc PROC pParams:DWORD
   
    mov     eax,pParams
    invoke  MessageBox,DWORD PTR [eax-8],DWORD PTR [eax-4],DWORD PTR [eax],MB_OK
    ret
   
ThreadProc ENDP
   
EnumWndProc PROC hWnd:DWORD,lParam:DWORD
   
    LOCAL pid:DWORD
   
    invoke  GetWindowThreadProcessId,hWnd,ADDR pid
    mov     edx,lParam
    cmp     eax,[edx]
    jne     @f
    push    hWnd
    pop     DWORD PTR [edx-4]  ; get the handle of the window
    xor     eax,eax            ; displaying the message box
    ret
@@:
    mov     eax,TRUE
    ret
   
EnumWndProc ENDP
   
MsgboxProc PROC hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
   
    .IF uMsg == WM_COMMAND
   
        .IF wParam == IDB_EXIT
            invoke  SendMessage,hWnd,WM_CLOSE,0,0
   
        .ELSEIF wParam == IDB_ABOUT
            invoke  MessageBox,0,ADDR msg2,ADDR capt,MB_OK
   
        .ENDIF
   
        invoke    CallWindowProc,pOldProc,hWnd,uMsg,wParam,lParam
        ret
   
    .ELSEIF uMsg == WM_CLOSE
   
    invoke    EndDialog,hWnd,0
   
    .ELSE
   
    invoke    CallWindowProc,pOldProc,hWnd,uMsg,wParam,lParam
    ret
   
    .ENDIF
   
    xor    eax,eax
    ret
   
    MsgboxProc    ENDP

END start



[attachment deleted by admin]
Title: Re: Custom message box
Post by: jj2007 on October 06, 2008, 08:03:32 PM
Quote from: Vortex on October 03, 2008, 05:42:00 PM
m2m flag, 0

That's a nice idea and that was exactly what I added to the SolAsm translation of this demo. About the usage of CallNextHookEx, ...

Is there any argument against unhooking immediately in the HCBT_ACTIVATE handler? It seems to work fine, see below...

.nolist
include \masm32\include\masm32rt.inc

CustomMsgBox PROTO :DWORD, :DWORD, :DWORD, :DWORD
MsgboxProc PROTO :DWORD, :DWORD, :DWORD, :DWORD

IDB_EXIT equ 1001
IDB_ABOUT equ 1002

.data
msg1 db 'Customized message box with menu', 0
capt db 'Hello!', 0
btext db 'Click here', 0
msg2 db 'Message box test', 0
flagCmb dd 0

.data?
hMboxHook dd ?
pButtonText dd ?
pOldProc dd ?

.code

start: invoke CustomMsgBox, 0, addr msg1, addr capt, addr btext
invoke CustomMsgBox, 0, chr$("Another one"), addr capt, chr$("A button!")
exit

mboxCBTProc proc uses esi nCode:DWORD, wParam:DWORD, lParam:DWORD
LOCAL rc:RECT
.if nCode==HCBT_ACTIVATE && flagCmb==0
lea esi, rc
invoke GetWindowRect, wParam, esi
mov eax, RECT.right[esi]
sub eax, RECT.left[esi]
mov ecx, RECT.bottom[esi]
sub ecx, RECT.top[esi]
add ecx, 24
invoke MoveWindow, wParam, RECT.left[esi], RECT.top[esi], eax, ecx, TRUE

invoke GetDlgItem, wParam, IDOK
invoke SetWindowText, eax, pButtonText
invoke GetModuleHandle, 0
invoke LoadMenu, eax, 100
invoke SetMenu, wParam, eax
mov flagCmb, 1

invoke SetWindowLong, wParam, GWL_WNDPROC, addr MsgboxProc
mov pOldProc, eax

invoke UnhookWindowsHookEx, hMboxHook ; new: unhook immediately ******************

.endif
invoke CallNextHookEx, hMboxHook, nCode, wParam, lParam
ret
mboxCBTProc endp

CustomMsgBox proc hWnd:DWORD, message:DWORD, caption:DWORD, buttontext:DWORD

push buttontext
pop pButtonText
invoke GetCurrentThreadId
invoke SetWindowsHookEx, WH_CBT, addr mboxCBTProc, NULL, eax
mov hMboxHook, eax
invoke MessageBox, 0, message, caption, MB_OKCANCEL

; invoke UnhookWindowsHookEx, hMboxHook ; moved upstairs

m2m flagCmb, 0 ; needed for multiple uses
ret

CustomMsgBox ENDP

MsgboxProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

.if uMsg==WM_COMMAND
.if wParam==IDB_EXIT
invoke SendMessage, hWnd, WM_CLOSE, 0, 0

.elseif wParam==IDB_ABOUT
invoke MessageBox, 0, addr msg2, addr capt, MB_OK

.endif

.elseif uMsg==WM_CLOSE
invoke EndDialog, hWnd, 0
xor eax, eax
ret
.endif

invoke CallWindowProc, pOldProc, hWnd, uMsg, wParam, lParam
ret

MsgboxProc endp

end start
Title: Re: Custom message box
Post by: Vortex on October 06, 2008, 08:24:35 PM
Hi jj2007,

Your example works fine on my system. Thanks for your contribution.
Title: Re: Custom message box
Post by: jj2007 on October 06, 2008, 10:33:09 PM
Quote from: Vortex on October 06, 2008, 08:24:35 PM
Your example works fine on my system. Thanks for your contribution.

I tried with various flag settings to see what happens if another app displays a normal MessageBox while the hook is active - no effect. Apparently the hook is limited to the thread.

Do we need .elseif uMsg==WM_CLOSE ? It seems to work without this branch, and I also don't see a good reason why the default handler (pOldProc) should not deal with it.
Title: Re: Custom message box
Post by: Vortex on October 11, 2008, 02:49:06 PM
There is no need of WM_CLOSE. That's right. I was typing the code as if I would work on a full dialog box procedure. WM_CLOSE can be removed safely.
Title: Re: Custom message box
Post by: PBrennick on October 12, 2008, 03:47:37 PM
Vortex,

Quote
What I mean is that you can create a dialog box simulating a message box. It's always easier to customize a dialog box.

I understand your point here but remember what the purpose of a MessageBox is. It is to quickly and easiily send information to the user. It requires much less programming to get the job done so in most cases it is just best to leave well enough alone.

Everyone,
There was an example on this board that hooked  timer so that the MessageBox would only be displayed for a certain interval. The user could click OK or just let it go away by itself. It was used in conjunction with an Empth The Recyle Bin utility. I don't remember the author but I still have tyhe code if anyone wants it.

JJ,
Your point about ..

   cmp      nCode,0
   jl      exit
   cmp      nCode,HCBT_ACTIVATE
   jne      exit   ; implies that it will also jump to exit for all negative values...


is a very good point. The second branch will only be tested if the first one is not taken. If the first one is not taken then the second one will not either, so it is redundant.

Paul
Title: Re: Custom message box
Post by: Vortex on October 12, 2008, 05:41:59 PM
Hi Paul,

QuoteI understand your point here but remember what the purpose of a MessageBox is. It is to quickly and easiily send information to the user. It requires much less programming to get the job done so in most cases it is just best to leave well enough alone.

True, the MessageBox function requires less coding but the customization options are limited as this function displays a modal dialog box. This means that you have to find a method to get the handle of the message box.

New version with simplified code :

.386
.model flat,stdcall
option casemap:none

include MBox.inc
   
.data
capt        db "Hello!",0
msg         db 'Customized message box',0
msg2        db 'Message box test',0
ButtonText  db 'Click here',0
bclass      db 'Button',0
   
.data?
   
pOldProc dd ?
   
.code
   
start:
   
    invoke  CustMsgBox,0,ADDR msg,ADDR capt,ADDR ButtonText
    invoke  ExitProcess,0
   
CustMsgBox  PROC uses esi handle:DWORD,message:DWORD,caption:DWORD,button:DWORD
   
LOCAL rc:RECT
LOCAL ThreadID:DWORD
LOCAL hWnd:DWORD
LOCAL hThread:DWORD
LOCAL buffer[100]
   
    invoke  CreateThread,0,0,ADDR ThreadProc,ADDR caption,0,ADDR ThreadID
    mov     hThread,eax
@@:
    invoke  EnumWindows,ADDR EnumWndProc,ADDR ThreadID
    invoke  GetWindowText,hWnd,ADDR buffer,100
    invoke  StrCmpA,ADDR buffer,caption
    test    eax,eax
    jz      @b
    invoke  FindWindowEx,hWnd,0,ADDR bclass,0
    invoke  SetWindowText,eax,button
    invoke  GetWindowRect,hWnd,ADDR rc
    mov     ecx,rc.right
    sub     ecx,rc.left
    mov     edx,rc.bottom
    sub     edx,rc.top
    add     edx,20
    invoke  SetWindowPos,hWnd,0,0,0,ecx,edx,SWP_NOACTIVATE or SWP_NOMOVE
    invoke  GetModuleHandle,0
    invoke  LoadMenu,eax,IDM_MENU
    invoke  SetMenu,hWnd,eax
    invoke  SetWindowLong,hWnd,GWL_WNDPROC,ADDR MsgboxProc
    mov     pOldProc,eax
    invoke  WaitForSingleObject,hThread,INFINITE
    invoke  CloseHandle,hThread
    ret   
   
CustMsgBox ENDP
   
ThreadProc PROC pParams:DWORD
   
    mov     eax,pParams
    invoke  MessageBox,DWORD PTR [eax-8],DWORD PTR [eax-4],DWORD PTR [eax],MB_OK
    ret
   
ThreadProc ENDP
   
EnumWndProc PROC hWnd:DWORD,lParam:DWORD
   
    LOCAL pid:DWORD
   
    invoke  GetWindowThreadProcessId,hWnd,ADDR pid
    mov     edx,lParam
    cmp     eax,[edx]
    jne     @f
    push    hWnd
    pop     DWORD PTR [edx-4]  ; get the handle of the window
    xor     eax,eax            ; displaying the message box
    ret
@@:
    mov     eax,TRUE
    ret
   
EnumWndProc ENDP
   
MsgboxProc  PROC hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
   
    .IF uMsg == WM_COMMAND
   
        .IF wParam == IDB_EXIT
            invoke  SendMessage,hWnd,WM_CLOSE,0,0
   
        .ELSEIF wParam == IDB_ABOUT
            invoke  MessageBox,0,ADDR msg2,ADDR capt,MB_OK
   
        .ENDIF
   
    .ENDIF
   
    invoke  CallWindowProc,pOldProc,hWnd,uMsg,wParam,lParam
    ret
   
    MsgboxProc    ENDP

END start




[attachment deleted by admin]
Title: Re: Custom message box
Post by: Vortex on September 27, 2011, 07:14:03 PM
The loop to "catch" the message box window is unnecessary now. WaitForInputIdle is a much more elegant solution :


.
.
    invoke  CreateThread,0,0,ADDR ThreadProc,ADDR caption,0,ADDR ThreadID
    mov     hThread,eax
    invoke  GetCurrentProcess
    invoke  WaitForInputIdle,eax,INFINITE
@@:
    invoke  EnumWindows,ADDR EnumWndProc,ADDR ThreadID
    invoke  GetWindowText,hWnd,ADDR buffer,100

;   invoke  StrCmpA,ADDR buffer,caption
;   test    eax,eax
;   jz      @b

    invoke  FindWindowEx,hWnd,0,ADDR bclass,0
    invoke  SetWindowText,eax,button
    invoke  GetWindowRect,hWnd,ADDR rc
.
.