Zoom Icon

Iczelion 26

From UIC

Tutorial 26: Splash Screen

Contents


Iczelion 26
Author: Iczelion
Email: Translate: Death_Reaver
Website: Mirror
Date: 06/03/2009 (dd/mm/yyyy)
Level: Luck and skills are required
Language: Italian Flag Italian.gif
Comments: Formattazione Wiki: Antelox


Introduzione

Ora che sappiamo come usare una bitmap, siamo in grado di fare qualcosa di più avanzato con essa.


Tools

Esempio


Essay

Una splash-screen è una finestra che non ha la barra del titolo, il system menu, bordi che mostra soltanto una bitmap per un pò e poi sparisce automaticamente. Di solito è usata durante l'avvio di un programma per mostrare il logo di tale programma o distrarre l'attenzione dell'utente mentre il programma ha lunghe fasi di inizializzazione. In questo tutorial implementeremo una splash-screen. Il primo passo è di includere la bitmap nel file di risorse. Se ci pensi però, è una grande perdita di memoria caricare la bitmap che useremo solo una volta e tenerla in memoria fino alla chiusura del programma. Una soluzione ottimale sarebbe di creare una "DLL di risorse" che contenga la bitmap e che abbia il solo compito di mostrare lo splash-screen. In questo modo puoi caricare la DLL quando vuoi mostrare la splash-screen e scaricarla quando non serve più. Perciò avremo due moduli: il programma principale e la "splash-DLL". Metteremo la bitmap nelle risorse della DLL.

Lo schema generale che seguiremo sarà questo:

  1. Mettere la bitmap nella DLL come risorsa
  2. Il programma principale chiamerà LoadLibrary per caricare la DLL.
  3. La funzione di entry-point della DLL sarà chiamata. Creerà un timer settando il tempo in cui la splash-screen resterà visibile. Dopo registreremo e creeremo una finestra senza titolo e bordi e infine metteremo la bitmap nella client-area.
  4. Quando il tempo specificato sopra sarà passato, la splash-screen sarà rimossa dallo schermo e il controllo tornerà al programma principale.
  5. Il programma principale chiamerà FreeLibrary per scaricare la DLL dalla memoria e quindi farà i compiti che deve.

Esamineremo i punti in dettaglio.

Caricamento/Scaricamento DLL

Puoi caricare dinamicamente una DLL con LoadLibrary che ha la seguente sintassi:

LoadLibrary PROTO lpDLLName:DWORD

Prende solo un parametro: l'indirizzo al quale è contenuto il nome della DLL da caricare in memoria. Se la chiamata ha successo, ritorna con l'handle della DLL altrimenti ritorna con NULL. Per scaricare la DLL si chiama FreeLibrary

FreeLibrary proto hLib:DWORD

Prende solo un parametro: l'handle della DLL da scaricare. Normalmente l'handle lo prendi da LoadLibrary.

Come usare un Timer

Per prima cosa devi crearlo con SetTimer

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

hWnd è l'handle della finestra che riceverà i messaggi di notifica. Questo parametro può essere NULL per specificare che non c'è nessuna finestra associata al timer;

TimerID è un valore definito dell'utente che è usata come ID del timer;

uElapse è l‘intervallo (ogni quando manda i messaggi di notifica, in millisecondi);

lpTimerFunc è l'indirizzo di una funzione che processerà il messaggio di notifica del timer. Se è settato a NULL i messaggi del timer saranno mandati alla finestra specificata da hWnd (WM_TIMER);

SetTimer, se ha successo, restituisce l'ID di un timer. Altrimenti restituisce NULL. Quindi è meglio non associare a una TimerID zero

Puoi creare un timer in due modi:

  • Se hai una finestra e vuoi che i messaggio di notifica del timer vadano alla tua finestra devi passare tutti i parametri a SetTimer e inoltre lpTimerFunc deve essere NULL.
  • Se non hai una finestra o non vuoi processare i messaggi del timer nella WndProc, devi passare NULL al valore di hWnd. Devi inoltre specificare l'indirizzo della timer-function che processerà i messaggi. Useremo il primo approccio nell'esempio.

Quando il periodo di intervallo scade, un WM_TIMER è mandato alla finestra associata al timer. Per esempio se specifichi uElapse a 1000, la tua finestra riceverà un WM_TIMER ogni secondo.

