Post view

組合語言新手上路篇:打造文字窗(2)

組合語言新手上路篇:打造文字窗(2)


作者:蘇言霖

變換組合語言檔位

上次介紹的WINDOW.ASM只能畫一個視窗,如果要畫多個視窗,只要把繪製程式重複寫幾次即可。不過,這裡有更優雅的做法,那就是利用呼叫「副程式」的技巧。


副程式設計之一:使用標記


我們把WINDOW.ASM做了一點更動,首先是在main程式前面加了一小段show副程式。
副程式的開頭用一個簡單的「show標記」來表示「這是副程式的進入點」,以及「這是副程式的名稱的。最後則以一個ret指令來回到呼叫show的主程式。


        .code
show:            ← show副程式的進入點
        mov     ah,09H
        int     21H
        ret        ← show副程式的返回點

主程式只要把dx指向win1視窗變數,再以call show這樣的指令呼叫show副程式即可。call會記住返回位置,待副程式執行完畢,ret會回到call的底下繼續執行程式。請看圖一:
圖一:

ASM2-1.png

詳細的WINS1.ASM列表請看程式一,粗體字是表示與WINDOW.ASM不同的地方。
程式一 :


;  File Name : wins1.asm

        .286
        .model  small
        .stack        ;堆疊區
        .data         ;資料區
win1    db      "-----------------",10,13
        db      "|               |",10,13
        db      "|               |",10,13
        db      "|               |",10,13
        db      "-----------------",10,13,"$"

        .code        ;程式區
show:            ;顯示視窗副程式
        mov     ah,09H
        int     21H
        ret        ;回到主程式

        .code        ;程式區
main:            ;WINS1主程式
        mov     dx,@data
        mov     ds,dx        ;把ds指向資料區

        lea     dx,win1      ;把dx指向win1
        call    show         ;呼叫show畫出視窗

        mov     ax,4c00H
        int     21H          ;結束程式回到DOS
        end     main

程式的編譯方式很簡單,您可以用標準的指令:


C:\>masm wins1; <Enter>
C:\>link wins1; <Enter>

也可以利用上次介紹的ASM.BAT來編譯:


C:\>asm wins1 <Enter>

WINS1的執行結果與WINDOW完全相同,這裡就不重複介紹了。


要特別說明的是,其實ret之後的 .CODE宣告是可有可無的。small模式只有一個程式區,即使多加幾個 .CODE,主、副程式還是會放在一起。這裡只是習慣的把副程式區用 .CODE做個區隔。


因此,如果忘了寫副程式的ret返回指令,會變成int 21H之後,緊接著主程式的啟始指令mov dx,@data,再也沒有機會回到原呼叫的位置。您可以想像會發生什麼事:電腦不斷繪製視窗,最後就當機了!


用proc宣告副程式,也有同樣的問題,那只是「宣告」方式不同罷了,可不會自動加上ret。


副程式設計之二:使用proc假指令


用「標記」宣告副程式其實並不正確,比較好的做法是利用proc與endp假指令來宣告副程式。理由之一是proc(procdure)支援高階語言機制,如BASIC、PASCAL、C等。proc的寫法如下:


show    proc
         :
        ret
show    endp

我們告訴MASM,show副程式從proc開始到endp為止。很簡單吧!但不要忘記寫ret或endp,否則從proc開始到程式最後,統統是show副程式的勢力範圍。
只畫一個視窗不過癮吧?WINS2.ASM試著繪製兩個視窗,請看程式二。執行結果與執行過程如圖二和圖三所示。


程式二:
;  File Name : wins2.asm

        .286
        .model  small
        .stack
        .data
win1    db      "-----------------",10,13
        db      "|               |",10,13
        db      "|               |",10,13
        db      "|               |",10,13
        db      "-----------------",10,13,"$"

win2    db      "-----------------------",10,13
        db      "|                     |",10,13
        db      "|                     |",10,13
        db      "|                     |",10,13
        db      "|                     |",10,13
        db      "|                     |",10,13
        db      "-----------------------",10,13,"$"

        .code
show    proc        ;宣告show副程式
        mov     ah,09H
        int     21H
        ret         ;別忘了ret
show    endp        ;結束show副程式

        .code
