第二十六课:启动画面
启动画面是一个没有标题栏,系统菜单栏,和边框的窗口,它暂时性显示一位图然后自动的消失。它常常被用在程序启动时,用于显示程序的标识语或者是用来转移用户的注意力以便程序做一些冗长初始化工作。
第一步是在资源文件中包含一个位图文件。然而,如果你考虑过,装载的位图仅被使用一次然后它将被保存在内存中直到程序被关闭,这样做浪费了宝贵的内存资源。一种好的解决办法是创建一包含位图的资源Dll,它唯一的目的就是显示启动画面。用这种方式,你能在你想显示启动画面的时候装载位图并在你不需要它的时候卸载它。所以我们将有两个模块:主程序和启动画面DLL。我们放置一位图到dll的资源中。一般的方法如下:
1: 放置一位图到dll中作为一位图资源。
2: 主程序调用LoadLibrary来装载dll到内存中。
3: Dll的入口函数被调用。它将创建一个定时器并设置一个长的时间值,这个时间用于显示启动画面。
4: 注册并创建一个没有标题和边框的窗口并在这个窗口的客户区中显示一位图。
当指定的时间值到达时,启动画面被从屏幕上移走并且将控制权返还给主程序。
5:主程序调用FreeLibrary来从内存中释放dll文件,然后继续做它该做的任务。
LoadLibrary函数动态的装载一dll文件,下面是函数的句法:
LoadLibrary proto lpDLLName:DWORD
它仅有一参数:你想装载到内存中的dll文件名的地址。如果调用成功,它返回dll的模块句柄,否则,它返回NULL值。
调用FreeLibrary函数来卸载一个dll文件:
FreeLibrary proto hLib:DWORD
它仅有一个参数:你想卸载的dll模块句柄。通常,就是LoadLibrary函数返回的句柄。
如何使用定时器:
首先,你必须用SetTimer函数创建一定时器。
SetTimer proto hWnd:DWORD, TimerID:DWORD, uElapse:DWORD, lpTimerFunc:DWORD
Hwnd :是接受定时器通知消息的窗口句柄。这个参数能为NULL值,表示,这是一个没有和任何窗口关联的定时器。
TimerID :用户自定义的值,被用作定时器的ID号。
uElapse :定时器所定的时间值,以毫秒为单位。
LpTimerFunc: 是处理定时器通知消息的函数地址。如果你传递NULL值,定时器消息将被发送给由hWnd参数指定的窗口。
如果调用成功,SetTimer函数返回定时器的ID。否则,它返回NULL值。所以,最好不要用0作为定时器的ID。
你能用两种方式创建一定时器:
1: 如果你有一个窗口并且你想定时器的通知消息外卖给这个窗口,你必须传递上面的四个参数给SetTimer。 (lpTimerFunc必须为NULL)
2: 如果你没有窗口或者是你不想在窗口过程中处理定时器消息,你必须在函数的窗口句柄的地方传递一空值。
你还必须指定处理定时器消息的定时器函数的地址值。
在例子中,我们将使用第一种方法.
当定时器所定的时间达到时,WM_TIMER消息被发送给与定时器相关联的窗口。例如,如果你指定UElapse的值为1000,你的窗口将在每隔一1000毫秒接受一WM_TIMER消息。
当你不再需要定时器时,用KillTimer函数销毁它,该函数句法如下:
KillTimer proto hWnd:DWORD, TimerID:DWORD
Example:
;-----------------------------------------------------------------------
; The main program
;-----------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
.data
ClassName db "SplashDemoWinClass",0
AppName db "Splash Screen Example",0
Libname db "splash.dll",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
invoke LoadLibrary,addr Libname
.if eax!=NULL
invoke FreeLibrary,eax
.endif
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, 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
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
;--------------------------------------------------------------------
; The Bitmap DLL
;--------------------------------------------------------------------
.386
.model flat, stdcall
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
.data
BitmapName db "MySplashBMP",0
ClassName db "SplashWndClass",0
hBitMap dd 0
TimerID dd 0
.data
hInstance dd ?
.code
DllEntry proc hInst:DWORD, reason:DWORD, reserved1:DWORD
.if reason==DLL_PROCESS_ATTACH ; When the dll is loaded
push hInst
pop hInstance
call ShowBitMap
.endif
mov eax,TRUE
ret
DllEntry Endp
ShowBitMap proc
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,0
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\
WS_POPUP,CW_USEDEFAULT,\
CW_USEDEFAULT,250,250,NULL,NULL,\
hInstance,NULL
mov hwnd,eax
INVOKE ShowWindow, hwnd,SW_SHOWNORMAL
.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
ShowBitMap endp
WndProc proc hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
LOCAL ps:PAINTSTRUCT
LOCAL hdc:HDC
LOCAL hMemoryDC:HDC
LOCAL hOldBmp:DWORD
LOCAL bitmap:BITMAP
LOCAL DlgHeight:DWORD
LOCAL DlgWidth:DWORD
LOCAL DlgRect:RECT
LOCAL DesktopRect:RECT
.if uMsg==WM_DESTROY
.if hBitMap!=0
invoke DeleteObject,hBitMap
.endif
invoke PostQuitMessage,NULL
.elseif uMsg==WM_CREATE
invoke GetWindowRect,hWnd,addr DlgRect
invoke GetDesktopWindow
mov ecx,eax
invoke GetWindowRect,ecx,addr DesktopRect
push 0
mov eax,DlgRect.bottom
sub eax,DlgRect.top
mov DlgHeight,eax
push eax
mov eax,DlgRect.right
sub eax,DlgRect.left
mov DlgWidth,eax
push eax
mov eax,DesktopRect.bottom
sub eax,DlgHeight
shr eax,1
push eax
mov eax,DesktopRect.right
sub eax,DlgWidth
shr eax,1
push eax
push hWnd
call MoveWindow
invoke LoadBitmap,hInstance,addr BitmapName
mov hBitMap,eax
invoke SetTimer,hWnd,1,2000,NULL
mov TimerID,eax
.elseif uMsg==WM_TIMER
invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL
invoke KillTimer,hWnd,TimerID
.elseif uMsg==WM_PAINT
invoke BeginPaint,hWnd,addr ps
mov hdc,eax
invoke CreateCompatibleDC,hdc
mov hMemoryDC,eax
invoke SelectObject,eax,hBitMap
mov hOldBmp,eax
invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap
invoke StretchBlt,hdc,0,0,250,250,\
hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY
invoke SelectObject,hMemoryDC,hOldBmp
invoke DeleteDC,hMemoryDC
invoke EndPaint,hWnd,addr ps
.elseif uMsg==WM_LBUTTONDOWN
invoke DestroyWindow,hWnd
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
End DllEntry
分析:
首先,我们分析主程序的代码。
invoke LoadLibrary,addr Libname
.if eax!=NULL
invoke FreeLibrary,eax
.endif
我们调用LoadLibrary函数来装载名为“splash.dll的动态链接库。其后,调用FreeLibrary函数从内存中卸载它。直到dll文件完成它的初始化,否则LoadLibrary将不返回。
这就是所有的主程序,有趣的部分在dll中。
.if reason==DLL_PROCESS_ATTACH ; When the dll is loaded
push hInst
pop hInstance
call ShowBitMap
当dll被装载后,windows调用有DLL_PROCESS_ATTACH标志的入口函数。我们用这个机会显示启动画面。首先,我们储存dll的实例句柄以便将来使用。然后调用名为ShowBitmap的函数去做真正的工作。ShowBitMap注册一个窗口类,然后创建一个窗口进入消息循环,就像以前创建窗口一样。有趣的部分在CreateWindowEx调用中:
INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\
WS_POPUP,CW_USEDEFAULT,\
CW_USEDEFAULT,250,250,NULL,NULL,\
hInstance,NULL
注意,窗口样式仅能设为WS_POPUP,这个标志使窗口没有边框和标题栏。我们还限制了窗口的大小为250*250像素。
现在,当窗口被创建时,在WM_CREATE的消息处理过程中,我们用下面的代码移动窗口到屏幕的中心位置。
invoke GetWindowRect,hWnd,addr DlgRect
invoke GetDesktopWindow
mov ecx,eax
invoke GetWindowRect,ecx,addr DesktopRect
push 0
mov eax,DlgRect.bottom
sub eax,DlgRect.top
mov DlgHeight,eax
push eax
mov eax,DlgRect.right
sub eax,DlgRect.left
mov DlgWidth,eax
push eax
mov eax,DesktopRect.bottom
sub eax,DlgHeight
shr eax,1
push eax
mov eax,DesktopRect.right
sub eax,DlgWidth
shr eax,1
push eax
push hWnd
call MoveWindow
它重新获取桌面和窗口的尺寸规格,然后计算窗口左上角合适的坐标来使它位于屏幕中心。
invoke LoadBitmap,hInstance,addr BitmapName
mov hBitMap,eax
invoke SetTimer,hWnd,1,2000,NULL
mov TimerID,eax
下一步,用LoadBitmap函数从资源文件中装载位图并创建一ID值为1,时间间隔为2秒的定时器。这个定时器将每隔两秒就发送一WM_TIMER消息给窗口。
.elseif uMsg==WM_PAINT
invoke BeginPaint,hWnd,addr ps
mov hdc,eax
invoke CreateCompatibleDC,hdc
mov hMemoryDC,eax
invoke SelectObject,eax,hBitMap
mov hOldBmp,eax
invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap
invoke StretchBlt,hdc,0,0,250,250,\
hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY
invoke SelectObject,hMemoryDC,hOldBmp
invoke DeleteDC,hMemoryDC
invoke EndPaint,hWnd,addr ps
当窗口接收到WM_PAINT消息时,它创建一内存DC,并将位图选进DC中,然后调用GetObject 函数获取位图的大小,随后调用StretchBlt函数将位图放在窗口上,StretchBlt函数功能和BitBlt一样,但是它能改变位图的大小来得到我们渴望的尺寸大小。在这个例子中,我们希望位图的大小和我们窗口相符合所以我们调用StretchBlt函数来代替了BitBlt函数。最后,我们删除内存DC。
.elseif uMsg==WM_LBUTTONDOWN
invoke DestroyWindow,hWnd
如果用户每次使用软件的时候都得等到启动画面消失后才能正常使用,这会让启动画面相当的厌烦。我们能提供给用户一个选择。当他在启动画面上点击时,画面就消息。这就是为什么我们要在Dll中处理WM_LBUTTONDOWN消息的原因。于是,当收到这个消息时,调用DestroyWindow函数来销毁窗口。
.elseif uMsg==WM_TIMER
invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL
invoke KillTimer,hWnd,TimerID
如果用户选择等待,启动画面将在指定的时间值达到时才消息。(在我们的例子只能给,它为2秒)。我们通过处理WM_TIMER消息来做到这点。于是,当接受到这个消息时,我们通过发送WM_LBUTTONDOWN消息给窗口来将窗口关闭。这避免了代码副本的出现。我们在以后都用不上定时器,所以调用KillTimer函数销毁它。
当窗口关闭后,dll将控制权返还给主程序。