第五章 質數 (2) 兼談子類化

前一章已經建立好具有三個副程式的 PRIMEDLL.DLL,這一章說明如何利用這三個副程式。首先利用 is_prime,撰寫質因數分解程式,PF.ASM。


質因數分解 ( prime factorization ):PF.ASM

原理

把一數分解成質因數乘積,是小學基本的數學能力,也是培養抽象思考的基本過程。因數分解是指把一個數寫成幾個因數相乘的形式,例如 10=2×5 或 36=2×3×6。如果這些因數都是質數,那麼就叫「質因數分解」,例如 36=22×33。質數無法分解成大於一的兩正整數之乘積,因此把一正整數做質因數分解,所得的答案為唯一的。底下小木偶就利用 is_prime 去撰寫 PF.ASM 程式,去做質因數分解,結果如下:

在說明 PF.ASM 程式之前,先說說它的限制。正整數有無限個,因此我們沒法子做出「能把任意正整數分解成質因數乘積」的程式來。而 64 位元電腦的暫存器,能表示最大的正整數是 264-1,所以小木偶合理的把 PF.ASM 限制在,能算出小於 264-1 的質因數乘積。在 PF.ASM 程式中,最關鍵的部份是計算出質因數的副程式,is_prime。它能判斷某個正整數是否為質數;如果不是質數的話,還能計算出這個正整數最小的質因數。利用此項特性,很容易就能夠把一個正整數分解成質因數乘積。具體的流程如下:
 ①先以使用者所輸入的正整數為參數,呼叫 is_prime,以判斷該正整數是否為質數,如果是的話,跳到⑤;如果不是的話,跳至步驟②。
 ②利用 is_prime 計算出的最小質因數。以此質因數去除使用者所給的正整數,並記錄能整除幾次,此能整除的次數就是此質因數的指數。質因數與指數都得紀錄於一陣列堙C
 ③等到不能整除時,就以最後能整除的商,代替原來使用者所給的正整數,以此商數為參數,再次呼叫 is_prime。
 ④重複②∼③步驟,直到所除的商為 1,跳到⑤。
 ⑤結束。

在整個過程中,質因數與其指數必須保存起來,小木偶定義了一個 216 個位元組的陣列保存它們。PF.ASM 能處理的最大正整數是 264,有 20 位數那麼大;而最小的 16 個質數 ( 2、3、5、7……53 ) 相乘所得之積,已經超過 264 了。也就是說,PA.ASM 能處理的最大的正整數,最多也只能是 16 個質因數相乘。換句話說,只要準備好能容納 16 筆資料就可以了;而每一筆都是由質因數跟其指數所構成的。但為了對齊 8 個位元組,因此準備了能容納 24 筆資料。質因數可能是 64 位元長,亦即八個位元組長;指數只需一個位元組就夠了 ( 一個位元組已能表示 255 次方 )。因此,一筆資料有九個位元組長,24 筆資料,就有 216 個位元組長。

底下以 14872=23×11×132 為例,執行完上面步驟之後,這 216 個位元組的前幾個位元組內容變成

00000001'40004050                        -02 00 00 00 00 00 00 00  .:..............
00000001'40004060 03 0b 00 00 00 00 00 00-00 01 0d 00 00 00 00 00  ................
00000001'40004070 00 00 02 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000001'40004080 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00

上面的資料,白色的部分是質因數,而紅色的部分為其指數。結尾的記號是連續 9 個 0。

子類化 ( subclass windows ) 原理及其實踐

Windows 系統內建了許多控制元件 ( control,簡稱控件 ),例如按鈕、編輯框、靜態控件……等,這些控件大幅簡化了程式設計師的工作,否則程式設計師就得花費心力,為這些控件特別設計專屬的程式。其實,這些控件都是一種子視窗,所以也有視窗的行為。我們常常會在設計程式時,遇到某個控件,符合我們所希望的大部分功能,但又缺少那麼一點兒,這時可以利用子類化 ( subclass ) 技術達到這個目的。

一般而言,使用者在單行的編輯框媬擗J資料,如果按下鍵盤上的「Enter」鍵時,系統會忽略這個按鍵。小木偶想要在 PF 程式堙A想要多增加底下的功能:當使用者在編輯框內輸完數字,按下鍵盤上的「Enter」鍵後,就相當於使用滑鼠左鍵點擊「計算」按鈕,這樣使用者就不必再移動滑鼠,就會變得方便許多。這時可利用子類化技術,達到這個目的。

