上一章我們學習了位圖的使用.在這一章我們要用上帝賦予我們的創造力來融會貫通上一章我們學到的知識.那就是研究如何用位圖來創建啟動畫面.
理論
首先,我們先要搞清楚什麼是啟動畫面.舉個簡單的例子:我們啟動某些作的專業一點的程序時(比如Netscape,Adobe Acrobat等)會先跳出一個啟動畫面.上面通常有一些版權信息,版本號等.與此同時,程序後台正做著一些程序的加載或初始化工作.這個啟動畫面有別於一般的窗口.它沒有標題欄,沒有系統菜單,也沒有邊框.只有一張位圖在屏幕上顯示一會兒,然後消失 .在這一章我們來試試自己做一個.
第一步你可能會想到把要顯示的位圖包含到資源文件中去.但是這樣做有一個缺點.你的程序只在啟動的時候顯示這張位圖,可是它卻至始至終存在於你的內存中,直到你把程序關掉.這不能不說是對內存的極大浪費.好辦法是:創建一個"資源"DLL(動態連接庫)來包含位圖和它獨特的顯示代碼.這樣,你就可以在想顯示啟動畫面的時候加載他,用完了就卸載它.所以,我們的程序需要2個模塊:主程序和啟動畫面DLL.我們要把位圖放到這個DLL的資源中去.
基本步驟如下:
把位圖作為一個位圖資源放到DLL中去.
主程序調用 LoadLibrary 把 dll 加載到內存中去.
DLL 被調用後,它會建立一個定時器用於管理啟動畫面顯示的時間.然後,注冊並創建一個沒有 標題和邊框的窗口.同時在窗口的客戶區顯示位圖.
等啟動畫面的顯示時間長度到達你預先的設定值,啟動畫面消失,控制權回到主程序手中.
主程序調用 FreeLibrary 從內存中卸載 DLL .然後,做它該做的事去.
下面我們來研究細節部分
加載/卸載 DLL
你可以用 LoadLibrary 靈活的加載一個 DLL ,它的格式如下:
LoadLibrary proto lpDLLName:DWORD
它只有一個參數: 你想要加載的 DLL 的名稱所在的地址.調用成功返回指向該DLL模塊的句柄,反之返回NULL.
要卸載 DLL, 則調用 FreeLibrary:
FreeLibrary proto hLib:DWORD
它也只有一個參數: 你想要卸載的 DLL 模塊的句柄(通常就是上面那個函數返回的啦).
怎樣使用定時器
首先, 你要用 SetTimer 創建一個定時器:
SetTimer proto hWnd:DWORD, TimerID:DWORD, uElapse:DWORD, lpTimerFunc:DWORD
hWnd接受這個定時器消息的窗口的句柄.如果,你的定時器不需要窗口接受它的消息,你也可以 用NULL作為參數
TimerID定時器的 ID 值. 由你自己定義.
uElapse 定時器定的時間.以ms(千分之一秒)為單位.
lpTimerFunc 處理該定時器消息的函數所在的地址.如果你用NULL作為該參數,那麼定時器的消息會被送給 hWnd 參數所指定的窗口.
SetTimer 如果成功則返回定時器的 ID 否則返回 NULL. 所以最好不要把定時器的ID設為0(Pheadnius:NULL代表0, 記得嗎?).
你可以用2種方法創建定時器:
如果你有一個窗口並且定時器把消息傳給這個窗口.那麼你需要把所有的4個參數都傳送給 Settimer 函數 (lpTimerFunc參數必須為NULL).
如果你沒有窗口或者你不想讓窗口處理定時器的消息,那麼你必須在窗口句柄中傳送一個NULL.同時你要指定用於處理定時器消息的函數的地址.
在這個例子中我們要使用第一種方法.
當你設定的時間到了, 與定時器相連的窗口會收到 WM_TIMER 消息.例如,你指定 uElapse 的值為 1000, 你的窗口每過一秒都會收到 WM_TIMER 消息.
等到你再也不需要這個定時器了,就用 KillTimer 來去除定時器.
KillTimer proto hWnd:DWORD, TimerID:DWORD
例子:
;-----------------------------------------------------------------------
; 主程序
;-----------------------------------------------------------------------
.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
;--------------------------------------------------------------------
; 位圖 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" 的 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 . 所以窗口即沒有標題欄,也沒有邊界. 我們同時也限定窗口的寬高為 250x250個像素.
現在窗口創建好了. 在 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 秒. 定時器將每 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(Pheadnius:還記得DC嗎. 在win32編程中你會經常遇到DC這個詞. 它是 Device Context 的縮寫, 官方譯為"設備描述表". 如果你研究過vc, 你應該對它不陌生. 不過如果你不明白它是什麼也不要緊. 你可以把它看作一個句柄. 就是某個設備或某塊內存的名稱.),然後把位圖選進內存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 把控制權還給主程序.