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]
Here is another version of the customized message box displaying a menu.
[attachment deleted by admin]
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.
Thanks for your kind words. SetWindowsHookEx, it's this API doing the job.
Very cute indeed. Do you need the first branch?
cmp nCode,0
jb GetOut
cmp nCode,HCBT_ACTIVATE
jne GetOut
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).
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.
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:
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.
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
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)
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
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
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.
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.
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]
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
Hi jj2007,
Your example works fine on my system. Thanks for your contribution.
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.
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.
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
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]
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
.
.