2007. 2. 16. 14:31
DevX Win32 Assembly Tutorial 26: Splash Screen

Tutorial 26: Splash Screen

이전 장을 읽었다면 비트맵에 대한 지식이 있을 것이므로, 이 번장에서는 비트맵을 응용하여 「스플래쉬 스크린」에 대해서 설명한다.
 메인 소스    비트 맵    실행 결과  
 DLL 소스    DEF 파일    리소스  

Theory:

스플래쉬 스크린이라는 것은 타이틀바와 시스템메뉴를 가지지 않는 윈도우로서 비트맵을 일정시간 표시한 후, 자동으로 소멸되는 윈도우이다. 프로그램의 시작 시에 자주 사용되고 로고를 표시하거나 실행시간이 길어질 경우 유저로 하여금 동작중임을 알리는 역활도 하게된다. 이 번장에서는 스플래쉬 스크린을 실제로 작성해 본다.

먼저, resource file에 비트맵을 추가한다. 그러나 이런 경우, 프로그램이 종료할 때까지 비트맵을 로드하고 있기 때문에 메모리를 낭비하게 된다. 해결방법은, 비트맵을 포함하는 리소스 DLL을 작성하는 것이다.리소스 DLL은, 단지 스플래쉬 스크린을 저장하는 역활만 하게 된다. 이런 방법을 이용하면, 화면에 표시하고 싶은때에만 DLL을 로드하고, 필요없어 지면 언로드를 수행해서 메모리에서 해제하게 되서 메모리 효율성이 높아진다. 당연히, 2개의 모듈이 필요하게 된다. 한개는 메인 프로그램이고, 다른 하나는 스플래쉬 DLL로서, DLL에 비트맵을 저장한다.

사용하는 방법은 다음과 같다.

  1. 비트맵 리소스로서 DLL에 비트맵을 저장한다
  2. 메인 프로그램에서, LoadLibrary 함수를 호출해 DLL을 로드한다
  3. DLL의 엔트리 포인트 함수가 실행된다. 타이머를 생성하고, 스플래쉬 스크린이 일정시간 동안 표시되는지를 지정한다. 다음으로, 타이틀바와 경계선이 없는 윈도우를 등록한 후, 생성해서, 작업영역에 비트맵을 표시한다.
  4. 일정한 시간이 지나면, 스플래쉬 스크린은 화면에서 사라지고, 제어가 메인 프로그램에 돌아온다.
  5. 메인 프로그램에서, FreeLibrary 함수를 호출해서, 메모리로부터 DLL를 해제한다. 그후, 자신의 처리를 계속한다.

그럼, 단계별로 설명한다.

●DLL의 로드/언로드

LoadLibrary 함수를 호출함으로써 동적으로 DLL을 로드할 수가 있다.

LoadLibrary proto lpDLLName:DWORD

이 함수는 단지 하나의 인수만 가지며,로드하고 싶은 DLL의 문자열을 설정한다. 호출이 성공적으로 수행되면 DLL의 모듈핸들이 반환되고,실패하게되면 NULL이 반환된다.
DLL를 언로드하려면 , FreeLibrary 함수를 호출한다.

FreeLibrary proto hLib:DWORD

이 함수역시 한개의 인수를 가지며, 언로드하고 싶은 DLL의 모듈핸들을 설정한다. 보통, LoadLibrary 함수로부터 얻은 핸들이다.

●타이머의 사용법

타이머를 사용하려면 먼저, SetTimer 함수를 호출한다.

SetTimer proto hWnd:DWORD, TimerID:DWORD, uElapse:DWORD, lpTimerFunc:DWORD

  • hWnd
    타이머로부터 통지메세지를 받는 윈도우핸들. 타이머로부터 통지를 받는 윈도우가 없을 경우는 NULL을 설정.
  • TimerID
    타이머ID로서, 유저가 자유롭게 정의할 수 있다.
  • uElapse
    타이머 메세지의 발생주기를 밀리 세컨드로 지정한다.(1000 == 1 초)
  • lpTimerFunc
    타이머메세지가 발생할 경우 실행되는 함수의 포인터(타이머전용 처리함수). NULL을 지정하면, 타이머 메세지는 hWnd에 지정된 윈도우로 전송된다.

함수가 성공적으로 수행되면, SetTimer 함수는 타이머ID를 반환하고, 실패한다면 NULL을 반환한다.

타이머를 생성하는 방법은 다음의 2가지가 있다.

  1. 타이머 메세지를 전송하고 싶은 윈도우가 있는 경우, SetTimer 함수에 모든 인수를 설정해야한다. (lpTimerFunc은 NULL로 설정)
  2. 윈도우가 없는 경우와 타이머 메세지를 윈도우 프로시저에서 처리하고 싶지 않은 경우에는, 윈도우 핸들 hWnd 에 NULL을 설정하고, 타이머 메세지를 처리하는 타이머함수의 포인터를 지정해야 한다.

예제에서는 처음의 방법을 사용한다.

일정한 시간이 경과했을때, WM_TIMER 메세지가 윈도우에 전송된다. 예를 들면, uElapse를 1000으로 하면, WM_TIMER 메세지가 지정한 윈도우로 1초 마다 전송된다.

타이머가 필요없다면, KillTimer 함수를 호출해서 타이머를 해제한다.(타이머도 시스템 자원이다)

KillTimer proto hWnd:DWORD, TimerID:DWORD

Example:


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


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

Analysis:

메인 프로그램은 다음 코드로 시작하고 있다.

invoke LoadLibrary, addr Libname
.if eax != NULL
  invoke FreeLibrary, eax
.endif

이것은,"splash.dll"이라는 리소스DLL을 로드하는 코드이다. 그리고, FreeLibrary 함수로 언로드하고 있다. LoadLibrary 함수는 DLL의 초기화가 완료될 때까지 제어를 반환하지 않게 되어있다.

메인 프로그램에서의 처리는 이것이 전부이다. 실제적인 처리는 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초마다(2000) 메세지를 발생하는 타이머를 생성한다. 이 타이머는 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 함수와 비슷하지만, 지정한 폭과 높이에 따라 크기를 확장하는 기능을 가지고 있다.예제에서는 생성한 윈도우에 딱맞도록 해야하기 때문에, BitBlt 함수가 아니고 StretchBlt 함수를 사용했다. 그 후, 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

유저가 아무런 일도 하지않는다면, 스플래쉬 스크린은 타이머가 지정한 시간이 되면 사라지게 되어 있다. 이는 WM_TIMER 메세지에서 처리하고 있지만, WM_LBUTTONDOWN 메세지에서 윈도우를 파괴하는 코드를 작성했으므로, 해당 메세지를 보내서 같은 코드를 사용하게 하였다.타이머는 이제 필요없기 때문에, KillTimer 함수를 사용해서 삭제한다.

윈도우가 화면에서 사라지면, DLL은 메인 프로그램에 제어를 반환하게 된다.



Posted by openserver