事實上,子類化技術的原理並不困難。眾所周知,編輯框其實也是個視窗,也有自己的視窗函式 ( window procedure )。因此只要找到編輯框的視窗函式的位址,然後以新的編輯框視窗函式取代。在這新的視窗函式中,必須包含了處理「Enter」鍵訊息的程式,除此之外,其他的訊息,最好都交給原有的編輯框視窗函式處理。那麼,要如何取得編輯框的視窗函式的位址呢?在 Win32 API 埵陪 SetWindowLong,但微軟建議用新的 SetWindowLongPtr 取代它。

SetWindowLongPtr API

SetWindowLongPtr 的功用是設定視窗的某些屬性,這些屬性包含視窗的延伸風格、視窗函式等等資料,除設定視窗屬性外,它還會傳回原來的屬性,這些屬性其實都是數值。底下是 SetWindowLongPtr API 的原型:

LONG_PTR SetWindowLongPtr ( HWND     hWnd,
                            int      nIndex,
                            LONG_PTR dwNewLong
);

hWnd 是要設定屬性的視窗代碼,必須在呼叫 SetWindowLongPtr 前設定好。nIndex 是要設定的視窗屬性,可以是下面幾種;想要設定的新屬性,在呼叫前存入第三個參數,dwNewLong,堙C

nIndex數值 說  明
GWL_EXSTYLE-20設定視窗的延伸風格 ( extended window styles )
GWL_STYLE-16設定新的視窗風格
GWLP_WNDPROC-4設定新的視窗函式
GWLP_HINSTANCE-6設定新的模組代碼
GWLP_ID-12設定新的視窗識別碼
GWLP_USERDATA-21設定與視窗口關聯的使用者資料,此資料目的在提供建立該視窗的程式使用,其初始值為零
DWLP_MSGRESULT0設定對話盒函式內,已處理過訊息之返回值。hWnd 為對話盒代碼時,才能使用這個參數
DWLP_DLGPROC設定新的對話盒函式。hWnd 為對話盒代碼時,才能使用這個參數
DWLP_USER設定對話盒程式額外的資料。hWnd 為對話盒代碼時,才能使用這個參數

如果呼叫 SetWindowLongPtr 成功,傳回值是原來的屬性;如果呼叫失敗,返回值是 0。如果原來屬性值為 0,呼叫成功時傳回值仍為 0,但是不會清除錯誤碼。因此如果要確定呼叫失敗或成功,最好的方法是先呼叫 SetLastError 設定錯誤碼為 0,再呼叫 SetWindowLongPtr,如果呼叫錯誤,那麼傳回值為 0,且呼叫 GetLastError 得到的錯誤碼非零。

底下小木偶將著重在如何使用子類化技術,我想以新增處理按下「Enter」鍵的訊息為例,說明大致的過程。第一步,當然是設定新的編輯框視窗函式,當成功的呼叫 SetWindowLongPtr 後,就能設定新的編輯框視窗函式,並獲得原有的視窗函式位址,這件事應該在主程式的視窗函式堙A在處理 WM_CREATE 訊息時完成。呼叫 SetWindoiwLongPtr 時,因為第一個參數是編輯框的視窗代碼,第二個參數為 GWLP_WNDPROC,意思就是要設定 hEdit 的視窗函式,所以第三個參數為新的編輯框視窗函式位址。如果呼叫成功,返回值就是原來的編輯框視窗函式的位址,把它儲存起來,在新的編輯框視窗函式中會用到。像下面的例子:

;---------------------------------------------------------------------------------------------------
;新的編輯框視窗函式,主要處理Enter鍵,可讓使用者在編輯框按下Enter鍵,相當於以滑鼠點擊「計算」按鈕
new_edit_proc   PROC    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
.IF uMsg==WM_KEYDOWN
;處理使用者按下鍵盤上的鍵時,系統傳來的WM_KEYDOWN訊息
        .IF wParam==VK_RETURN
        ;處理使用者輸入Enter鍵,使R8D=識別碼,R8的高雙字組=通知碼,然後將WM_COMMAND訊息傳
        ;給hwnd的視窗函式
                mov     r8,BN_CLICKED
                shl     r8,32
                add     r8,IDB_CALC     
                INVOKE  SendMessage,hwnd,WM_COMMAND,r8,hBtnCalc
        .ELSE
        ;其他按鍵,則傳給原來的編輯框視窗函式處理
                INVOKE  CallWindowProc,pOldEditProc,hWnd,uMsg,wParam,lParam
                jmp     quit
        .ENDIF