Quando non hai più bisogno di un timer, va distrutto con KillTimer

KillTimer proto hWnd:DWORD,TimerID:DWORD

Esempio

;--------------------------------------------------------------------------------------------------------
;                         Il programma principale
;--------------------------------------------------------------------------------------------------------
.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

;--------------------------------------------------------------------------------------------------------
;                            La 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  ; quando il processo è caricato
      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


Analisi

Esamineremo il codice del programma per primo.

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

Chiamiamo LoadLibrary per caricare la DLL chiamata "splash.dll". Dopodichè la scarichiamo con FreeLibrary. LoadLibrary ritornerà solo quando la DLL ha finito la sua inizializzazione. E' tutto quello che fà il programma. Il bello è nella DLL.

.if reason==DLL_PROCESS_ATTACH  ; quando il processo è caricato
      push hInst
      pop hInstance
      call ShowBitMap

quando la DLL è caricata, Windows chiama la sua funzione di entry-point con il flag DLL_PROCESS_ATTACH. Usiamo questa opportunità per mostrare la splash-screen.

Per prima cosa, mettiamo al sicuro l'handle della DLL per uso futuro. Quindi chiamiamo una funzione (ShowBitMap) la quale fa il vero lavoro. ShowBitMap registra una window-class(classe di finestra), crea una finestra ed entra in message-loop come al solito. La parte interessante è nella chiamata a CreateWindowsEx:

INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,
           WS_POPUP,CW_USEDEFAULT,
           CW_USEDEFAULT,250,250,NULL,NULL,
           hInstance,NULL

Notare che lo stile finestra è solo WS_POPUP che creerà una finestra senza bordi e senza titolo. Inoltre limitiamo l'altezza e la larghezza della finestra a 250x250 pixel.

Quando la finestra è creata, in WM_CREATE moviamo la finestra al centro dello schermo con il seguente codice:

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

Restituisce le dimenzioni del desktop e la finestra quindi calcola le giuste coordinate dall'angolo superiore sinistro per mettersi al centro.

invoke LoadBitmap,hInstance,addr BitmapName
                mov hBitMap,eax
                invoke SetTimer,hWnd,1,2000,NULL
                mov TimerID,eax

Poi carica la bitmap dal file di risorse con LoadBitmap e crea un timer con ID == 1 e l'intervallo di 2 secondi (2000 mmsec). Il timer manderà WM_TIMER alla finestra ogni 2 secondi.

.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

Quando la finestra riceve un WM_PAINT, crea un DC, un mDC (device context in memoria), ci inserisce la bitmap, ne ottiene la grandezza con GetObject e infine mette la bitmap nella finestra con StretchBlt che funziona come BitBlt ma può allargare o stringere la bitmap per raggiungere la dimensioni volute. In questo caso vogliamo che la bitmap riempia la finestra percui usiamo StretchBlt invece che BitBlt. Dopo questa operazione cancelliamo il DC.

.elseif uMsg==WM_LBUTTONDOWN
                invoke DestroyWindow,hWnd

Sarebbe frustrante per l'utente aspettare la scomparsa della splash-screen. Possiamo risolvere questo problema con una scelta: quando clicka sulla splash-screen, questa sparisce. E' l'unico motivo per il quale processiamo WM_LBUTTONDOWN nella DLL. Quando riceve tale messaggio, la finestra viene distrutta chiamando DestroyWindow.

.elseif uMsg==WM_TIMER
                invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL
                invoke KillTimer,hWnd,TimerID

Se l'utente sceglie di aspettare, la splash-screen sparirà quando il timer avrà raggiunto l'intervallo specificato (2 secondi). Possiamo farlo processando il messaggio WM_TIMER. Quando si riceviamo tale messaggio chiudiamo la finestra mandando un WM_LBUTTONDOWN alla finestra (con SendMessage). In questo modo ci risparmiamo di scrivere due volte il codice. Non abbiamo più bisogno del timer, percui lo distruggiamo con KillTimer. Quando la finestra è chiusa, la DLL ridarà il controllo al programma principale.


Note Finali

Spero che la traduzione sia stata chiara. Per eventuali chiarimenti o altro, contattatemi (l'indirizzo e-mail è sopra). Un saluto a TUTTI!!!!!!!


Disclaimer

I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.

Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevole e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.