main:
        mov     bx,@data
        mov     ds,bx

        lea     dx,win1
        call    show        ;呼叫show副程式
        lea     dx,win2
        call    show        ;呼叫show副程式

        mov     ax,4c00H
        int     21H
        end     main


圖二:

ASM2-2.png
圖三:

ASM2-3.pngASM2-4.png


                       lea     dx,win1
                       call    show

                       lea     dx,win2
                       call    show


製作新視窗

Show 只能繪製簡單而固定的視窗,如果希望能畫出隨意大小的窗子,show就做不到了!問題是出在資料結構。之前設計的視窗,把視窗結構直接寫在程式裡頭,因而 缺乏彈性。現在,我們希望藉由更換WINS2.ASM的「資料結構」與「show副程式」兩個組件,做出更具彈性的窗子。


譬如,我們設計了這樣的資料結構,來表示一個視窗能有多大:


windows  db  Width,Length

Width表示視窗的寬度,Length則表示視窗的長度。很簡單吧!新的ShowWindow副程式並不難,只是略長了些。請看程式三。提醒您,WIN3.ASM的main主程式與WIN2.ASM完全一樣喲!


繪框副程式配件


我們需要一個新的列印字元服務,查「技術手冊」找到了一個相當好用的DOS服務:


DOS 服務代號 02H

功能:列印字元。

說明:DOS 會把存在 DL 的字元以「黑底白字」
      的方式印到標準螢幕(STDOUT)。

用法:
            .data
      char  db    'R‘

            .code
      mov   dl,char  ; dl = char
      mov   ah,02H   ; ah = 2
      int   21H      ; 呼叫 DOS 的第 2 號功能

扣掉將字元存入dl暫存器的mov指令,我們把這個列印服務寫在printch副程式。另外,還設計了一個「換列」專用的newline副程式,以簡化ShowWindow程式。

建構文字視窗

與其研究ShowWindow每個指令的設計流程,不如把程式劃分為幾個段落,您一定會發現ShowWindow原來是如此簡單啊!
以單線視窗為例,我們把整個窗子劃為圖四幾個部份:

圖四:

ASM2-4a.png

第一個數字表示繪製順序,第二個數字表示該框線的字元碼(以10進制表示)。我們從視窗左上角開始畫到右下角為止。以開始的1,128來說,其實只要寫:


        mov     dl,218
        call    printch

真的很簡單吧!緊接著就是2,196的繪製程式:


        mov     cx,0     ;把 cx 歸零
        mov     cl,[bx]  ;從 [bx] 所指的變數,取出視窗寬度
        sub     cx,2     ;將視窗寬度減 2
line1:
        mov     dl,196
        call    printch
        loop    line1    ;重複畫出一條橫線

3,191就如同1,218一般簡單,因為游標正好印到視窗的右上角,我們只要印191字元並換列再印即可:
 

        mov     dl,191
        call    printch
        call    newline  ;換列

第4步的4,179程式比較冗長,因為我們想同時印出視窗左右兩邊的框線:


        mov     cl,[bx+1]  ;取出視窗長度到 cl
        sub     cx,2       ;把 cx 減 2
        mov     di,cx      ;di=cx,用 di 做為計數器
line2:
        mov     dl,179
        call    printch    ;印左邊的垂直線
        mov     cl,[bx]    ;cl=視窗寬度
        sub     cx,2       ;將 cx 減 2
space:
        mov     dl,32
        call    printch
        loop    space      ;這個迴圈用來印視窗內的空白字元
        mov     dl,179
        call    printch    ;印右邊的垂直線
        call    newline    ;換列
        dec     di         ;將視窗長度減一
        jnz     line2      ;若 di 不為 0 則跳回到 line2

5、6、7步驟的繪製方式與1、2、3幾乎完全相同,這裡就不再重複說明了。


程式三:
;  File Name : wins3.asm

        .286
        .model  small
        .stack
        .data
win1    db      10,3        ;寬度=10,長度=20
win2    db      20,6        ;寬度=20,長度=6

        .code
printch proc        ;列印字元副程式
        push    ax
        mov     ah,02H
        int     21H
        pop     ax
        ret
printch endp