.ELSE
;處理除了WM_KEYDOWN以外的訊息
                INVOKE  CallWindowProc,pOldEditProc,hWnd,uMsg,wParam,lParam
                jmp     quit
.ENDIF
                xor     rax,rax
quit:           ret
new_edit_proc   ENDP
;---------------------------------------------------------------------------------------------------
;主程式的視窗函式
WndProc         PROC    hWnd:QWORD,uMsg:UINT,wParam:QWORD,lParam:QWORD
.IF rdx==WM_CREATE
        INVOKE  SetWindowLongPtr,hEdit,GWLP_WNDPROC,OFFSET new_edit_proc
        mov     pOldEditProc,rax
        ......

第二步,要建立新的編輯框視窗函式。事實上,所有的視窗函式,包含編輯框視窗函式,所需參數都一樣,都是四個,那就是 hWnd、uMsg、wParam、lParam,其意義也相同。另外,此新的視窗函式不僅應包含原有函式的全部功能,同時也要能處理,當使用者按下「Enter」鍵,就相當於以滑鼠左鍵點擊按鈕的功能。因此最好的方式,就是除了處理有關按下「Enter」鍵的訊息外,其餘訊息都交由原來的視窗函式處理。而按下「Enter」鍵時,會使系統對編輯框發出 WM_KEYDOWN 訊息,因此上面的程式片段就以一個 .IF/.ELSE/.ENDIF 假指令判斷分支路線。除了 WM_KEYDOWN 訊息由 new_edit_proc 處理,其他訊息都交由原來的編輯框視窗函式處理。

但並非只有按下「Enter」鍵才會產生 WM_KEYDOWN 訊息,按下鍵盤上的任一鍵都會。因此處理 WM_KEYDOWN 訊息時,先檢查是否按下的是「Enter」鍵,如果是,對主視窗發送 WM_COMMAND 訊息,這是因為按下「按鈕」時,系統就會對擁有「按鈕」的視窗發送 WM_COMMAND;只是現在由編輯框的視窗函式,對主視窗發出 WM_COMMAND。依 Windows 系統的規定,已處理完成的訊息,返回值為 0,所以對主視窗發出 WM_COMMAND 後,返回 0。如果按下的不是「Enter」鍵,就交由原來的編輯框視窗函式處理。

由以上說明,可知道除了因為按下「Enter」鍵所產生的 WM_KEYDOWN 訊息之外,其餘按鍵產生的 WM_KEYDOWN,乃至於其他訊息,都交給編輯框原有的視窗函式處理。如何把這些訊息交由編輯框原有的視窗函式呢?那就得呼叫 CallWindowProc。當系統完成處理這些訊息之後,所產生的返回值,也應原封不動的返回系統中,因此呼叫 CallWindowProc 後,不改變返回值,改變 CallWindowProc 的返回值。

CallWindowProc API

顧名思義,CallWindowProc 是用來呼叫某個視窗函式的,其原型是:

LRESULT CallWindowProc ( WNDPROC lpPrevWndFunc,
                         HWND    hWnd,
                         UINT    Msg,
                         WPARAM  wParam,
                         LPARAM  lParam
);

這個 API 就是用來把四個參數傳給某個,而該視窗函式的位址就是第一個參數,lpPrevWndFunc;後面四個參數,就是視窗函式所需的四個參數。返回值則視所處理的訊息不同而改變。


原始碼

底下是 PF.ASM 的原始碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
;PF能讓使用者輸入一個小於18446744073709551616(=2^64)正整數,然後把該正整數分解為質因數之乘積
;此程式呼叫is_prime求出質因數。
        OPTION  CASEMAP:NONE
        OPTION  WIN64:7

UNICODE         EQU     1       ;使用萬國碼
IDB_EXIT        EQU     32100
IDB_CALC        EQU     32101
IDE_NUMBER      EQU     32102

INCLUDE         WINDOWS.INC
INCLUDELIB      GDI32.LIB
INCLUDELIB      KERNEL32.LIB
INCLUDELIB      USER32.LIB
INCLUDELIB      PRIMEDLL.LIB    ;is_prime副程式在PRIMEDLL程式庫內

is_prime        PROTO   :QWORD
;***************************************************************************************************
.CONST
rcHint          RECT    <5,5,328,36>
rcPrmFtrArea    RECT    <5,38,330,90>
AppName         DW      8ceah,56e0h,6578h,5206h,89e3h,0                 ;分解為質因數乘積
ClassName       DW      50h,72h,69h,6dh,65h,46h,61h,63h,74h,6fh,72h,69h ;PrimeFactori
                DW      7ah,61h,74h,69h,6fh,6eh,0h                      ;zation
