汇编实验报告-屏幕窗口程序实验
1. 题目要求:自行编写一个键盘输入并且在屏幕输出的程序,它可以完成键盘读入并且在屏幕显示出来。具体要求:
![image-20221109144849693](https://gitee.com/CaoZhongyang/cartographic-bed/raw/master/https://gitee.com/CaoZhongyang/cartographic-bed/202211291505134.png)
![image-20221109144902289](https://gitee.com/CaoZhongyang/cartographic-bed/raw/master/https://gitee.com/CaoZhongyang/cartographic-bed/202211291505135.png)
2. 运行环境:Windows11+MASM
3. 题目分析:
题目要求建立三个窗口用于回显,利用BIOS调用的10H中的6号命令可以完成将屏幕中给定范围的矩形区域上卷n行,因此我们可以使用该命令划分出三个窗口的形状。
此外,题目要求我们的所有输入都要在最下方窗口显示,默认也会在右窗口显示,但是使用左右箭头可以切换显示屏幕。因此我们需要动态的置光标于指定位置,这个可以使用类型10H的0A号命令完成,只要给定光标所在的行列坐标即可。
最后考虑左右箭头切换屏幕的问题。由于左右箭头不是可显示字符,没有ASCII码,因此我们要利用键盘中断获取的扫描码进行判断。搜索可知左右箭头的扫描码分别为4BH、4DH.取扫描码可以使用BIOS调用的16H类型的0号命令,AH中即为扫描码,AL中为ASCII码(如果有)。并且判断完之后,还可以直接利用AL存储ASCII这一点进行显示输出。
4. 流程图绘制:根据分析和题目要求绘制主函数的流程图:
![image-20221109150326752](https://gitee.com/CaoZhongyang/cartographic-bed/raw/master/https://gitee.com/CaoZhongyang/cartographic-bed/202211291505136.png)
根据主函数流程图,可以编写主函数程序如下:
main proc
push ds
mov ax,data
mov ds,ax
call clear
scroll 11,win1_ulc,win1_ulr,win1_lrc,win1_lrr
scroll 11,win2_ulc,win2_ulr,win2_lrc,win2_lrr
scroll 1,win3_ulc,win3_ulr,win3_lrc,win3_lrr
myloop:
call get_char
call display
jmp myloop
main endp
5. 各部分与功能的流程图/程序:
- 数据段定义:当我们定义数据段时,我们要考虑的有以下几方面因素:
-
三个窗口的光标起始位置:因为回显时使用的BIOS调用需要光标的x,y坐标,而光标定位到三个窗口的左下角,因此我们需要分别设置三个窗口左下角的行列号。
-
三个屏幕的位置坐标:我们使用上卷的方式显示三个窗口,而10h类型的6号命令需要提供窗口的左上角坐标和右下角坐标,因此我们需要分别定义win1\win2\win3的左上角、右下角的行号和列号。
-
判断光标在左还是右的flag:因为题目要求按下左右箭头实现屏幕显示的切换,因此我们的get_char函数中需要对左右窗口进行设置,而display函数则需要知道在左还是右窗口进行显示。要实现这个功能,可以引入一个flag,为1则表示在右窗口,反之在左。
数据段定义(较长,同类型的以...省略):
data segment
win1x db 15;左窗口光标
...
win3y db 15
win1_ulc db 10;win1左上列
...
win3_lrr db 22
isright db 1;flag,判断是否在右窗口
data ends
-
置光标位置函数curse:
这两个函数主要执行的功能是根据给定的坐标把光标置于该位置,使用10H中断即可,实现比较简单。不过由于该函数被diplay调用,此时AL中可能存储着要输出字符的ASCII,因此要保护一下。但由于本实验多次反复使用该函数,该函数需要光标位置的行、列号,但我们将行列号存储在内存单元中(win1x,win1y,win2x......),则在每次调用子函数之前,都需要通过寄存器设置这两个存储,在curse中再使用寄存器读取,比较麻烦。若使用宏编程方式,可以直接使用形式参数接收主函数提供的实参,因此在调用时实参可以直接写内存单元名win1x,win1y等等,更加方便。
- 流程图
- 程序段代码如下:
curse MACRO x,y ;置光标位置
push ax
mov dh,x
mov dl,y
mov bh,0
mov ah,2
int 10h
pop ax
ENDM
-
屏幕上卷函数scroll
该函数功能为将给定屏幕上卷指定行数,使用10H中断的6号命令即可。与curse函数类似,由于本函数一共需要5个参数,如果编写子程序需要用寄存器对这些地址单元依次存取,比较麻烦,因此也采用宏的方式编写,使用形式参数接收实参即可。同理,也要考虑保护AX寄存器。
-
流程图
-
程序段代码如下:
scroll MACRO cnt,ulc,ulr,lrc,lrr ;窗口上卷cnt行
push ax;AX里可能有字符
mov al,cnt;卷cnt行
mov ch,ulr
mov cl,ulc
mov dh,lrr
mov dl,lrc
mov ah,6
mov bh,24h
int 10h
pop ax
ENDM
-
键盘输入与控制字符判断函数get_char
该函数功能是利用键盘中断获取字符的扫描码和ASCII码,用扫描码判断是否为左右箭头或ESC,若是则执行对应控制命令;将非控制字符的ASCII码存在AL寄存器,留待display展示。
-
流程图
-
代码段如下:
-
get_char proc near;获取字符,判断是否是左右箭头和esc。注意我们这里不在意现在光标在win1/win2
input:
mov ah,0
int 16h;BIOS,ah存扫描码,al存ASCII码
cmp ah,4bh;左箭头
jnz no_left;若不是,继续判断
mov isright,0;是,切换光标
jmp input;不可输出箭头,需要再接收
no_left:
cmp ah,4dh;右箭头
jnz no_right;也不是右箭头
mov isright,1
jmp input;不可输出箭头,需要再接收
no_right:
cmp ah,01;ESC
jnz get_complete;说明不是控制字符,回主函数准备输出
mov ah,4ch
int 21h;ESC,结束整个程序
get_complete:
ret;回主函数,此时al保存着字符
get_char endp
-
输出与上卷判断函数display
该函数功能是将AL中字符输出到下方窗口,以及左右窗口的其中一个,并在输出完成后判断是否应该上卷。(本函数调用的output1只是对BIOS调用10H的封装,不再赘述)
-
流程图
-
代码段较长,这里不再附上,可参看附件中的源程序。
-
6.运行结果
-
输入,默认在下窗口、右窗口显示。可以看到当右侧窗口光标进行到行末后,右侧屏幕会上卷一行。
-
按下左箭头,继续输入,可以看到左窗口和下窗口开始显示内容。可以看到左、下窗口的上卷也成功实现。由于下窗口只有一行,所以上卷后只留下新一行的信息。
-
当左/右窗口输满时,最上一行会上卷消失:
可见功能均正常实现。