随着汇编程序的进一步复杂化,我们可以将多个汇编文件一起汇编,每个文件称作一个模块,并且在不同的模块之间交换数据,以在实现更加庞大和复杂的功能同时依然保持可读性。
另外,输入/输出是程序最重要的部分之一。在之前我们已经学习了一小部分通过DOS中断实现标准I/O的指令。经过高级语言特性的进一步拓展,汇编语言可以实现更加复杂的I/O。
目录
模块化程序设计
源程序文件的包含
MASM汇编程序允许把源程序放在几个不同的文本文件中,在汇编时通过包含伪指令INCLUDE结合成一体。即:
include 文件名
文件名必须符合DOS命名规范。对于文件的后缀没有明确规定,但一般默认为ASM,MAC(宏定义库)或INC(包含文件)。文件名可以包含路径。当没有指定路径时,MASM会在以下三个路径查找:
- 命令行参数/I指定的目录下查找
- 当前文件所在的目录下查找
- 环境参数INCLUDE指定的目录下查找
INCLUDE伪指令进行汇编时,会将它指定的文本文件内容插入到伪指令所在的位置进行同时汇编。
下面给出一个多模块同时汇编的例子:将输入的数据按照升序输出。最多输入100个数据,每个数据都是无符号字节常量。
分析:常见的DOS输出应该汇编为宏,保存在宏定义库中,方便其他文件共享。另外设计处理数据输入和输出的子程序保存在源文件文件中。主程序则主要提供入口参数和出口参数。
; 宏定义库 MACRO.mac
dispchar macro char
mov dl,char
mov ah,2
int 21h
endm
dispmsg macro msg
mov dx,offset msg
mov ah,9
int 21h
endm
; 主程序文件
include MACRO.mac
.model small
.stack
.data
msg1 db 'Please enter the number(XX):',13,10,'$'
msg2 db 'The number sentered are:',13,10,'$'
msg3 db 'The sorting result(ascending):',13,10,'$'
crlf db 13,10,'$'
maxcount = 100
count dw ?
buf db maxcount dup(?)
.code
.startup
dispmsg msg1
mov bx,offset buf
call input
.if cx!=0
mov count,cx
dispmsg crlf
dispmsg msg2
mov bx,offset buf
mov cx,count
start2:mov al,[bx]
call aldisp
disp char','
inc bx
loop start2
dispmsg crlf
mov bx offset buf
mov cx,cunt
call sorting
dispmsg msg3
mov bx,offset buf
mov cx,count
start3: mov al,[bx]
call aldisp
disp char','
inc bx
loop start3
dispmsg crlf
.endif
start4: .exit 0
include minproc.asm
end
;minproc.asm子程序实现文件
aldisp proc
push ax
push dx
push ax
mov dl,al
shr dl,1
shr dl,1
shr dl,1
shr dl,1
or dl,30h
cmp dl,39h
jbe aldisp1
add dl,7
aldisp1:mov ah,2
int 21h
pop dx
and dl,0fh
or dl,30h
cmp dl,39h
jbe aldisp2
add dl,7
aldisp2:
mov ah,2
int 21h
pop dx
pop ax
ret
aldisp endp
sorting proc
.if cx!=0
.if cx!=1
push ax
push dx
push si
push si,bx
dec cx
outlp:mov dx,cx
mov bx,si
inlp:mov al,[bx]
cmp al,[bx+1]
jna next
xchg al,[bx+1]
mov [bx],al
next:inc bx
dec dx
jnz inlp
loop outlp
pop si
pop dx
pop ax
.endif
.endif
sortend:ret
sorting endp
convert macro
local input21,input22
local input24,input25
cmp dl,0
je input25
.if dl>9
sub dl,7
.endif
input21:and dl,0fh
.if dh!=0
.if dh>9
sub dh,7
.endif
input22:shl dh,1
shl dh,1
shl dh,1
shl dh,1
or dl,dh
input24:mov [bx],dl
inc bx
inc cx
input25:
endm
input proc
push ax
push dx
xor cx,cx
input01:xor dx,dx
input02:mov ah,1
int 21h
input10:cmp al,0dh
je input30
cmp al,' '
je input20
cmp al,','
je input20
cmp al,8
je input17
cmp al,'0'
jb input17
cmp al,'f'
ja input17
cmp al,'a'
jb input11
sub al,20h
jmp input12
input11:cmp al,'f'
ja input17
cmp al,'a'
ja input12
cmp al,'9'
ja input17
input12:
cmp dl,0
jne input13
mov dl,al
jmp input02
input13:
cmp dh,0
jne input17
mov dh,dl
mov dl,al
jmp input02
input17:
mov dl,7
mov ah,2
int 21h
mov dl,'?'
mov ah,2
int 21h
jmp input01
input20:
convert
pop dx
pop ax
ret
input endp
利用目标文件的连接开发源程序,需要注意几个问题。
- 全局变量要使用PUBLIC或者EXTERN声明
- 设置好段属性
- 正确处理参数传递问题。少量参数传递可以使用寄存器和堆栈传参。大量变量可以使用缓冲区buffer传参。也可以利用全局变量传递参数。
- 必须有且只有一个模块中还有主程序(用.STARTUP和.EXIT标注),其他模块为宏定义或者子程序形式。
子程序库的调入
当使用INCLUDE伪指令时,被调用的子程序.asm文件的所有代码(即便没有被使用)也会被一起汇编,最终造成可执行文件过于庞大。MASM汇编程序提供子程序库(.lib)的方法来克服这个缺点。
子程序库的调用相比源文件的调用更加严格。主要体现在:
- 各子程序的传递参数方法需要保持一致
- 子程序类型最好一样(都为FAR或者NEAR)
- 采用相同的存储模式(寄存器或者堆栈)
可以编写完.asm文件后使用指令把子程序目标模块连接成一个库文件:
lib 库文件名(+源文件名)
当汇编成文件时,要使得子程序的类型一致,因此上面的例子如果要使用库管理,需要删除PUBLIC COUNT语句,并且所有的子程序都改为了NEAR类型(因为使用寄存器传参)。当使用LIB指令连接源文件时,注意连接的是汇编完成的.OBJ文件。
当连接完子程序库之后,就可以在主程序中使用INCLUDE LIB伪指令,这样就不需要在LINK时说明库文件。
总结
汇编语言的INCLUDE语句允许将多份源文件一起进行联合汇编与连接,而库文件管理程序减少了部分的汇编开销,可以使得其在需要时方便调用,提高了效率。