IconName        DW      50h,72h,69h,6dh,65h,46h,61h,63h,74h,6fh,72h,0h  ;PrimeFactor
szFontFace      DW      54h,69h,6dh,65h,73h,20h,4eh,65h,77h,20h,52h,6fh ;Times New Ro
                DW      6dh,61h,6eh,0                                   ;man
EditClass       DW      65h,64h,69h,74h,0h                              ;edit=編輯框視窗類別
ButtonClass     DW      62h,75h,74h,74h,6fh,6eh,0h                      ;button=按鈕視窗類別
szBtnCalc       DW      8a08h,7b97h,0h                                  ;計算
szBtnExit       DW      96e2h,958bh,0h                                  ;離開
szPrmFmt        DW      25h,49h,36h,34h,75h,662fh,8ceah,6578h,3002h,0h  ;%I64u是質數。
szNumberFmt     DW      25h,49h,36h,34h,75h,3dh,0h                      ;%I64u=
szPrmFtrFmt0    DW      25h,49h,36h,34h,75h,0h                          ;%I64u
szPrmFtrFmt1    DW      0d7h,25h,49h,36h,34h,75h,0h                     ;×%I64u
szZeroOne       DW      6b64h,6578h,662fh,8981h,600eh,9ebch,5206h,89e3h,0ff1fh,0h   ;此數是要怎麼分解?
szHint          DW      8f38h,5165h,4e00h,500bh,6b63h,6574h,6578h,0ff0ch,50h,72h    ;輸入一個正整數,Pr
                DW      69h,6dh,65h,46h,61h,63h,74h,6fh,72h,6703h,628ah,6b64h,6578h ;imeFactor會把此數
                DW      8868h,793ah,6210h,8ceah,56e0h,6578h,4e4bh,4e58h,7a4dh,3002h ;表示成質因數之乘積。
                DW      8acbh,8f38h,5165h,4e00h,6b63h,6574h,6578h,0ff1ah,0h         ;請輸入一正整數:
szOver2p64      DW      60a8h,8f38h,5165h,7684h,6578h,4e0dh,53efh,8d85h,904eh,31h   ;您輸入的數不可超過1
                DW      38h,34h,34h,36h,37h,34h,34h,30h,37h,33h,37h,30h,39h,35h,35h ;844674407370955
                DW      31h,36h,31h,35h,0ff01h,0h                                   ;1615!
;***************************************************************************************************
.DATA
hInstance       HINSTANCE       ?       ;模組代碼
hwnd            HWND            ?       ;主視窗代碼
hEdit           HANDLE          ?       ;編輯框代碼
hBtnCalc        HANDLE          ?       ;「計算」按鈕代碼
hBtnExit        HANDLE          ?       ;「離開」按鈕代碼
hFont           HFONT           ?       ;編輯框、工作區字形代碼
hBaseFont       HFONT           ?       ;質因數的字形代碼
hExpFont        HFONT           ?       ;質因數的指數之字形代碼
pOldEditProc    QWORD           ?       ;原來編輯框的視窗函式
;將要印出質因數乘積時,what_to_draw=1;使用者數入超過2^64時,what_to_draw=2;否則what_to_draw=0
what_to_draw    DQ              0
number          DQ              ?       ;使用者輸入的數值
;以9個位元組為一單位,每一單位的前8個位元組為質因數,第9個位元組是該質因數的冪方
aryPrmFtr       DB              40*9 DUP (0)
msg             MSG             <>
;***************************************************************************************************
.CODE
;---------------------------------------------------------------------------------------------------
;prime_factorization能將num分解為質因數乘積。所用原理為呼叫is_prime,is_prime能判斷某正整數是否為質
;數,如果是質數,返回值為1;如果不是質數,返回值為該正整數的最小因數(1除外)。因此只要依序求得最小質
;因數,再依次以此質因數除以該正整數,再將所得之商替換該正整數,直到商為1,就完成了。
;輸入:num-要分解為質因數乘積的正整數
;   pPrmFtr-儲存質因數乘積的位址,共有360個位元組。每九個位元組為一組,前面四字組為質因數,接著
;       的一個位元組為其指數。結尾記號是連續九個位元組均為0
;輸出:RAX-RAX=0,num為0或1;RAX=1,num為質數;RAX=2,num可分解成質因數乘積
prime_factorization     PROC    USES rdi num:QWORD,pPrmFtr:QWORD
                LOCAL   input:QWORD     ;一開始為num,然後除以質因數後,所得的商,存回input,循
                mov     input,rcx       ;環除以質因數,直到商為1為止
        ;把pPrmFtr所指位址的360個位元組,均設為0
                mov     rdi,rdx
                xor     rax,rax
                mov     rcx,45
                rep     stosq
                mov     rdi,rdx
        ;檢查num是否為質數,若RAX=0,表示num為0或1;RAX=1,表示num為質數;RAX=其他數,
        ;表示num不是質數,且RAX為其最小之質因數
