除了本身整合開發環境高效率,體積小,還可以將專案 dsp 檔轉成標準的 makefile 檔之外
就是第六版還附有所謂 17 大經典工具,這些工具被稱為 SDK Tools,它們會被稱為
經典不僅僅是各個程式小巧,而且每一支都很實用,更棒的就是這些工具本身原始碼
也是公開可以研究,這些工具不僅輔助開發程式的利器,本身每支工具的原始碼就是
示範 Win32 許多重要 API 的經典寶庫,我學習 Windows 程式設計的歷史中,絕大部分的
功底就是念透這些工具的原始碼,這個比買一本書看還有用,其中有六支值得仔細閱讀
1. aniedit : 可以創造與編輯動畫游標 ( animated cursors ),從原始碼會學習到游標相關 API
2. fontedit : 可以造字,從原始碼會學習到不少重要控制字型檔的 API
3. imagedit : 這個工具很實用,可以創造、修改與讀取 Icon,會學習到一個編輯工具要怎麼寫
4. spy : 此工具可以截取視窗的訊息如何流動是很重要的工具,順便學會 Hook DLL 怎麼寫
5. windiff : 可以比較兩個讀進來的文字檔有甚麼地方不同,可以用視覺化方式呈現
6. zoomin : 恩,這是今天要講的工具,它可以對螢幕局部作像素級放大還可以複製入剪貼簿
這六支工具的原始碼都相當重要,除了針對特殊用途的 API,還會看到很多常用不斷重複
與視窗有關的常用 API,另外除了學習 API 之外,還要好好看這些程式的架構,培養撰寫
建構結構化視窗程式框架的能力,那原始碼哪裡找?看看有沒有人還有 MSDN 6 的光碟,
SDK Tools 原始碼通通都在裡面,另外它們的環境都 "非常標準",就是說它們其實跟環境
無關,在指令列下 nmake 就可以直接產生可執行檔,所以它們連 "環境" 都很經典,也是學習
打造 makefile 指令檔的好對象,每支我都曾經在 VS2008 下重新編譯過,老程式馬上變成
新版本,這些經典工具都有後繼傳人,例如 spy 的強化版 spy++,可惜第六版之後
這些好工具就各自分家了,你還是可以一支一支慢慢收集,但是有一些新的進化版已經
不在提供原始碼了,蠻可惜的事情 。
有趣的地方就出現了,前一陣子我重新轉譯了 ZoomIn 這支工具,整個程式重新用
FASM 寫了一遍,其實發現了不少好玩的地方與困難點,我並不想講解 ZoomIn 工具
原始碼本身的原理,而是要談談,我在轉譯過程中遇到的一些困難與樂趣,那我要拿
那一部分呢?就是整個 ZoomIn 的 C 程式裡面最難轉譯的副程式 CreatePhysicalPalette
這個副函式的用途我不想多談,反正就是一種 GDI 物件,可以創造實體調色盤,利用
調色盤 ( Palette ) 這種玩意,可以讓 ZoomIn 工具達到快速縮放像素級畫面的功能,有些
人仿 ZoomIn 這類的工具只學半套,以為只要會 StretchBlt 這個 API 就可以達成,但是發現
怎麼做出來的效率跟 ZoomIn 差了一大截,那就是因為還忘了要使用 Palette 這種 GDI 物件
沒有用 Palette,StretchBlt 就會很頓,因為 Palette 可是直接把顏色作查表阿,這種速度當然
不是 RGBA 顏色系統可以拼的囉,原理講到這,我們應該把重點擺在,怎麼把條副函式
的 C 版本改用 FASM 實現才對,現在讓我們看看這條 C 函式原本的面貌
原始 ZoomIn 中 CreatePhysicalPalette 的 C 原始碼 :
HPALETTE CreatePhysicalPalette(VOID) { PLOGPALETTE ppal; HPALETTE hpal = NULL; INT i; ppal = (PLOGPALETTE)LocalAlloc(LPTR, sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * NPAL); if (ppal) { ppal->palVersion = 0x300; ppal->palNumEntries = NPAL; for (i = 0; i < NPAL; i++) { ppal->palPalEntry[i].peFlags = (BYTE)PC_EXPLICIT; ppal->palPalEntry[i].peRed = (BYTE)i; ppal->palPalEntry[i].peGreen = (BYTE)0; ppal->palPalEntry[i].peBlue = (BYTE)0; } hpal = CreatePalette(ppal); LocalFree(ppal); } return hpal; }
各位可以看到,這個程式碼使用了 Win32 API 中邏輯調色盤結構 LOGPALETTE,這個其實
就是一個色表的結構,而根據 MSDN 裡面記載,要配置色表的記憶體區塊,就是要計算
LOGPALETTE 結構大小加上有多少項 PALETTEENTRY,因此 NPAL 就是 PALETTEENTRY
的個數,最後呼叫 LocalAlloc 配置色表的記憶體區塊,下面迴圈就沒有甚麼,對每個色表
內的項目填值而已,細部用途就不解釋了,迴圈跑完就放入 CreatePalette 用這個設定好的
邏輯調色盤創造調色盤,獲得調色盤的 Handle 後就可以釋放剛剛配置的記憶體了,然後
回傳調色盤的 Handle,這個函式用 C 寫很簡單,但是我發現用 FASM 寫就不好搞了,首先
它有宣告三個區域變數,另外很糟糕的一件事,LOGPALETTE 在 FASM 內提供的引入標頭
竟然沒有,那怎麼辦呢?這個就要考驗你怎麼善用 x86 組合語言的 Register 囉。
在寫組合語言的時候,有一個很重要的觀念要先建立,就是 Register 有分兩大類
第一類是 Volatile 與 第二類是 Nonvolatile
這是許多寫組合語言常常會迷惑的地方,就是 " 目前要用什麼 Register,會不會有問題 "
這個問題的解答就是要弄懂哪些 Register 是 volatile 那些是 nonvolatile,假如是 volatile 就大膽
的用,不用怕,假如是 nonvolatile 也沒甚麼好怕, push 後在用,用完記得 pop 回來,這樣就
很清楚了,所以我的組語版 CreatePhysicalPalette 副函式我決定要偷吃步,用人工最佳化,誰說
一定要放在區域變數,各位還記得 Win32 API 會把回傳值放在 eax 嗎?恩,我們現在用
FASM 寫就有直接操作暫存器的優勢了,在組語裡面,我們手上有哪些籌碼可以讓我們偷吃
步呢? eax, ecx, edx, ebx, esi, edi, ebp 我們手上有這 7 個暫存器可以直接用喔,那我們幹嘛還要
宣告區域變數阿,直接用暫存器在 CPU 內部搬來搬去更有效率,所以下面就給出
我最後寫出來的版本。
CreatePhysicalPalette 的 FASM 原始碼 :
CreatePhysicalPalette: push ebx esi edi ; LPTR := 0x0040 Combines LMEM_FIXED and LMEM_ZEROINIT ; 1032 = sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * NPAL invoke LocalAlloc,LPTR,1032 cmp eax, 0 ; eax := PLOGPALETTE ppal je ExitCreatePhys ; exit subroutine if memory allocation failed mov word[eax],768 ; ppal->palVersion = 0x300 mov word[eax+2],NPAL ; ppal->palNumEntries xor ecx,ecx ; ecx = 0 lea edx,dword[eax+4] ; address of ppal->palPalEntry CreatePhys: mov byte[edx],cl ; ppal->palPalEntry.peRed mov byte[edx+1],0 ; ppal->palPalEntry.peGreen mov byte[edx+2],0 ; ppal->palPalEntry.peBlue mov byte[edx+3],2 ; ppal->palPalEntry.peFlags = PC_EXPLICIT inc ecx add edx,4 ; ppal->palPalEntry += 4 move to next that cmp ecx,NPAL jl CreatePhys mov edi,eax ; store ppal into edi invoke CreatePalette,edi ; edi := PLOGPALETTE ppal mov ebx,eax ; ebx <- eax := HPALETTE hpal invoke LocalFree,edi ; release memory mov eax,ebx ; return hpal ExitCreatePhys: pop edi esi ebx ret
我想第一行不用在做多解釋了,以前 FASM 文章出現過很多次了, ebx esi edi 這些是屬於
nonvolatile 的 register 反正先 push 就對了,接下來在組語裡面 invoke 配置記憶體的函式後,
後面組語威力強大的地方就出現了,因為我知道 API 一定會把返回值放在 eax 裡面,因此
直接用 word[eax] 直接就存取 LOGPALETTE 結構第一個欄位 palVersion 直接把 768 寫入
eax 指向的位址,word[eax+2] 直接將 NPAL 寫入結構第二個欄位 palNumEntries,所以跟
C 版完全不同的思路,直接操作 eax 就好了,不用宣告區域變數,直接使用暫存器更快
而且產生的機械碼更短,後面迴圈也不需要宣告變數,把 ecx 抓過來用,因為要存取
調色盤的項目, 調色盤項目本身又是結構,這邊就可以看到結構中的結構在組語中的面貌
就是用 基底位址+偏移位址 又變成另外新的基底位址,也就是程式碼裡面的
lea edx, dword[eax+4] , 為什麼是 dword 呢?因為位址其實本身就是一個 DWORD 的整數值,
然後接著持續利用暫存器偷懶,直接把新的基底位址放在 edx,指令 lea 是指
load effective address 的縮寫,表示載入有效位址,這樣 edx 就存放了 palPalEntry 的基底位址
所以各位可以看到 CreatePhys 標籤下就開始對每個 相對於 edx 的偏移值把值放進去,假如
ecx 一直小於 NPAL 的話,jl 指令就會跳回 CreatePhys 標籤 就形成了迴圈,每次 edx 都會把
自己累加 4 就相當於推進到下一個顏色項目的結構,整個跑完後,最後一樣繼續偷懶,把
eax 先移動到 edi 中,為甚麼呢?因為 API 回傳值會放在 eax,我們要在 eax 被破壞前趕快
先複製一份到其他的暫存器,這邊我用 edi,其實 ecx 上面跑完迴圈後就沒用了,也可以
抓過來用,這就是寫組語很有趣的地方,變化多端,暫存器當免費變數用,最後,把
edi 傳遞給 CreatePalette,然後把 eax 回傳的 HPALETTE 一樣先複製到 ebx,理由跟上面一樣
因為呼叫 LocalFree 會破壞 eax 的值,把 edi 傳入釋放記憶體,這邊仔細看,組語擅長的
共用技巧再次出現啦,edi 被連用兩次於 CreatePalette 與 LocalFree,從暫存器 push 值絕對比
從記憶體 push 來的更快,而且產生的機械碼更短,最後當然就是把回傳值放入 eax 啦,所以
把剛剛的 ebx 在放入 eax 中就完成了,在 FASM 版本中我走了一條完全不同的思考方法,
完全只用 register 就轉譯了這個函式,這邊告訴我們撰寫組語就要用組語這種語言的腦袋來
思考,盡可能只使用暫存器當變數用,盡可能思考如何只在 CPU 移動資料達成更高的性能
與更簡潔的程式碼,沒有必要盡量不要使用堆疊配置區域變數使用,這是玩組語有趣之處
也是困難的地方,這基本上就是把自己當組譯器用作程式最佳化的問題,這個絕對不是
把 ZoomIn 的 C 原始碼用編譯器產生組語,然後把 MASM 語法改成 FASM 語法那麼容易,
從轉譯 CreatePhysicalPalette 副函式這個例子來看,我在 FASM 的做法已經完全跟原來在
C 版本的原始碼意義完全不同了,有興趣的讀者可以把 ZoomIn 原始的 C 程式碼用 CL 編譯
器產生組語看看,因為受制於得要機械化的將 C 語言轉成組合語言,本篇這種共用有限的
暫存器存放各個階段回傳值的技巧,編譯器就不可能產生這種型式的組語碼,因為編譯器
必須要確保暫存器之間不會衝突導致結果錯誤,一般保守的透過區域變數 ( 堆疊 ) 當媒介
作資料交換,常寫程式的人可能就會碰到因為選擇編譯器最佳化選項,可能導致產生出錯誤
的執行結果 ( 我在編譯 Firefox 原始碼就有碰過,後來不使用任何最佳化編譯程式碼就沒問題 )
可是寫組語使用暫存器的直接控制大權就是落在程式設計者手中,只要你的腦袋夠清醒,寫
出完全只用 register 在 CPU 內移動的高效率程式也是有可能達成的事情,至少盡可能的使用
register 當變數用以提升程式效率。
讀者可能不知道那些暫存器是 volatile 那些是 nonvolatile,這邊特別給個表格讓大家參考
現在 64-bit 時代了,直接看 64-bit 的暫存器表格比較實在,32-bit 你把前面的 r 看成 e 就
對啦,例如 rax 就看成 eax,當然還有很多是 32-bit 世界裡面沒有的東西,這表示甚麼呢
恩, 64-bit 下 CPU 內有更多天生的免費變數可以用啦,不用宣告。
The following table describes how each register is used across function calls:
Register
|
Status
|
Use
|
RAX
|
Volatile
|
Return value register
|
RCX
|
Volatile
|
First integer argument
|
RDX
|
Volatile
|
Second integer argument
|
R8
|
Volatile
|
Third integer argument
|
R9
|
Volatile
|
Fourth integer argument
|
R10:R11
|
Volatile
|
Must be preserved as needed by caller; used in syscall/sysret instructions
|
R12:R15
|
Nonvolatile
|
Must be preserved by callee
|
RDI
|
Nonvolatile
|
Must be preserved by callee
|
RSI
|
Nonvolatile
|
Must be preserved by callee
|
RBX
|
Nonvolatile
|
Must be preserved by callee
|
RBP
|
Nonvolatile
|
May be used as a frame pointer; must be preserved by callee
|
RSP
|
Nonvolatile
|
Stack pointer
|
XMM0
|
Volatile
|
First FP argument
|
XMM1
|
Volatile
|
Second FP argument
|
XMM2
|
Volatile
|
Third FP argument
|
XMM3
|
Volatile
|
Fourth FP argument
|
XMM4:XMM5
|
Volatile
|
Must be preserved as needed by caller
|
XMM6:XMM15
|
Nonvolatile
|
Must be preserved as needed by callee.
|
我想上面這個表格就很清楚囉,64-bit 世界比較特別的是還有通用的 R8~R15 可以用
彈性也比較大,當然這些暫存器 32-bit 模式下是不能用的喔,要在 64-bit 下的長模式
才可以用。
大寫 F 表示 ZoomIn 的 FASM 重寫版,很想玩這支程式的話,就把原始碼貼到 FASMW
自己編譯一下吧,記得找一個 ZOOMIN.ICO 跟原始檔放在同一個目錄,因為程式碼
內有資源區段,隨便找個 ICO 改個檔名。
; Microsoft ZoomIn utility for FASM version. ; This tool magnifies a portion of the screen, ; allowing you to see things at a pixel level. ; Translated by UBIWU format PE GUI 4.0 entry start include 'win32a.inc' macro jif op1,cond,op2,label { cmp op1,op2 j#cond label } macro jeif op1,op2,label { cmp op1,op2 je label } macro jeif_umsg op1,label { jif [umsg],e,op1,label } macro ret0 label { xor eax,eax jmp label } macro valbound op1,min,max { jif op1,ge,min,@F mov op1,min @@: jif op1,le,max,@F mov op1,max @@: } macro memmov op1,op2 { mov eax,op2 mov op1,eax } macro splitword op1 { mov eax,op1 movsx ecx,ax ; ecx := loword(op1) shr eax,16 movsx edx,ax ; edx := hiword(op1) } ; CS_BYTEALIGNCLIENT+CS_VREDRAW+CS_HREDRAW csStyle = 00001003h ; CAPTION+OVERLAPPED+SYSMENU+THICKFRAME+MINIMIZEBOX+VSCROLL flStyle = 14ee0000h ; EX_LEFT+EX_LTRREADING+EX_RIGHTSCROLLBAR+EX_WINDOWEDGE flStyleEx = 00000100h MIN_ZOOM = 1 MAX_ZOOM = 32 FASTDELTA = 8 MM10PERINCH = 254 ; Tenths of a millimeter per inch NPAL = 256 ; Number of palette entries. ; Resource ID IDI_MAINICON = 17 section '.text' code readable executable start: invoke GetModuleHandle,0 mov [ghInst],eax mov [wc.hInstance],eax invoke LoadIcon,eax,IDI_MAINICON mov [wc.hIcon],eax invoke LoadCursor,0,IDC_ARROW mov [wc.hCursor],eax invoke GetStockObject,BLACK_BRUSH mov [wc.hbrBackground],eax invoke RegisterClass,wc call CreatePhysicalPalette mov [ghpalPhysical],eax invoke GetSystemMetrics,SM_CXSCREEN inc eax mov [gcxScreenMax],eax invoke GetSystemMetrics,SM_CYSCREEN inc eax mov [gcyScreenMax],eax mov eax,44 imul eax,[gnZoom] ; eax = gnZoom*44 mov ecx,eax ; ecx := _dx mov eax,36 imul eax,[gnZoom] ; edx:eax = gnZoom*36 mov edx,eax ; edx := _dy invoke SetRect,rc,0,0,ecx,edx invoke AdjustWindowRect,rc,flStyle,1 mov eax,[rc.right] sub eax,[rc.left] ; eax := Window width mov ecx,[rc.bottom] sub ecx,[rc.top] ; ecx := Window height invoke CreateWindowEx,flStyleEx,szClassName,szAppName,flStyle,\ CW_USEDEFAULT,0,eax,ecx,\ NULL,NULL,[ghInst],NULL test eax,eax jz end_loop mov [ghwndApp],eax invoke ShowWindow,eax,SW_SHOWNORMAL msg_loop: invoke GetMessage,msg,NULL,0,0 cmp eax,1 jb end_loop jne msg_loop invoke TranslateMessage,msg invoke DispatchMessage,msg jmp msg_loop end_loop: invoke ExitProcess,[msg.wParam] CreatePhysicalPalette: push ebx esi edi ; LPTR := 0x0040 Combines LMEM_FIXED and LMEM_ZEROINIT ; 1032 = sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * NPAL invoke LocalAlloc,LPTR,1032 cmp eax, 0 ; eax := PLOGPALETTE ppal je ExitCreatePhys ; exit subroutine if memory allocation failed mov word[eax],768 ; ppal->palVersion = 0x300 mov word[eax+2],NPAL ; ppal->palNumEntries xor ecx,ecx ; ecx = 0 lea edx,dword[eax+4] ; address of ppal->palPalEntry CreatePhys: mov byte[edx],cl ; ppal->palPalEntry.peRed mov byte[edx+1],0 ; ppal->palPalEntry.peGreen mov byte[edx+2],0 ; ppal->palPalEntry.peBlue mov byte[edx+3],2 ; ppal->palPalEntry.peFlags = PC_EXPLICIT inc ecx add edx,4 ; ppal->palPalEntry += 4 move to next that cmp ecx,NPAL jl CreatePhys mov edi,eax ; store ppal into edi invoke CreatePalette,edi ; edi := PLOGPALETTE ppal mov ebx,eax ; ebx <- eax := HPALETTE hpal invoke LocalFree,edi ; release memory mov eax,ebx ; return hpal ExitCreatePhys: pop edi esi ebx ret proc AppWndProc hwnd,umsg,wparam,lparam push ebx esi edi jeif_umsg WM_CREATE ,.wmcreate jeif_umsg WM_PAINT ,.wmpaint jeif_umsg WM_SIZE ,.wmsize jeif_umsg WM_LBUTTONDOWN,.wmlbuttondown jeif_umsg WM_LBUTTONUP ,.wmlbuttonup jeif_umsg WM_MOUSEMOVE ,.wmmousemove ;jeif_umsg WM_SETFOCUS ,.wmsetfocus ;jeif_umsg WM_COMMAND ,.wmcommand jeif_umsg WM_VSCROLL ,.wmvscroll jeif_umsg WM_KEYDOWN ,.wmkeydown jeif_umsg WM_CLOSE ,.wmclose jeif_umsg WM_DESTROY ,.wmdestroy .defwndproc: invoke DefWindowProc,[hwnd],[umsg],[wparam],[lparam] jmp .finish .wmcreate: invoke SetScrollRange,[hwnd],SB_VERT,MIN_ZOOM,MAX_ZOOM,FALSE invoke SetScrollPos,[hwnd],SB_VERT,[gnZoom],FALSE ret0 .finish .wmpaint: invoke BeginPaint,[hwnd],ps stdcall DoTheZoomIn,[ps.hdc] invoke EndPaint,[hwnd],ps ret0 .finish .wmsize: stdcall CalcZoomedSize ret0 .finish .wmlbuttondown: ; ecx := loword(lparam) ; edx := hiword(lparam) splitword [lparam] mov [gptZoom.x],ecx mov [gptZoom.y],edx invoke ClientToScreen,[hwnd],gptZoom stdcall DrawZoomRect stdcall DoTheZoomIn,NULL invoke SetCapture,[hwnd] mov [gfTracking],TRUE ret0 .finish .wmmousemove: jif [gfTracking],ne,1,@F stdcall DrawZoomRect splitword [lparam] ; ecx:=loword(lparam), edx:=hiword(lparam) mov [gptZoom.x],ecx mov [gptZoom.y],edx invoke ClientToScreen,[hwnd],gptZoom stdcall DrawZoomRect stdcall DoTheZoomIn,NULL @@: ret0 .finish .wmlbuttonup: jif [gfTracking],ne,1,@F stdcall DrawZoomRect invoke ReleaseCapture mov [gfTracking],FALSE @@: ret0 .finish .wmvscroll: mov eax,[wparam] ; eax = wparam jif ax,e,SB_LINEDOWN ,.sblinedown ; ax = LOWORD(wparam) jif ax,e,SB_LINEUP ,.sblineup jif ax,e,SB_PAGEUP ,.sbpageup jif ax,e,SB_PAGEDOWN ,.sbpagedown jif ax,e,SB_THUMBPOSITION,.sbthumb jif ax,e,SB_THUMBTRACK ,.sbthumb jmp @F .sblinedown: inc [gnZoom] jmp @F .sblineup: dec [gnZoom] jmp @F .sbpagedown: add [gnZoom],2 jmp @F .sbpageup: sub [gnZoom],2 jmp @F .sbthumb: sar eax,16 ; gnZoom = HIWORD(wparam) mov [gnZoom],eax @@: valbound [gnZoom],MIN_ZOOM,MAX_ZOOM invoke SetScrollPos,[hwnd],SB_VERT,[gnZoom],TRUE stdcall CalcZoomedSize stdcall DoTheZoomIn,NULL ret0 .finish .wmkeydown: jif [wparam],e,VK_UP ,.keydown jif [wparam],e,VK_DOWN ,.keydown jif [wparam],e,VK_LEFT ,.keydown jif [wparam],e,VK_RIGHT,.keydown jmp @F .keydown: invoke GetKeyState,VK_SHIFT and eax,8000h invoke GetKeyState,VK_CONTROL and ecx,8000h stdcall MoveView,[wparam],eax,ecx @@: ret0 .finish .wmclose: jif [ghpalPhysical],e,0,@F invoke DeleteObject,[ghpalPhysical] @@: invoke DestroyWindow,[hwnd] ret0 .finish .wmdestroy: invoke PostQuitMessage,0 xor eax,eax .finish: pop edi esi ebx ret endp ; Calculates some globals. This routine needs to be called any ; time that the size of the app or the zoom factor changes. proc CalcZoomedSize push ebx esi edi invoke GetClientRect,[ghwndApp],rc mov eax,[rc.right] cdq mov ecx,[gnZoom] ; gnZoom is divisor idiv ecx inc eax mov [gcxZoomed],eax ; gcxZoomed = (rc.right/gnZoom) + 1 mov eax,[rc.bottom] cdq idiv ecx inc eax mov [gcyZoomed],eax ; gcyZoomed = (rc.bottom/gnZoom) + 1 pop edi esi ebx ret endp ; Does the actual paint of the zoomed image. ; ; Arguments: ; HDC hdc - If not NULL, this hdc will be used to paint with. ; If NULL, a dc for the apps window will be obtained. proc DoTheZoomIn hdc local hpalOld : DWORD local fRelease : DWORD local hdcScreen: DWORD local x : DWORD local y : DWORD pushad ; ebx esi edi mov [fRelease],FALSE jif [hdc],ne,0,@F ; jmp @@ if hdc != 0 invoke GetDC,[ghwndApp] ; Get hdc from hwnd mov [hdc],eax ; hdc <- eax inc [fRelease] ; fRelease = 1 @@: jif [ghpalPhysical],e,0,@F ; jmp if ghpalPhysical == 0 invoke SelectPalette,[hdc],[ghpalPhysical],FALSE mov [hpalOld],eax invoke RealizePalette,[hdc] @@: mov edx,[gcxZoomed] sar edx,1 ; min in edx = gcxZoomed/2 mov eax,[gcxScreenMax] sub eax,edx ; max in eax = gcxScreenMax-(gcxZoomed/2) stdcall RangeBound,[gptZoom.x],edx,eax mov [x],eax mov ecx,[gcyZoomed] sar ecx,1 ; min in ecx = gcyZoomed/2 mov edi,[gcyScreenMax] sub edi,ecx ; max in edi = gcyScreenMax-(gcyZoomed/2) stdcall RangeBound,[gptZoom.y],ecx,edi mov [y],eax invoke GetDC,NULL mov [hdcScreen],eax invoke SetStretchBltMode,[hdc],COLORONCOLOR mov eax,[gcxZoomed] imul eax,[gnZoom] mov ecx,eax ; ecx = gnZoom*gcxZoomed mov eax,[gcyZoomed] imul eax,[gnZoom] mov edx,eax ; edx = gnZoom*gcyZoomed mov esi,[gcxZoomed] sar esi,1 ; esi = gcxZoomed/2 sub [x],esi ; x = x-gcxZoomed/2 sal esi,1 ; esi = gcxZoomed mov eax,[x] ; eax = x-gcxZoomed/2 mov edi,[gcyZoomed] sar edi,1 ; edi = gcyZoomed/2 sub [y],edi ; y = y-gcyZoomed/2 sal edi,1 ; edi = gcyZoomed mov ebx,[y] ; ebx = y-gcyZoomed/2 invoke StretchBlt,[hdc],0,0,ecx,edx,\ [hdcScreen],eax,ebx,esi,edi,SRCCOPY invoke ReleaseDC,0,[hdcScreen] jif [hpalOld],e,0,@F invoke SelectPalette,[hdc],[hpalOld],FALSE @@: jif [fRelease],e,0,@F invoke ReleaseDC,[ghwndApp],[hdc] @@: popad ; edi esi ebx ret endp ; This function moves the current view around. ; ; Arguments: ; INT nDirectionCode - Direction to move. Must be VK_UP, VK_DOWN, ; VK_LEFT or VK_RIGHT. ; BOOL fFast - TRUE if the move should jump a larger increment. ; If FALSE, the move is just one pixel. ; BOOL fPeg - If TRUE, the view will be pegged to the screen ; boundary in the specified direction. This overides ; the fFast parameter. proc MoveView nDirectionCode,fFast,fPeg push ebx esi edi ; ebx := delta mov ebx,1 jif [fFast],e,FALSE,@F mov ebx,FASTDELTA @@: jeif [nDirectionCode],VK_UP ,.vkup jeif [nDirectionCode],VK_DOWN ,.vkdown jeif [nDirectionCode],VK_LEFT ,.vkleft jeif [nDirectionCode],VK_RIGHT,.vkright jmp .finish .vkup: ; mov eax,[gptZoom.y] sub eax,ebx mov [gptZoom.y],eax cmp [fPeg],0 je .updown_rangebound mov eax,[gcyZoomed] sar eax,1 mov [gptZoom.y],eax jmp .updown_rangebound .vkdown: ; mov eax,[gptZoom.y] add eax,ebx mov [gptZoom.y],eax cmp [fPeg],0 je .updown_rangebound mov eax,[gcyScreenMax] mov ecx,[gcyZoomed] sar ecx,1 sub eax,ecx mov [gptZoom.y],eax .updown_rangebound: stdcall RangeBound,[gptZoom.y],0,[gcyScreenMax] mov [gptZoom.y],eax jmp .finish .vkleft: ; mov eax,[gptZoom.x] sub eax,ebx mov [gptZoom.x],eax cmp [fPeg],0 je .leftright_rangebound mov eax,[gcxZoomed] sar eax,1 mov [gptZoom.x],eax je .leftright_rangebound .vkright: ; mov eax,[gptZoom.x] add eax,ebx mov [gptZoom.x],eax cmp [fPeg],0 je .leftright_rangebound mov eax,[gcxScreenMax] mov ecx,[gcxZoomed] sar ecx,1 sub eax,ecx mov [gptZoom.x],eax .leftright_rangebound: stdcall RangeBound,[gptZoom.x],0,[gcxScreenMax] mov [gptZoom.x],eax jmp .finish .finish: stdcall DoTheZoomIn,NULL pop edi esi ebx ret endp ; This function draws the tracking rectangle. The size and shape of ; the rectangle will be proportional to the size and shape of the ; app's client, and will be affected by the zoom factor as well. proc DrawZoomRect ; local x : DWORD ; local y : DWORD pushad mov edx,[gcxZoomed] sar edx,1 ; min in edx = gcxZoomed/2 mov eax,[gcxScreenMax] sub eax,edx; ; max in eax = gcxScreenMax-(gcxZoomed/2) stdcall RangeBound,[gptZoom.x],edx,eax sub eax,edx ; eax = x - gcxZoomed/2 mov [rc.left],eax mov ecx,[gcyZoomed] sar ecx,1 ; min in ecx = gcyZoomed/2 mov edi,[gcyScreenMax] sub edi,ecx ; max in edi = gcyScreenMax-(gcyZoomed/2) stdcall RangeBound,[gptZoom.y],ecx,edi sub eax,ecx ; eax = y - gcyZoomed/2 mov [rc.top],eax mov eax,[gcxZoomed] add eax,[rc.left] mov [rc.right],eax mov eax,[gcyZoomed] add eax,[rc.top] mov [rc.bottom],eax invoke InflateRect,rc,1,1 invoke GetDC,NULL mov ebx,eax ; ebx := hdc mov eax,[rc.right] sub eax,[rc.left] ; eax = rc.right-rc.left invoke PatBlt,ebx,[rc.left],[rc.top],eax,1,DSTINVERT mov eax,[rc.top] sub eax,[rc.bottom] ; eax = -([rc.bottom]-[rc.top]) invoke PatBlt,ebx,[rc.left],[rc.bottom],1,eax,DSTINVERT mov eax,[rc.right] sub eax,1 ; eax = rc.right - 1 mov ecx,[rc.bottom] sub ecx,[rc.top] ; ecx = rc.bottom - rc.top invoke PatBlt,ebx,eax,[rc.top],1,ecx,DSTINVERT mov eax,[rc.bottom] sub eax,1 ; eax = rc.bottom - 1 mov ecx,[rc.left] sub ecx,[rc.right] ; ecx = -(rc.right-rc.left) invoke PatBlt,ebx,[rc.right],eax,ecx,1,DSTINVERT invoke ReleaseDC,NULL,ebx popad ret endp ; eax := return value proc RangeBound x,min,max push ebx esi edi mov ebx,[x] ; ebx := x cmp ebx,[min] jge @F ; jmp if x => min mov eax,[min] ; eax = min jmp .finish @@: cmp ebx,[max] ; jmp if x <= max jle @F mov eax,[max] ; eax = max jmp .finish @@: mov eax,ebx ; eax = x .finish: pop edi esi ebx ret endp section '.data' data readable writeable szClassName TCHAR 'FASMZOOMIN',0 ; Class name szAppName TCHAR 'FZoomIn',0 ; Application name ghInst dd ? ; Instance handle ghwndApp dd ? ; Main window handle gnZoom dd 4 ; Magnification factor ghpalPhysical dd ? ; Handle to the physical palette gcxScreenMax dd ? ; Width of the screen gcyScreenMax dd ? ; Height of the screen gcxZoomed dd ? ; Client width in zoomed pixels gcyZoomed dd ? ; Client height in zoomed pixels gfTracking dd 0 ; 1 if tracking is in progress gptZoom POINT 100,100 ; The center of the zoomed area ; COLOR_BTNFACE+1 is black not grey wc WNDCLASS csStyle,AppWndProc,0,0,NULL,NULL,NULL,NULL,NULL,szClassName msg MSG rc RECT ps PAINTSTRUCT hcurOld dd ? section '.idata' import data readable writeable library kernel,'KERNEL32.DLL',\ user,'USER32.DLL',\ gdi,'GDI32.DLL' import kernel,\ LocalAlloc,'LocalAlloc',\ LocalFree,'LocalFree',\ GetModuleHandle,'GetModuleHandleA',\ ExitProcess,'ExitProcess' import user,\ GetDC,'GetDC',\ ReleaseDC,'ReleaseDC',\ BeginPaint,'BeginPaint',\ EndPaint,'EndPaint',\ RegisterClass,'RegisterClassA',\ CreateWindowEx,'CreateWindowExA',\ GetSystemMetrics,'GetSystemMetrics',\ ClientToScreen,'ClientToScreen',\ ShowWindow,'ShowWindow',\ InflateRect,'InflateRect',\ SetRect,'SetRect',\ AdjustWindowRect,'AdjustWindowRect',\ DefWindowProc,'DefWindowProcA',\ SetWindowLong,'SetWindowLongA',\ RedrawWindow,'RedrawWindow',\ GetMessage,'GetMessageA',\ TranslateMessage,'TranslateMessage',\ DispatchMessage,'DispatchMessageA',\ SendMessage,'SendMessageA',\ LoadCursor,'LoadCursorA',\ LoadIcon,'LoadIconA',\ LoadMenu,'LoadMenuA',\ GetClientRect,'GetClientRect',\ MoveWindow,'MoveWindow',\ SetCapture,'SetCapture',\ ReleaseCapture,'ReleaseCapture',\ SetFocus,'SetFocus',\ GetKeyState,'GetKeyState',\ MessageBox,'MessageBoxA',\ SetScrollRange,'SetScrollRange',\ SetScrollPos,'SetScrollPos',\ DestroyWindow,'DestroyWindow',\ PostQuitMessage,'PostQuitMessage' import gdi,\ PatBlt,'PatBlt',\ GetStockObject,'GetStockObject',\ CreatePalette,'CreatePalette',\ SelectPalette,'SelectPalette',\ RealizePalette,'RealizePalette',\ SetStretchBltMode,'SetStretchBltMode',\ StretchBlt,'StretchBlt',\ CreateFont,'CreateFontA',\ DeleteObject,'DeleteObject' section '.rsrc' resource data readable ; resource directory directory RT_ICON,icons,\ RT_GROUP_ICON,group_icons ; resource subdirectories resource icons,\ 1,LANG_NEUTRAL,icon_data resource group_icons,\ 17,LANG_NEUTRAL,main_icon icon main_icon,icon_data,'ZOOMIN.ICO'
沒有留言:
張貼留言