newline proc        ;換行副程式
        push    dx
        mov     dl,13
        call    printch    ;將游標移到左端
        mov     dl,10
        call    printch    ;游標往下移動一列
        pop     dx
        ret
newline endp

ShowWindow    proc
        mov     bx,dx        ;bx = dx
        mov     dl,218    ;視窗的左上角字元
        call    printch
        mov     cx,0
        mov     cl,[bx]         ;cl = width
        sub     cx,2            ;cx = cx - 2
line1:
        mov     dl,196    ;印出視窗的水平框線
        call    printch
        loop    line1
        mov     dl,191    ;視窗的右上角字元
        call    printch
        call    newline    ;換列
        mov     cl,[bx+1]       ;cl = length
        sub     cx,2            ;cx = cx - 2
        mov     di,cx
line2:
        mov     dl,179    ;印出視窗的垂直框線
        call    printch
        mov     cl,[bx]         ;cl = width
        sub     cx,2            ;cx = cx - 2
space:
        mov     dl,32        ;印空白字元
        call    printch
        loop    space
        mov     dl,179    ;印右邊的垂直框線
        call    printch
        call    newline
        dec     di        ;長度減一
        jnz     line2
        mov     dl,192    ;視窗的左下角字元
        call    printch
        mov     cl,[bx]         ;cl = width
        sub     cx,2            ;cx = cx - 2
line3:
        mov     dl,196    ;印底部的水平框線
        call    printch
        loop    line3
        mov     dl,217    ;視窗的右下角字元
        call    printch
        call    newline
        ret
ShowWindow    endp

        .code
main:
        mov     cx,@data
        mov     ds,cx

        lea     dx,win1
        call    ShowWindow
        lea     dx,win2
        call    ShowWindow

        mov     ax,4c00H
        int     21H
        end     main

打造新的文字窗

相信讀者看到這裡都皺著眉,WINS3.ASM肥胖的身軀著實沉重,沒關係,只要把WINS3.ASM送到「美容瘦身中心」,曲線窈窕非夢事喲!
請跟著我們的美容程序做一次,來!One More、Two More...


一、把WINS3.ASM複製為WINS4.ASM。


二、把WINS4.ASM整段 .CODE副程式,用文字編輯器的區段功能mark起來,另存新檔到WINSLIB.ASM,並刪掉整個副程式區段。


三、以相同的手法,把WINS4.ASM從 .286到 .STACK部份mark起來並另存新檔到WINSLIB.INC。


四、WINS4.ASM現在只剩下 .data與main程式了,請在程式前加上include winslib.inc含括指令,如程式四所示。粗體字表示新增的指令。為什麼要這樣做,稍後會解釋。


五、由於main程式叫用的副程式已不在WINS4.ASM了,所以得在WINSLIB.INC含括檔追加「外部」extrn宣告指令,如程式五。


六、最後請在WINSLIB.ASM加上幾個宣告性的假指令,特別是「公用」宣告指令public(與extrn相對),把ShowWindow等副程式宣告為開放供大家使用。不要忘了程式最後要加上END指令,如程式六所示。


七、不難吧!這套程式的編譯與連結方式如下:


C:\>masm wins4; <Enter>
C:\>masm winslib; <Enter>
c:\>link wins4+winslib; <Enter>

八、如此即可得到一個全新的WINS4.EXE!由於美容期間未加上任何CPU指令(只追加假指令),因此WINS4.EXE的大小應該與WINS4.EXE相等才是。比對WINS3.ASM與WINS4.ASM程式,WINS4簡潔多了!您喜歡那一種設計呢?


我們把這套美容課程稱為「模組化程式設計基礎」。通常能完成的學員比例小於30:5!不會吧?有這麼難嗎?如果做不出來,乾脆參考程式四、五、六的內容,重新輸入一次算了!


探討問題的原因有二:一是學員對文字編輯器的區段操作指令不熟,不會把區段另寫成新檔。另一個原因是,很多學校不教「程式模組化」,以致學生學會一堆組合語言指令,卻不會劃分程式!幾個假指令就弄得一頭霧水!


您注意到了嗎,其實整個模組化程序,只用了三個新的include、extrn、public假指令而已!


程式四(主程式):
;  File Name : wins4.asm

include winslib.inc

        .data
win1    db      10,3
win2    db      20,6

        .code