get_nxt_prm:    INVOKE  is_prime,input
                or      rax,rax         ;如果RAX=0,表示num為0或1,不是質數
                jz      quit
                cmp     rax,1           ;如果RAX=1,表示input為質數
                jne     num_isnt_prm
                mov     r9,num          ;如果input為質數,再比較input與num是否相等
                cmp     r9,input        ;若input=num,表示input未經質因數除
                je      num_is_prm      ;過,亦即num為質數;否則num為合數
        ;主程式所給的num並非質數,但因為num除以所有不是最大的質因數後,此時num為最大的質因數
                mov     rax,input
num_isnt_prm:   mov     r8,rax          ;如果num不是質數,除以其因數,並計算可以整除幾次
                stosq
                mov     rax,input
                xor     rdx,rdx
do_again:       mov     r9,rax          ;先把被除數存在R9堙A如無法整除,可由R9存回原被除數
                div     r8
                or      rdx,rdx
                jnz     nxt_prm
                inc     BYTE PTR [rdi]
                jmp     do_again
nxt_prm:        inc     rdi             ;如無法整除,先使RDI指向下一筆資料,再比較被除數是否已經
                cmp     r9,1            ;是1了,如果是則使RAX=2並離開程式;如果不是1,表示尚未除
                je      finish          ;完,使不能整除的原被除數存回input,再搜尋下一個質因數
                mov     input,r9
                jmp     get_nxt_prm
finish:         mov     rax,2
                jmp     quit
num_is_prm:     inc     BYTE PTR [rdi+8];使第零筆資料的質因數為num,指數為1。其實這就代表num只有
                mov     [rdi],r9        ;一個質因數,亦即num為質數
quit:           ret
prime_factorization     ENDP
;---------------------------------------------------------------------------------------------------
draw_prm_ftr    PROC    hDC:HDC
                LOCAL   pPrmFtrPrdt:LPSTR
                LOCAL   cText:DWORD     ;要印出的字元數
                LOCAL   ptEqu:DWORD     ;等號最右邊的點所在X座標
                LOCAL   flag:QWORD
                LOCAL   rcBase:RECT,rcExp:RECT
                LOCAL   buffer[40]:BYTE
                INVOKE  SelectObject,hDC,hBaseFont
                mov     rcBase.left,5
                mov     rcBase.top,44
                mov     rcExp.top,39
                mov     flag,0
        ;若使用者輸入1或0,則直接印出「此數怎麼分解?」,然後返回
                cmp     number,1
                ja      over_one
                mov     rcBase.right,280
                mov     rcBase.bottom,66
                INVOKE  DrawText,hDC,OFFSET szZeroOne,9,ADDR rcBase,DT_SINGLELINE
                jmp     display_ok
        ;檢查使用者輸入的數,是否為質數,如果是質數,只有2個因數,自己和1
over_one:       lea     r10,aryPrmFtr
                mov     pPrmFtrPrdt,r10
        .IF (QWORD PTR [r10+9]==0)&&(BYTE PTR [r10+8]==1)
                mov     rdx,OFFSET szPrmFmt     ;使用者輸入質數
        .ELSE
                mov     rdx,OFFSET szNumberFmt  ;使用者輸入合數,印出使用者輸入的數值
        .ENDIF
                INVOKE  wsprintf,ADDR buffer,rdx,number
                mov     cText,eax
                INVOKE  DrawText,hDC,ADDR buffer,cText,ADDR rcBase,DT_SINGLELINE or DT_CALCRECT
                INVOKE  DrawText,hDC,ADDR buffer,cText,ADDR rcBase,DT_SINGLELINE
                mov     r8d,rcBase.right
                inc     r8d
                mov     r10,pPrmFtrPrdt         ;因為呼叫wsprintf、DrawText,所以R10會被破壞
                mov     ptEqu,r8d
                cmp     QWORD PTR [r10+9],0     ;檢查使用者輸入的數,是否為質數
                jnz     next_factor
                cmp     BYTE PTR [r10+8],1      ;若為質數,跳躍至display_ok
                jz      display_ok
next_factor:    mov     r10,pPrmFtrPrdt ;決定底數在螢幕上的開始位置,rcBase.left
        .IF (flag==0)||(flag==2)        ;flag=0,表示第一個質因數,因此以前一次rcBase.right為基準
                mov     r9d,rcBase.right;flag=2,前一個質因數的指數為1,以前一次rcBase.right為基準
        .ELSEIF flag==1
                mov     r9d,rcExp.right ;flag=1,前一個質因數的指數>1,以前一次rcExp.right為基準
        .ENDIF
                inc     r9d
                mov     r8,QWORD PTR [r10]
                or      r8,r8
                jz      display_ok
                mov     rcBase.left,r9d
        .IF flag==0
                mov     rdx,OFFSET szPrmFtrFmt0 ;flag=0,表示第一個質因數,不須顯示「×」
                mov     flag,1
        .ELSEIF flag==1                         ;flag=1,表示前一個質因數的指數>1,且非最小質因數
                mov     rdx,OFFSET szPrmFtrFmt1 ;,要顯示「×」
        .ELSE
                mov     rdx,OFFSET szPrmFtrFmt1 ;flag=2,前一個質因數的指數為1,要顯示「×」
                mov     flag,1
        .ENDIF
                INVOKE  wsprintf,ADDR buffer,rdx,r8
                mov     cText,eax
                INVOKE  SelectObject,hDC,hBaseFont
                INVOKE  DrawText,hDC,ADDR buffer,cText,ADDR rcBase,DT_SINGLELINE or DT_CALCRECT
        .IF rcBase.right>330
                mov     r8d,ptEqu
                mov     rcExp.top,55
                mov     rcBase.left,r8d
                mov     rcBase.top,60
                INVOKE  DrawText,hDC,ADDR buffer,cText,ADDR rcBase,DT_SINGLELINE or DT_CALCRECT
        .ENDIF
                INVOKE  DrawText,hDC,ADDR buffer,cText,ADDR rcBase,DT_SINGLELINE
                add     pPrmFtrPrdt,8
                mov     r11d,rcBase.right
                mov     r10,pPrmFtrPrdt
                inc     r11d
                movzx   r8,BYTE PTR [r10]
                cmp     r8,1
                jne     exp_is_not_1
                mov     flag,2                  ;指數為1,不顯示指數
                jmp     no_draw_exp
exp_is_not_1:   mov     rcExp.left,r11d
                INVOKE  wsprintf,ADDR buffer,OFFSET szPrmFtrFmt0,r8
                mov     cText,eax
                INVOKE  SelectObject,hDC,hExpFont
                INVOKE  DrawText,hDC,ADDR buffer,cText,ADDR rcExp,DT_SINGLELINE or DT_CALCRECT
                INVOKE  DrawText,hDC,ADDR buffer,cText,ADDR rcExp,DT_SINGLELINE
no_draw_exp:    inc     pPrmFtrPrdt
                jmp     next_factor
display_ok:     ret
draw_prm_ftr    ENDP
;---------------------------------------------------------------------------------------------------
;由RCX所指的位址,算出十六進位數值,存放於RAX堛藀^(若超過18446744073709551616=2^64,返回時R8為1;
;否則R8為0)。小於2^64的最大質數是18446744073709551557
GetInt64        PROC
                LOCAL   buffer[48]:WORD
                INVOKE  GetWindowText,hEdit,ADDR buffer,44
                lea     rcx,buffer
                xor     rax,rax
                xor     rdx,rdx
                mov     r9,10
next_digit:     movzx   r8,WORD PTR [rcx]
                or      r8,r8
                jz      finish
                sub     r8,"0"
                mul     r9
                jc      set_r8_one
                add     rcx,2
                add     rax,r8
                jnc     next_digit