main:
        mov     bx,@data
        mov     ds,bx

        lea     dx,win1
        call    ShowWindow
        lea     dx,win2
        call    ShowWindow

        mov     ax,4c00H
        int     21H
        end     main

程式五(含括檔):
;  File Name : winslib.inc

        .286
        .model  small
        .stack

        .code
extrn   printch:proc
extrn   newline:proc
extrn   ShowWindow:proc

程式六(副程式):
;  File Name : winslib.asm

        .286
        .model  small
        .code
public  printch, newline, ShowWindow

        .code
printch proc
         :  (同 WINS3.ASM)
        ret
printch endp

newline proc
         :  (同 WINS3.ASM)
        ret
newline endp

ShowWindow    proc
         :  (同 WINS3.ASM)
        ret
ShowWindow    endp

        end

含括檔

include假指令會告訴MASM在編譯WINS4.ASM程式時,把WINSLIB.INC檔含括進來,就像直接寫在WINS4.ASM一般。
含括檔的副檔名隨便您取,甚至用 .ASM都可以,但不要用 .H或 .HPP,以免與C或C++的含括檔混淆。這樣可以把常用的指令搬到WINSLIB.INC含括檔裡頭,使main程式看起來清爽多了!


由於所有副程式都搬到另一個WINSLIB.ASM檔,不在WINS4.ASM裡頭。因此,您必須在WINS4.ASM把這些副程式宣告為extrn(external)外部的副程式,以免MASM找不到這些程式,而告訴您程式寫錯了!


當然,這些extrn宣告可直接寫在WIN4.ASM,但如果寫在含括檔會更優雅!至於WINSLIB.ASM副程式檔,則必須把需要對外公開的副程式宣告為public,否則將會是WINSLIB.ASM的私有程式。


對WINS4.ASM來說,其實只要把ShowWindow宣告為public即可。請試試,程式仍可以正常編譯執行!


模組程式結構


整套WINS4.ASM程式的結構如圖五所示。


圖五:
ASM2-5.png

多程式編譯方式


WIN4.ASM與WINSLIB.ASM要分別編譯:


C:\>masm wins4;
C:\>masm winslib;

含括檔則不必用MASM編譯,其實WINSLIB.INC就是WINS4.ASM的一部份,只是寫成另一個檔案罷了。
接著再用LINK把這兩個程式「加」起來即可。請注意加號的先後順序,以免造出WINSLIB.EXE檔。


c:\>link wins4+winslib;

使用函式庫

如果有多個副程式檔,每次連結程式都得一一相加,少了一個就做不出執行程式,這未免太麻煩了。
我們可以把所有的 .OBJ副程式檔,收集到WINSLIB.LIB圖書館裡,叫LINK自己到圖書館(函式庫)找尋所需的 .OBJ檔,不錯吧!
製作函式庫的方法很簡單,請輸入:


C:\>lib winslib.lib +winslib.obj; <Enter>

輸入時可省略 .LIB和 .OBJ副檔名。這樣就會建好一個新的WINSLIB.LIB檔。
如果WINSLIB.ASM曾修正並重新編譯,請用底下的指令更新函式庫裡的WINSLIB.OBJ檔:


C:\>lib winslib -+winslib; <Enter>

函式庫程式編譯方式
做好的WINSLIB.LIB得通知WINS4.ASM才行,請在含括檔WINSLIB.INC追加程式七的includelib指令。然後只要這樣編譯程式即可:


C:\>masm wins4;
C:\>link wins4;

很簡單吧!程式的編譯方式如同未模組化的WINS1.ASM一般。


結論


程式模組化,可說是簡化程式設計的最佳「利器」,而不是成為您的「負擔」,希望本文能提供您一個更快更好的設計方向。對本文有任何的看法或意見,非常歡迎您提出來討論,謝謝!


程式七:


;  File Name : winslib.inc

includelib winslib.lib

        .286
        .model  small
        .stack
   :     :     :(以下同程式六)

蘇言霖 2013/09/11 0 3442
Comments
Order by: 
Per page:
 
  • There are no comments yet
Rate
0 votes
Post info
蘇言霖
「超級懶貓級」社群網站站長
2013/09/11 (3852 days ago)
Actions