set_r8_one:     mov     r8,1
finish:         ret
GetInt64        ENDP
;---------------------------------------------------------------------------------------------------
;新的編輯框視窗函式,主要處理Enter鍵,可讓使用者在編輯框按下Enter鍵,相當於以滑鼠點擊「計算」按鈕
new_edit_proc   PROC    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
.IF uMsg==WM_KEYDOWN
;處理使用者按下鍵盤上任一鍵時,系統發出的WM_KEYDOWN訊息
        .IF wParam==VK_RETURN
        ;處理使用者輸入Enter鍵,使R8D=識別碼,R8的高雙字組=通知碼,然後將WM_COMMAND訊息
        ;傳給hwnd的視窗函式
                mov     r8,BN_CLICKED
                shl     r8,32
                add     r8,IDB_CALC    
                INVOKE  SendMessage,hwnd,WM_COMMAND,r8,hBtnCalc
                xor     rax,rax
        .ELSE
        ;其他按鍵,則傳給原來的編輯框視窗函式處理
                INVOKE  CallWindowProc,pOldEditProc,hWnd,uMsg,wParam,lParam
        .ENDIF
.ELSE
;除了WM_KEYDOWN訊息外,其他訊息由原來的編輯框視窗函式處理
                INVOKE  CallWindowProc,pOldEditProc,hWnd,uMsg,wParam,lParam
.ENDIF
                ret
new_edit_proc   ENDP
;---------------------------------------------------------------------------------------------------
WndProc         PROC    hWnd:QWORD,uMsg:UINT,wParam:QWORD,lParam:QWORD
                LOCAL   ps:PAINTSTRUCT
.IF rdx==WM_PAINT
                INVOKE  BeginPaint,hWnd,ADDR ps
                INVOKE  SelectObject,ps.hdc,hFont
                INVOKE  DeleteObject,rax
                INVOKE  DrawText,ps.hdc,OFFSET szHint,41,OFFSET rcHint,DT_WORDBREAK
        .IF what_to_draw==1
                INVOKE  draw_prm_ftr,ps.hdc
        .ELSEIF what_to_draw==2
                INVOKE  DrawText,ps.hdc,OFFSET szOver2p64,31,OFFSET rcPrmFtrArea,DT_WORDBREAK
        .ENDIF
                INVOKE  EndPaint,hWnd,ADDR ps

.ELSEIF rdx==WM_COMMAND
                mov     r10,r8
                mov     r11,r8
                shr     r10,20h         ;R10D=通知碼
                and     r11,0ffffffffh  ;R11D=控制元件識別碼
  .IF r10d==BN_CLICKED
      .IF r11d==IDB_EXIT
                jmp     exit
      .ELSEIF r11d==IDB_CALC
                call    GetInt64
         .IF r8==1
                mov     what_to_draw,2
                jmp     paint
         .ELSE
                mov     number,rax
                INVOKE  prime_factorization,number,ADDR aryPrmFtr
                mov     what_to_draw,1
paint:          INVOKE  InvalidateRect,hWnd,OFFSET rcPrmFtrArea,TRUE
         .ENDIF
      .ENDIF
  .ENDIF

.ELSEIF rdx==WM_CREATE
        ;建立三種字形﹔hFont(工作區及控件的字形)、hBaseFont(底數字形)、hExpFont(指數字形)
                INVOKE  CreateFont,15,0,0,0,100,0,0,0,ANSI_CHARSET,0,0,0,0,OFFSET szFontFace
                mov     hFont,rax
                INVOKE  CreateFont,18,0,0,0,100,0,0,0,ANSI_CHARSET,0,0,0,0,OFFSET szFontFace
                mov     hBaseFont,rax
                INVOKE  CreateFont,13,0,0,0,100,0,0,0,ANSI_CHARSET,0,0,0,0,OFFSET szFontFace
                mov     hExpFont,rax
        ;建立編輯框、計算按鈕、離開按鈕
                INVOKE  CreateWindowEx,0,OFFSET EditClass,0,WS_CHILD or WS_VISIBLE or WS_BORDER\
                        or ES_LEFT or ES_NUMBER,105,19,150,20,hWnd,IDE_NUMBER,hInstance,0
                mov     hEdit,rax
                INVOKE  CreateWindowEx,0,OFFSET ButtonClass,OFFSET szBtnCalc,BS_DEFPUSHBUTTON or\
                        WS_CHILD or WS_VISIBLE,145,84,88,28,hWnd,IDB_CALC,hInstance,NULL
                mov     hBtnCalc,rax
                INVOKE  CreateWindowEx,0,OFFSET ButtonClass,OFFSET szBtnExit,BS_PUSHBUTTON or\
                        WS_CHILD or WS_VISIBLE,239,84,88,28,hWnd,IDB_EXIT,hInstance,NULL
                mov     hBtnExit,rax
                INVOKE  SetFocus,hEdit
        ;設定按鈕及編輯框的文字字型
                INVOKE  SendMessage,hBtnCalc,WM_SETFONT,hFont,TRUE
                INVOKE  SendMessage,hBtnExit,WM_SETFONT,hFont,TRUE
                INVOKE  SendMessage,hEdit,WM_SETFONT,hFont,TRUE
        ;子類化編輯框,讓使用者在輸入完正整數後,按Enter鍵,就相當於以滑鼠點擊「計算」按鈕
                INVOKE  SetWindowLongPtr,hEdit,GWLP_WNDPROC,OFFSET new_edit_proc
                mov     pOldEditProc,rax

.ELSEIF uMsg==WM_CLOSE
exit:           INVOKE  DestroyWindow,hWnd

.ELSEIF uMsg==WM_DESTROY
                INVOKE  PostQuitMessage,NULL

.ELSE
                INVOKE  DefWindowProc,rcx,edx,r8,r9
                ret
.ENDIF
                xor     rax,rax
                ret
WndProc         ENDP
;---------------------------------------------------------------------------------------------------
main            PROC
                LOCAL   wc:WNDCLASSEX
                INVOKE  GetModuleHandle,NULL
                mov     hInstance,rax
                mov     rdx,OFFSET ClassName
                mov     rcx,OFFSET WndProc
                mov     wc.cbSize,SIZEOF WNDCLASSEX
                mov     wc.style,CS_HREDRAW or CS_VREDRAW
                mov     wc.lpfnWndProc,rcx
                mov     wc.cbClsExtra,NULL
                mov     wc.cbWndExtra,NULL
                mov     wc.hInstance,rax
                mov     wc.lpszClassName,rdx
                INVOKE  GetStockObject,COLOR_BTNFACE
                mov     wc.hbrBackground,rax
                mov     wc.lpszMenuName,NULL
                INVOKE  LoadIcon,hInstance,OFFSET IconName
                mov     wc.hIcon,rax
                mov     wc.hIconSm,rax
                INVOKE  LoadCursor,NULL,IDC_ARROW
                mov     wc.hCursor,rax
                INVOKE  RegisterClassEx,ADDR wc
                INVOKE  CreateWindowEx,0,OFFSET ClassName,OFFSET AppName,WS_OVERLAPPEDWINDOW\
                        ,650,330,350,156,0,0,hInstance,0
                mov     hwnd,rax
                INVOKE  ShowWindow,hwnd,SW_SHOWNORMAL
                INVOKE  UpdateWindow,hwnd

.WHILE TRUE
                INVOKE  GetMessage,ADDR msg,0,0,0
.BREAK .IF !rax
                INVOKE  TranslateMessage,ADDR msg
                INVOKE  DispatchMessage,ADDR msg
.ENDW        
                mov     rax,msg.wParam
                INVOKE  ExitProcess,eax
main            ENDP
;***************************************************************************************************
END             main

底下是 PF.RC 的程式碼:

1
2
3
4
5
#include "RESOURCE.H"

1       RT_MANIFEST MOVEABLE PURE "PF.EXE.MANIFEST"

PrimeFactor     ICON    PrimeFactor.ic

底下是 PF.EXE.MANIFEST 的內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
<dependency>
   <dependentAssembly>
      <assemblyIdentity type='win32'
                        name='Microsoft.Windows.Common-Controls'
                        version='6.0.0.0'
                        processorArchitecture='*'
                        publicKeyToken='6595b64144ccf1df'
                        language='*'
      />
   </dependentAssembly>
</dependency>
</assembly>

請把這三個檔案連同 PrimeFactor.ico 圖示檔放在同一個子目錄,然後開啟「命令提示字元」,依照下面的方式組譯及連結:

E:\HomePage\SOURCE\Win64\PRIME>uasm64 -win64 pf.asm [Enter]
UASM v2.46, Jan  8 2018, Masm-compatible assembler.
Portions Copyright (c) 1992-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.

PF.ASM: 365 lines, 4 passes, 184 ms, 0 warnings, 0 errors

E:\HomePage\SOURCE\Win64\PRIME>rc pf.rc [Enter]
Microsoft (R) Windows (R) Resource Compiler Version 6.0.5724.0
Copyright (C) Microsoft Corporation.  All rights reserved.


E:\HomePage\SOURCE\Win64\PRIME>link pf.obj pf.res [Enter]
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/SUBSYSTEM:WINDOWS /DEBUG

E:\HomePage\SOURCE\Win64\PRIME>

回上一章回到首頁到下一章