缓冲输入的工作原理
How buffered input works
下一个程序的输入工作正常,但是当我要求显示输出时,DOS
根本不显示任何东西!这怎么可能?
ORG 256
mov dx, msg1
mov ah, 09h ;DOS.WriteString
int 21h
mov dx, buf
mov ah, 0Ah ;DOS.BufferedInput
int 21h
mov dx, msg2
mov ah, 09h ;DOS.WriteString
int 21h
mov dx, buf
mov ah, 09h ;DOS.WriteString
int 21h
mov ax, 4C00h ;DOS.TerminateWithExitcode
int 21h
; --------------------------------------
msg1: db 'Input : ', '$'
buf: db 20 dup ('$')
msg2: db 13, 10, 'Output : ', '$'
; --------------------------------------
看看您是如何定义输入缓冲区的 (buf: db 20 dup ('$')
),我明白了
你想偷工减料并让输入已经 $-terminated 准备好
re-displaying它。遗憾的是,这搞乱了 DOS 输入所需的设置
功能 0Ah 并且您的程序存在潜在缓冲区的严重问题
超限。
此外,使用 $-termination 并不是您可以做出的最明智的选择
因为 $ 字符可能已经出现在输入的字符中。
我在下面展示的所有示例程序都将使用 zero-termination
相反。
正在使用 int 21h AH=0Ah
输入文本
这个Buffered STDIN Input函数从键盘获取字符并
继续这样做,直到用户按下 Enter 键。全部
字符和最后的回车 return 被放置在存储 space 中
从调用程序提供的输入缓冲区的第 3 个字节开始
通过 DS:DX
.
中的指针
字符数,不包括最后的回车 return,存储在
输入缓冲区的第二个字节。
调用程序有责任告诉 DOS 有多大
存储 space 是。因此你必须把它的长度放在
调用此函数之前输入缓冲区。允许输入 1
您将存储大小设置为 2 的字符。允许输入 254
您将存储大小设置为 255 个字符。
如果您不想从模板中调用任何以前的输入,
那么最好也将第二个字节归零。基本上模板是
pre-existing 调用程序输入缓冲区中的(有效)内容
假如。如果 pre-existing 内容无效则模板不可用
可用的。
令人惊讶的是,此功能的编辑功能有限。
- Escape 从当前输入中删除所有字符。
当前输入被放弃但停留在屏幕上并且光标位于
下一行,在输入开始的位置下方。
- 返回space 从当前输入中删除最后一个字符。
如果输入在屏幕上保持在一行内,则按预期工作。
另一方面,如果输入跨越多行,那么这个退格将
停在屏幕的左边缘。从此以后会有严重的
逻辑输入和视觉输入之间的差异,因为逻辑上
退格将继续,直到到达存储中的第一个位置 space!
- F6 在当前输入中插入一个 end-of-file 字符 (1Ah)。
屏幕将显示“^Z”。
- F7 在当前输入中插入一个零字节。
屏幕将显示“^@”。
- ctrlEnter 过渡到下一行(执行
carriage return and linefeed),当前输入没有添加任何内容,你
回不去了
可以使用更多的编辑键。都让人联想到EDLIN.EXE,
古老的 DOS 行编辑器,这是一个文本编辑器,每个前一行
成为您构建下一行的模板。
- F1 从模板复制一个字符到新行。
- F2 + ... 将模板中的所有字符复制到新行,直到指定的字符。
- F3 将模板中所有剩余的字符复制到新行。
- F4 + ... 跳过模板中的字符,向上
到指定的字符。
- F5 使新行成为新模板。
- Escape 清除当前输入并保持模板不变。
- 删除 跳过模板中的一个字符。
- Insert 进入或退出插入模式。
- 后退space 删除新行的最后一个字符并将光标在模板中后退一个字符。
- 左 同后space.
- 右同F1
此功能扩展了选项卡。 Tab展开就是替换的过程
ASCII 9 通过一系列一个或多个 spaces (ASCII 32) 直到光标到达
8 的倍数的列位置。
此选项卡扩展仅发生在屏幕上。存储 space 将保存 ASCII 9.
这个函数做ctrlC/ctrlBreak
检查。
当这个函数完成后,光标将在最左边的列中
当前行。
示例 1,缓冲的 STDIN 输入。
ORG 256 ;Create .COM program
cld
mov si, msg1
call WriteStringDOS
mov dx, buf
mov ah, 0Ah ;DOS.BufferedInput
int 21h
mov si, msg2
call WriteStringDOS
mov si, buf+2
movzx bx, [si-1] ;Get character count
mov word [si+bx+1], 10 ;Keep CR, append LF and 0
call WriteStringDOS
mov ax, 4C00h ;DOS.TerminateWithExitcode
int 21h
; --------------------------------------
; IN (ds:si) OUT ()
WriteStringDOS:
pusha
jmps .b
.a: mov dl, al
mov ah, 02h ;DOS.DisplayCharacter
int 21h ; -> AL
.b: lodsb
test al, al
jnz .a
popa
ret
; --------------------------------------
buf: db 255, 16, "I'm the template", 13, 255-16-1+2 dup (0)
msg1: db 'Choose color ? ', 0
msg2: db 10, 'You chose ', 0
; --------------------------------------
正在使用 int 21h AH=3Fh
输入文本
当与预定义句柄 0(在 BX
中)一起使用时,此 Read From File Or Device
函数从键盘获取字符并继续这样做,直到
用户按下 Enter。所有字符(不超过 127 个)和
最后的回车 return 加一个 additio所有换行都放在一个私人
DOS 内核中的缓冲区。这现在成为新模板。
此后该函数将写入 DS:DX
处提供的缓冲区,金额
CX
参数中请求的字节数。如果CX
指定了一个数字
小于此输入生成的字节数,一个或多个
需要额外调用此函数才能检索完整的输入。
只要还有剩余的字符需要拾取,这个函数就会
不要使用键盘启动另一个输入 session!这甚至是真实的
不同的程序或 session 个相同的程序。
上一节中描述的所有编辑键都可用。
选项卡仅在屏幕上展开,而不是在模板中展开。
这个函数做ctrlC/ctrlBreak
检查。
当此函数完成时,光标将位于
上最左侧的列
- 当前行,如果终止换行符不在 returned 字节中。
- 下一行,如果终止换行符在 returned 字节中。
示例 2a,从文件或设备中读取,一次全部提取。
ORG 256 ;Create .COM program
cld
mov si, msg1
call WriteStringDOS
mov dx, buf
mov cx, 127+2 ;Max input is 127 chars + CR + LF
xor bx, bx ;STDIN=0
mov ah, 3Fh ;DOS.ReadFileOrDevice
int 21h ; -> AX CF
jc Exit
mov bx, ax ;Bytes count is less than CX
mov si, msg2
call WriteStringDOS
mov si, buf
mov [si+bx], bh ;Keep CR and LF, append 0 (BH=0)
call WriteStringDOS
Exit: mov ax, 4C00h ;DOS.TerminateWithExitcode
int 21h
; --------------------------------------
; IN (ds:si) OUT ()
WriteStringDOS:
pusha
jmps .b
.a: mov dl, al
mov ah, 02h ;DOS.DisplayCharacter
int 21h ; -> AL
.b: lodsb
test al, al
jnz .a
popa
ret
; --------------------------------------
buf: db 127+2+1 dup (0)
msg1: db 'Choose color ? ', 0
msg2: db 'You chose ', 0
; --------------------------------------
示例 2b,从文件或设备读取,一次读取一个字节。
ORG 256 ;Create .COM program
cld
mov si, msg1
call WriteStringDOS
mov dx, buf
mov cx, 1
xor bx, bx ;STDIN=0
mov ah, 3Fh ;DOS.ReadFileOrDevice
int 21h ; -> AX CF
jc Exit
mov si, msg2
call WriteStringDOS
mov si, dx ;DX=buf, CX=1, BX=0
Next: mov ah, 3Fh ;DOS.ReadFileOrDevice
int 21h ; -> AX CF
jc Exit
call WriteStringDOS ;Display a single byte
cmp byte [si], 10
jne Next
Exit: mov ax, 4C00h ;DOS.TerminateWithExitcode
int 21h
; --------------------------------------
; IN (ds:si) OUT ()
WriteStringDOS:
pusha
jmps .b
.a: mov dl, al
mov ah, 02h ;DOS.DisplayCharacter
int 21h ; -> AL
.b: lodsb
test al, al
jnz .a
popa
ret
; --------------------------------------
msg1: db 'Choose color ? ', 0
msg2: db 10, 'You chose '
buf: db 0, 0
; --------------------------------------
正在使用 int 2Fh AX=4810h
输入文本
此 DOSKEY Buffered STDIN Input 函数只能被调用 如果
DOSKEY.COM TSR 已安装。它的操作很像常规的 Buffered
STDIN 输入函数 0Ah(见上),但具有相同的编辑
作为 DOS 命令行的可能性,包括使用所有的能力
DOSKEY 特殊键。
- Up 从历史记录中获取上一个项目。
- 向下 从历史记录中获取下一项。
- F7 显示历史中所有项目的列表。
- AltF7 清除历史记录。
- ...F8 查找以 ...
开头的项目
- F9 按编号从历史记录中选择一个项目。
- AltF10 删除所有宏定义。
在 DOS 6.2 上,存储 space 总是限制在 128 字节,允许输入
127 个字符和强制运输的空间 return。它不是
可能 pre-load 一个模板,所以总是设置输入的第二个字节
缓冲区为零。
在 DOS Win95 上,如果安装了
DOSKEY.COM TSR 带有类似 doskey /line:255
的命令。有可能
pre-load 存储space 带有模板。这带来了Win95版本
非常接近输入函数 0Ah 的可行性。
这个函数做ctrlC/ctrlBreak
检查。
当这个函数完成后,光标将在最左边的列中
当前行。如果字符数为零,则表示用户输入
尚未扩展的 DOSKEY 宏的名称。你不
去看看 un-expanded 行!需要第二次调用该函数
并且这次 returning 时,光标将在最后一个字符的后面
展开的文本。
一个特点是当 multi-command 宏 ($T
) 被展开时,你只
获取第一个命令的扩展文本。的额外调用
需要函数来获取其他扩展文本。虽然这一切都是
从用户内部的命令 shell 中非常有用,例如 COMMAND.COM
应用程序,你不知道什么时候会发生,这真的很烦人。
由于输入的文本已添加到命令历史记录中,因此不可避免
历史充满了不相关的项目。肯定不是你想看到的
在 DOS 提示符下!
示例 3,调用 DOSKEY.COM.
ORG 256 ;Create .COM program
cld
mov ax, 4800h ;DOSKEY.CheckInstalled
int 2Fh ; -> AL
test al, al
mov si, err1
jz Exit_
Again: mov si, msg1
call WriteStringDOS
mov dx, buf
mov ax, 4810h ;DOSKEY.BufferedInput
int 2Fh ; -> AX
test ax, ax
mov si, err2
jnz Exit_
cmp [buf+1], al ;AL=0
je Again ;Macro expansion needed
mov si, msg2
call WriteStringDOS
mov si, buf+2
movzx bx, [si-1] ;Get character count (is GT 0)
mov word [si+bx+1], 10 ;Keep CR, append LF and 0
Exit_: call WriteStringDOS
Exit: mov ax, 4C00h ;DOS.TerminateWithExitcode
int 21h
; --------------------------------------
; IN (ds:si) OUT ()
WriteStringDOS:
pusha
jmps .b
.a: mov dl, al
mov ah, 02h ;DOS.DisplayCharacter
int 21h ; -> AL
.b: lodsb
test al, al
jnz .a
popa
ret
; --------------------------------------
buf: db 128, 0, 128+2 dup (0)
msg1: db 'Choose color ? ', 0
msg2: db 13, 10, 'You chose ', 0
err1: db 'N/A', 13, 10, 0
err2: db 'Failed', 13, 10, 0
; --------------------------------------
正在使用 int 21h AH=08h
输入文本
Because of the 30000 byte limit that Stack Overflow imposes the text continues in the below answer...
无法理解来源?我使用的汇编程序:
- 将以点 (.) 开头的标签视为一级本地标签
- 将以冒号 (: ) 开头的标签视为二级本地标签
- 是单指令多操作数(SIMO),所以
push cx si
转换为 push cx
push si
.
正在使用 int 21h AH=08h
输入文本
到目前为止描述的所有三种输入法(在上面的回答中!)显然是为适合 EDLIN.EXE 和 COMMAND.COM.
等 Microsoft 工具而量身定制的
如果您正在编写自己的应用程序,那么可以获得更好的结果
通过生成您自己的输入程序。这种程序的核心
将是DOS单字符输入功能之一。我选择了 STDIN
input function 08h 因为我要让
ctrlC/ctrlBreak 检查和我
打算自己通过 BIOS 回显字符 Int 10h AH=09h
Write Character And Attribute At Cursor Position。这样我可以
避免弄乱任何重定向的输出。
以编程方式使用此 BufferedInput 过程没有区别
或 DOS.BufferedInput 系统调用。但是对于键盘上的用户
输入会容易得多,因为所有的键都与旧的和
困难的模板编辑已被取消,取而代之的是通常的
使您能够自由移动光标的编辑键。
- 向左 向左移动光标。
- 向右 向右移动光标。
- 主页 将光标移动到最左边。
- End 将光标移到最右边。
- CtrlHome 删除左边的所有字符。
- CtrlEnd 删除右边的所有字符。
- 删除 删除当前字符。
- 返回space 删除光标左侧的字符。
- Escape 删除所有字符。
- Return 结束输入。
如果输入缓冲区的第 2 个字节包含非零值,则存储 space
应该包含一个旧字符串(可能来自以前的输入)。 DOS 会
称此为模板。与DOS不同的是:
- 旧字符串不需要回车 return 终止。
- 旧字符串立即显示在屏幕上。
在输入过程中,制表符未展开并且输入是
仅限于停留在当前行内。较长的文本将水平滚动。
当输入最后完成时,完成的文本写一次和 tab
扩展(在屏幕上,存储 space 将始终保持 ASCII 9)并且不再局限于一行。
这个程序做ctrlC/ctrlBreak
检查。
此过程完成后,光标将位于
当前行。
这个程序是用input redirection and output redirection写的
记住,因此非常适合控制台应用程序。
输入重定向的一个影响是将任何临时输出回显到屏幕上是无用的。要么用户不在那里注视屏幕,要么临时输出将在眨眼间消失。
示例 4,改进的缓冲 STDIN 输入。
ORG 256 ;Create .COM program
cld
mov si, msg1
call WriteStringDOS
mov dx, buf
call BufferedInput ;Replaces 'mov ah, 0Ah : int 21h'
mov si, msg2
call WriteStringDOS
mov si, buf+2
movzx bx, [si-1] ;Get character count
mov word [si+bx+1], 10 ;Keep CR, append LF and 0
call WriteStringDOS
mov ax, 4C00h ;DOS.TerminateWithExitcode
int 21h
; --------------------------------------
; IN (ds:si) OUT ()
WriteStringDOS:
pusha
jmps .b
.a: mov dl, al
mov ah, 02h ;DOS.DisplayCharacter
int 21h ; -> AL
.b: lodsb
test al, al
jnz .a
popa
ret
; --------------------------------------
; IN (ds:dx) OUT ()
BufferedInput:
; Entry DS:DX Buffer of max 1+1+255 bytes
; 1st byte is size of storage space starting at 3rd byte
; 2nd byte is size of old (CR-terminated) string, 0 if none
; Storage space can contain old (CR-terminated) string
; Exit DS:DX Nothing changed if header bytes were invalid
; 1st byte unchanged
; 2nd byte is size of new CR-terminated string
; Storage space contains new CR-terminated string
; Local [bp-1] PAGE Display page
; [bp-2] STORE Size of storage space
; [bp-3] ROW Row of input box
; [bp-4] COL Column of input box
; [bp-5] SHIFT Number of characters shifted out on the leftside
; [bp-6] INBOX Size of input box
; [bp-7] LIX Number of characters in current input string
; [bp-8] CIX Position of cursor in current input string
; [bp-10] FLAGS Bit[0] is ON for normal keyboard input
pusha
mov si, dx
lodsw ; -> SI points at storage space
test al, al ;AL is size of storage space
jz .Quit ;No storage space!
cmp ah, al ;AH is size of old string
jnb .Quit ;Old string too long!
mov bl, al
sub sp, 256 ;Local edit buffer (max size)
mov bp, sp
mov ah, 0Fh ;BIOS.GetVideoMode
int 10h ; -> AL=Mode AH=Cols BH=Page
push bx ;STORE and PAGE
mov bl, ah
mov ah, 03h ;BIOS.GetCursor
int 10h ; -> CX=Shape DL=Col DH=Row
push dx ;COL and ROW
sub bl, dl ;Size of the widest inbox
xor bh, bh
push bx ;INBOX and SHIFT
push bx ;CIX and LIX (replaces 'sub sp, 2')
call .ESC ;Clear edit buffer, reset some vars
mov cl, [si-1] ;Size of old string (starts at SI)
jmps .b
.a: lodsb ;Storage space gives old string
push cx si
call .Asc ;Input old string
pop si cx
.b: sub cl, 1
jnb .a
xor bx, bx ;STDIN
mov ax, 4400h ;DOS.GetDeviceInformation
int 21h ; -> AX DX CF
jc .c ;Go default to keyboard
test dl, dl
jns .d ;Block device, not keyboard
shr dl, 1
.c: adc bx, bx ; -> BX=1 if Keyboard
.d: push bx ;FLAGS
.Main: call .Show ;Refresh input box on screen
call .Key ;Get key from DOS -> AX
mov bx, .Scans
test ah, ah
jz .f ;Not an extended ASCII
mov [cs:.Fail], ah ;Sentinel
.e: lea bx, [bx+3]
cmp ah, [cs:bx-1]
jne .e
.f: call [cs:bx]
jmps .Main
.Quit: popa ;Silently quiting just like DOS
ret
; - - - - - - - - - - - - - - - - - - -
.Scans: db .Asc
db 4Bh, .s4B ;<LEFT>
db 4Dh, .s4D ;<RIGHT>
db 47h, .s47 ;<HOME>
db 4Fh, .s4F ;<END>
db 77h, .s77 ;<CTRL-HOME>
db 75h, .s75 ;<CTRL-END>
db 53h, .s53 ;<DELETE>
.Fail: db ?, .Beep
; - - - - - - - - - - - - - - - - - - -
.Beep: mov ax, 0E07h ;BIOS.TeletypeBell
int 10h
ret
; - - - - - - - - - - - - - - - - - - -
.Key: call :1
test ah, ah ;Extended ASCII requires 2 calls
jnz :2
:1: mov ah, 08h ;DOS.STDINInput
int 21h ; -> AL
mov ah, 0
:2: xchg al, ah
ret
; - - - - - - - - - - - - - - - - - - -
.Show: test word [bp-10], 1 ;FLAGS.Keyboard ?
jz :Ready ;No, input is redirected
movzx di, [bp-6] ;INBOX
movzx si, [bp-5] ;SHIFT
mov dx, [bp-4] ;COL and ROW
mov cx, 1 ;Replication count
mov bh, [bp-1] ;PAGE
mov bl, 07h ;WhiteOnBlack
:Next: mov ah, 02h ;BIOS.SetCursor
int 10h
mov al, [bp+si]
mov ah, 09h ;BIOS.WriteCharacterAndAttribute
int 10h
inc dl ;Next column
inc si ;Next character
dec di
jnz :Next ;Process all of the input box
mov dx, [bp-4] ;COL and ROW
add dl, [bp-8] ;CIX
sub dl, [bp-5] ;SHIFT
mov ah, 02h ;BIOS.SetCursor
int 10h
:Ready: ret
; - - - - - - - - - - - - - - - - - - -
.BS: cmp byte [bp-8], 0 ;CIX
jne :1
ret
:1: call .s4B ;<LEFT>
; --- --- --- --- --- --- --
; <DELETE>
.s53: movzx di, [bp-8] ;CIX
movzx cx, [bp-7] ;LIX
sub cx, di
je :2 ;Cursor behind the current input
:1: mov dl, [bp+di+1] ;Move down in edit buffer
mov [bp+di], dl
inc di
dec cx
jnz :1
dec byte [bp-7] ;LIX
:2: ret
; - - - - - - - - - - - - - - - - - - -
.RET: xor si, si
mov bx, [bp+256+10] ;pusha.DX -> DS:BX
mov al, [bp-7] ;LIX
inc bx
mov [bx], al ;2nd byte is size of new string
inc bx
jmps :2
:1: mov dl, [bp+si]
mov [bx+si], dl ;Storage space receives new string
inc si
:2: sub al, 1
jnb :1
mov byte [bx+si], 13 ;Terminating CR
push bx ;(1)
call .ESC ;Wipe clean the input box
call .Show ; and reset cursor
pop si ;(1) -> DS:SI
:3: lodsb ;Final unrestricted display,
mov dl, al ; expanding tabs
mov ah, 02h ;DOS.DisplayCharacter
int 21h ; -> AL
cmp dl, 13 ;Cursor ends in far left column
jne :3
lea sp, [bp+256] ;Free locals and edit buffer
popa
ret
; - - - - - - - - - - - - - - - - - - -
.ESC: mov di, 256 ;Fill edit buffer with spaces
:1: sub di, 2
mov word [bp+di], " "
jnz :1
mov [bp-8], di ;DI=0 -> CIX=0 LIX=0
mov byte [bp-5], 0 ;SHIFT=0
ret
; - - - - - - - - - - - - - - - - - - -
.Asc: cmp al, 8 ;<BACKSPACE>
je .BS
cmp al, 13 ;<RETURN>
je .RET
cmp al, 27 ;<ESCAPE>
je .ESC
cmp al, 10 ;Silently ignoring linefeed
jne :1 ; in favor of input redirection
ret
:1: movzx di, [bp-8] ;CIX
movzx si, [bp-7] ;LIX
lea dx, [si+1]
cmp dl, [bp-2] ;STORE
jb :3
jmp .Beep ;Storage capacity reached
:2: mov dl, [bp+si-1] ;Move up in edit buffer
mov [bp+si], dl
dec si
:3: cmp si, di
ja :2
mov [bp+si], al ;Add newest character
inc byte [bp-7] ;LIX
; --- --- --- --- --- --- --
; <RIGHT>
.s4D: inc byte [bp-8] ;CIX
mov al, [bp-7] ;LIX
cmp [bp-8], al ;CIX
jbe .Shift
mov [bp-8], al ;CIX
ret
; - - - - - - - - - - - - - - - - - - -
; <LEFT>
.s4B: sub byte [bp-8], 1 ;CIX
jnb .Shift
; --- --- --- --- --- --- --
; <HOME>
.s47: mov byte [bp-8], 0 ;CIX
jmps .Shift
; - - - - - - - - - - - - - - - - - - -
; <END>
.s4F: mov al, [bp-7] ;LIX
mov [bp-8], al ;CIX
; --- --- --- --- --- --- --
.Shift: mov dl, [bp-5] ;SHIFT
mov al, [bp-8] ;CIX
cmp al, dl
jb :1
add dl, [bp-6] ;INBOX
sub al, dl
jb :2
inc al
add al, [bp-5] ;SHIFT
:1: mov [bp-5], al ;SHIFT
:2: ret
; - - - - - - - - - - - - - - - - - - -
; <CTRL-HOME>
.s77: call .BS
cmp byte [bp-8], 0 ;CIX
ja .s77
ret
; - - - - - - - - - - - - - - - - - - -
; <CTRL-END>
.s75: call .s53 ;<DELETE>
mov al, [bp-8] ;CIX
cmp al, [bp-7] ;LIX
jb .s75
ret
; --------------------------------------
buf: db 255, 16, "I'm an OldString", 13, 255-16-1+2 dup (0)
msg1: db 'Choose color ? ', 0
msg2: db 10, 'You chose ', 0
; --------------------------------------
无法理解来源?我使用的汇编程序:
- 将以点 (.) 开头的标签视为一级本地标签
- 将以冒号 (: ) 开头的标签视为二级本地标签
- 是单指令多操作数(SIMO),所以
push cx si
转换为 push cx
push si
.
For a really high performing input procedure, look at Rich Edit Form Input, a Code Review contribution.
下一个程序的输入工作正常,但是当我要求显示输出时,DOS 根本不显示任何东西!这怎么可能?
ORG 256
mov dx, msg1
mov ah, 09h ;DOS.WriteString
int 21h
mov dx, buf
mov ah, 0Ah ;DOS.BufferedInput
int 21h
mov dx, msg2
mov ah, 09h ;DOS.WriteString
int 21h
mov dx, buf
mov ah, 09h ;DOS.WriteString
int 21h
mov ax, 4C00h ;DOS.TerminateWithExitcode
int 21h
; --------------------------------------
msg1: db 'Input : ', '$'
buf: db 20 dup ('$')
msg2: db 13, 10, 'Output : ', '$'
; --------------------------------------
看看您是如何定义输入缓冲区的 (buf: db 20 dup ('$')
),我明白了
你想偷工减料并让输入已经 $-terminated 准备好
re-displaying它。遗憾的是,这搞乱了 DOS 输入所需的设置
功能 0Ah 并且您的程序存在潜在缓冲区的严重问题
超限。
此外,使用 $-termination 并不是您可以做出的最明智的选择
因为 $ 字符可能已经出现在输入的字符中。
我在下面展示的所有示例程序都将使用 zero-termination
相反。
正在使用 int 21h AH=0Ah
输入文本
这个Buffered STDIN Input函数从键盘获取字符并
继续这样做,直到用户按下 Enter 键。全部
字符和最后的回车 return 被放置在存储 space 中
从调用程序提供的输入缓冲区的第 3 个字节开始
通过 DS:DX
.
中的指针
字符数,不包括最后的回车 return,存储在
输入缓冲区的第二个字节。
调用程序有责任告诉 DOS 有多大
存储 space 是。因此你必须把它的长度放在
调用此函数之前输入缓冲区。允许输入 1
您将存储大小设置为 2 的字符。允许输入 254
您将存储大小设置为 255 个字符。
如果您不想从模板中调用任何以前的输入,
那么最好也将第二个字节归零。基本上模板是
pre-existing 调用程序输入缓冲区中的(有效)内容
假如。如果 pre-existing 内容无效则模板不可用
可用的。
令人惊讶的是,此功能的编辑功能有限。
- Escape 从当前输入中删除所有字符。
当前输入被放弃但停留在屏幕上并且光标位于 下一行,在输入开始的位置下方。 - 返回space 从当前输入中删除最后一个字符。
如果输入在屏幕上保持在一行内,则按预期工作。 另一方面,如果输入跨越多行,那么这个退格将 停在屏幕的左边缘。从此以后会有严重的 逻辑输入和视觉输入之间的差异,因为逻辑上 退格将继续,直到到达存储中的第一个位置 space! - F6 在当前输入中插入一个 end-of-file 字符 (1Ah)。
屏幕将显示“^Z”。 - F7 在当前输入中插入一个零字节。
屏幕将显示“^@”。 - ctrlEnter 过渡到下一行(执行 carriage return and linefeed),当前输入没有添加任何内容,你 回不去了
可以使用更多的编辑键。都让人联想到EDLIN.EXE, 古老的 DOS 行编辑器,这是一个文本编辑器,每个前一行 成为您构建下一行的模板。
- F1 从模板复制一个字符到新行。
- F2 + ... 将模板中的所有字符复制到新行,直到指定的字符。
- F3 将模板中所有剩余的字符复制到新行。
- F4 + ... 跳过模板中的字符,向上 到指定的字符。
- F5 使新行成为新模板。
- Escape 清除当前输入并保持模板不变。
- 删除 跳过模板中的一个字符。
- Insert 进入或退出插入模式。
- 后退space 删除新行的最后一个字符并将光标在模板中后退一个字符。
- 左 同后space.
- 右同F1
此功能扩展了选项卡。 Tab展开就是替换的过程
ASCII 9 通过一系列一个或多个 spaces (ASCII 32) 直到光标到达
8 的倍数的列位置。
此选项卡扩展仅发生在屏幕上。存储 space 将保存 ASCII 9.
这个函数做ctrlC/ctrlBreak 检查。
当这个函数完成后,光标将在最左边的列中 当前行。
示例 1,缓冲的 STDIN 输入。
ORG 256 ;Create .COM program
cld
mov si, msg1
call WriteStringDOS
mov dx, buf
mov ah, 0Ah ;DOS.BufferedInput
int 21h
mov si, msg2
call WriteStringDOS
mov si, buf+2
movzx bx, [si-1] ;Get character count
mov word [si+bx+1], 10 ;Keep CR, append LF and 0
call WriteStringDOS
mov ax, 4C00h ;DOS.TerminateWithExitcode
int 21h
; --------------------------------------
; IN (ds:si) OUT ()
WriteStringDOS:
pusha
jmps .b
.a: mov dl, al
mov ah, 02h ;DOS.DisplayCharacter
int 21h ; -> AL
.b: lodsb
test al, al
jnz .a
popa
ret
; --------------------------------------
buf: db 255, 16, "I'm the template", 13, 255-16-1+2 dup (0)
msg1: db 'Choose color ? ', 0
msg2: db 10, 'You chose ', 0
; --------------------------------------
正在使用 int 21h AH=3Fh
输入文本
当与预定义句柄 0(在 BX
中)一起使用时,此 Read From File Or Device
函数从键盘获取字符并继续这样做,直到
用户按下 Enter。所有字符(不超过 127 个)和
最后的回车 return 加一个 additio所有换行都放在一个私人
DOS 内核中的缓冲区。这现在成为新模板。
此后该函数将写入 DS:DX
处提供的缓冲区,金额
CX
参数中请求的字节数。如果CX
指定了一个数字
小于此输入生成的字节数,一个或多个
需要额外调用此函数才能检索完整的输入。
只要还有剩余的字符需要拾取,这个函数就会
不要使用键盘启动另一个输入 session!这甚至是真实的
不同的程序或 session 个相同的程序。
上一节中描述的所有编辑键都可用。
选项卡仅在屏幕上展开,而不是在模板中展开。
这个函数做ctrlC/ctrlBreak 检查。
当此函数完成时,光标将位于
上最左侧的列- 当前行,如果终止换行符不在 returned 字节中。
- 下一行,如果终止换行符在 returned 字节中。
示例 2a,从文件或设备中读取,一次全部提取。
ORG 256 ;Create .COM program
cld
mov si, msg1
call WriteStringDOS
mov dx, buf
mov cx, 127+2 ;Max input is 127 chars + CR + LF
xor bx, bx ;STDIN=0
mov ah, 3Fh ;DOS.ReadFileOrDevice
int 21h ; -> AX CF
jc Exit
mov bx, ax ;Bytes count is less than CX
mov si, msg2
call WriteStringDOS
mov si, buf
mov [si+bx], bh ;Keep CR and LF, append 0 (BH=0)
call WriteStringDOS
Exit: mov ax, 4C00h ;DOS.TerminateWithExitcode
int 21h
; --------------------------------------
; IN (ds:si) OUT ()
WriteStringDOS:
pusha
jmps .b
.a: mov dl, al
mov ah, 02h ;DOS.DisplayCharacter
int 21h ; -> AL
.b: lodsb
test al, al
jnz .a
popa
ret
; --------------------------------------
buf: db 127+2+1 dup (0)
msg1: db 'Choose color ? ', 0
msg2: db 'You chose ', 0
; --------------------------------------
示例 2b,从文件或设备读取,一次读取一个字节。
ORG 256 ;Create .COM program
cld
mov si, msg1
call WriteStringDOS
mov dx, buf
mov cx, 1
xor bx, bx ;STDIN=0
mov ah, 3Fh ;DOS.ReadFileOrDevice
int 21h ; -> AX CF
jc Exit
mov si, msg2
call WriteStringDOS
mov si, dx ;DX=buf, CX=1, BX=0
Next: mov ah, 3Fh ;DOS.ReadFileOrDevice
int 21h ; -> AX CF
jc Exit
call WriteStringDOS ;Display a single byte
cmp byte [si], 10
jne Next
Exit: mov ax, 4C00h ;DOS.TerminateWithExitcode
int 21h
; --------------------------------------
; IN (ds:si) OUT ()
WriteStringDOS:
pusha
jmps .b
.a: mov dl, al
mov ah, 02h ;DOS.DisplayCharacter
int 21h ; -> AL
.b: lodsb
test al, al
jnz .a
popa
ret
; --------------------------------------
msg1: db 'Choose color ? ', 0
msg2: db 10, 'You chose '
buf: db 0, 0
; --------------------------------------
正在使用 int 2Fh AX=4810h
输入文本
此 DOSKEY Buffered STDIN Input 函数只能被调用 如果 DOSKEY.COM TSR 已安装。它的操作很像常规的 Buffered STDIN 输入函数 0Ah(见上),但具有相同的编辑 作为 DOS 命令行的可能性,包括使用所有的能力 DOSKEY 特殊键。
- Up 从历史记录中获取上一个项目。
- 向下 从历史记录中获取下一项。
- F7 显示历史中所有项目的列表。
- AltF7 清除历史记录。
- ...F8 查找以 ... 开头的项目
- F9 按编号从历史记录中选择一个项目。
- AltF10 删除所有宏定义。
在 DOS 6.2 上,存储 space 总是限制在 128 字节,允许输入
127 个字符和强制运输的空间 return。它不是
可能 pre-load 一个模板,所以总是设置输入的第二个字节
缓冲区为零。
在 DOS Win95 上,如果安装了
DOSKEY.COM TSR 带有类似 doskey /line:255
的命令。有可能
pre-load 存储space 带有模板。这带来了Win95版本
非常接近输入函数 0Ah 的可行性。
这个函数做ctrlC/ctrlBreak 检查。
当这个函数完成后,光标将在最左边的列中
当前行。如果字符数为零,则表示用户输入
尚未扩展的 DOSKEY 宏的名称。你不
去看看 un-expanded 行!需要第二次调用该函数
并且这次 returning 时,光标将在最后一个字符的后面
展开的文本。
一个特点是当 multi-command 宏 ($T
) 被展开时,你只
获取第一个命令的扩展文本。的额外调用
需要函数来获取其他扩展文本。虽然这一切都是
从用户内部的命令 shell 中非常有用,例如 COMMAND.COM
应用程序,你不知道什么时候会发生,这真的很烦人。
由于输入的文本已添加到命令历史记录中,因此不可避免 历史充满了不相关的项目。肯定不是你想看到的 在 DOS 提示符下!
示例 3,调用 DOSKEY.COM.
ORG 256 ;Create .COM program
cld
mov ax, 4800h ;DOSKEY.CheckInstalled
int 2Fh ; -> AL
test al, al
mov si, err1
jz Exit_
Again: mov si, msg1
call WriteStringDOS
mov dx, buf
mov ax, 4810h ;DOSKEY.BufferedInput
int 2Fh ; -> AX
test ax, ax
mov si, err2
jnz Exit_
cmp [buf+1], al ;AL=0
je Again ;Macro expansion needed
mov si, msg2
call WriteStringDOS
mov si, buf+2
movzx bx, [si-1] ;Get character count (is GT 0)
mov word [si+bx+1], 10 ;Keep CR, append LF and 0
Exit_: call WriteStringDOS
Exit: mov ax, 4C00h ;DOS.TerminateWithExitcode
int 21h
; --------------------------------------
; IN (ds:si) OUT ()
WriteStringDOS:
pusha
jmps .b
.a: mov dl, al
mov ah, 02h ;DOS.DisplayCharacter
int 21h ; -> AL
.b: lodsb
test al, al
jnz .a
popa
ret
; --------------------------------------
buf: db 128, 0, 128+2 dup (0)
msg1: db 'Choose color ? ', 0
msg2: db 13, 10, 'You chose ', 0
err1: db 'N/A', 13, 10, 0
err2: db 'Failed', 13, 10, 0
; --------------------------------------
正在使用 int 21h AH=08h
输入文本
Because of the 30000 byte limit that Stack Overflow imposes the text continues in the below answer...
无法理解来源?我使用的汇编程序:
- 将以点 (.) 开头的标签视为一级本地标签
- 将以冒号 (: ) 开头的标签视为二级本地标签
- 是单指令多操作数(SIMO),所以
push cx si
转换为push cx
push si
.
正在使用 int 21h AH=08h
输入文本
到目前为止描述的所有三种输入法(在上面的回答中!)显然是为适合 EDLIN.EXE 和 COMMAND.COM.
等 Microsoft 工具而量身定制的
如果您正在编写自己的应用程序,那么可以获得更好的结果
通过生成您自己的输入程序。这种程序的核心
将是DOS单字符输入功能之一。我选择了 STDIN
input function 08h 因为我要让
ctrlC/ctrlBreak 检查和我
打算自己通过 BIOS 回显字符 Int 10h AH=09h
Write Character And Attribute At Cursor Position。这样我可以
避免弄乱任何重定向的输出。
以编程方式使用此 BufferedInput 过程没有区别 或 DOS.BufferedInput 系统调用。但是对于键盘上的用户 输入会容易得多,因为所有的键都与旧的和 困难的模板编辑已被取消,取而代之的是通常的 使您能够自由移动光标的编辑键。
- 向左 向左移动光标。
- 向右 向右移动光标。
- 主页 将光标移动到最左边。
- End 将光标移到最右边。
- CtrlHome 删除左边的所有字符。
- CtrlEnd 删除右边的所有字符。
- 删除 删除当前字符。
- 返回space 删除光标左侧的字符。
- Escape 删除所有字符。
- Return 结束输入。
如果输入缓冲区的第 2 个字节包含非零值,则存储 space 应该包含一个旧字符串(可能来自以前的输入)。 DOS 会 称此为模板。与DOS不同的是:
- 旧字符串不需要回车 return 终止。
- 旧字符串立即显示在屏幕上。
在输入过程中,制表符未展开并且输入是
仅限于停留在当前行内。较长的文本将水平滚动。
当输入最后完成时,完成的文本写一次和 tab
扩展(在屏幕上,存储 space 将始终保持 ASCII 9)并且不再局限于一行。
这个程序做ctrlC/ctrlBreak 检查。
此过程完成后,光标将位于 当前行。
这个程序是用input redirection and output redirection写的
记住,因此非常适合控制台应用程序。
输入重定向的一个影响是将任何临时输出回显到屏幕上是无用的。要么用户不在那里注视屏幕,要么临时输出将在眨眼间消失。
示例 4,改进的缓冲 STDIN 输入。
ORG 256 ;Create .COM program
cld
mov si, msg1
call WriteStringDOS
mov dx, buf
call BufferedInput ;Replaces 'mov ah, 0Ah : int 21h'
mov si, msg2
call WriteStringDOS
mov si, buf+2
movzx bx, [si-1] ;Get character count
mov word [si+bx+1], 10 ;Keep CR, append LF and 0
call WriteStringDOS
mov ax, 4C00h ;DOS.TerminateWithExitcode
int 21h
; --------------------------------------
; IN (ds:si) OUT ()
WriteStringDOS:
pusha
jmps .b
.a: mov dl, al
mov ah, 02h ;DOS.DisplayCharacter
int 21h ; -> AL
.b: lodsb
test al, al
jnz .a
popa
ret
; --------------------------------------
; IN (ds:dx) OUT ()
BufferedInput:
; Entry DS:DX Buffer of max 1+1+255 bytes
; 1st byte is size of storage space starting at 3rd byte
; 2nd byte is size of old (CR-terminated) string, 0 if none
; Storage space can contain old (CR-terminated) string
; Exit DS:DX Nothing changed if header bytes were invalid
; 1st byte unchanged
; 2nd byte is size of new CR-terminated string
; Storage space contains new CR-terminated string
; Local [bp-1] PAGE Display page
; [bp-2] STORE Size of storage space
; [bp-3] ROW Row of input box
; [bp-4] COL Column of input box
; [bp-5] SHIFT Number of characters shifted out on the leftside
; [bp-6] INBOX Size of input box
; [bp-7] LIX Number of characters in current input string
; [bp-8] CIX Position of cursor in current input string
; [bp-10] FLAGS Bit[0] is ON for normal keyboard input
pusha
mov si, dx
lodsw ; -> SI points at storage space
test al, al ;AL is size of storage space
jz .Quit ;No storage space!
cmp ah, al ;AH is size of old string
jnb .Quit ;Old string too long!
mov bl, al
sub sp, 256 ;Local edit buffer (max size)
mov bp, sp
mov ah, 0Fh ;BIOS.GetVideoMode
int 10h ; -> AL=Mode AH=Cols BH=Page
push bx ;STORE and PAGE
mov bl, ah
mov ah, 03h ;BIOS.GetCursor
int 10h ; -> CX=Shape DL=Col DH=Row
push dx ;COL and ROW
sub bl, dl ;Size of the widest inbox
xor bh, bh
push bx ;INBOX and SHIFT
push bx ;CIX and LIX (replaces 'sub sp, 2')
call .ESC ;Clear edit buffer, reset some vars
mov cl, [si-1] ;Size of old string (starts at SI)
jmps .b
.a: lodsb ;Storage space gives old string
push cx si
call .Asc ;Input old string
pop si cx
.b: sub cl, 1
jnb .a
xor bx, bx ;STDIN
mov ax, 4400h ;DOS.GetDeviceInformation
int 21h ; -> AX DX CF
jc .c ;Go default to keyboard
test dl, dl
jns .d ;Block device, not keyboard
shr dl, 1
.c: adc bx, bx ; -> BX=1 if Keyboard
.d: push bx ;FLAGS
.Main: call .Show ;Refresh input box on screen
call .Key ;Get key from DOS -> AX
mov bx, .Scans
test ah, ah
jz .f ;Not an extended ASCII
mov [cs:.Fail], ah ;Sentinel
.e: lea bx, [bx+3]
cmp ah, [cs:bx-1]
jne .e
.f: call [cs:bx]
jmps .Main
.Quit: popa ;Silently quiting just like DOS
ret
; - - - - - - - - - - - - - - - - - - -
.Scans: db .Asc
db 4Bh, .s4B ;<LEFT>
db 4Dh, .s4D ;<RIGHT>
db 47h, .s47 ;<HOME>
db 4Fh, .s4F ;<END>
db 77h, .s77 ;<CTRL-HOME>
db 75h, .s75 ;<CTRL-END>
db 53h, .s53 ;<DELETE>
.Fail: db ?, .Beep
; - - - - - - - - - - - - - - - - - - -
.Beep: mov ax, 0E07h ;BIOS.TeletypeBell
int 10h
ret
; - - - - - - - - - - - - - - - - - - -
.Key: call :1
test ah, ah ;Extended ASCII requires 2 calls
jnz :2
:1: mov ah, 08h ;DOS.STDINInput
int 21h ; -> AL
mov ah, 0
:2: xchg al, ah
ret
; - - - - - - - - - - - - - - - - - - -
.Show: test word [bp-10], 1 ;FLAGS.Keyboard ?
jz :Ready ;No, input is redirected
movzx di, [bp-6] ;INBOX
movzx si, [bp-5] ;SHIFT
mov dx, [bp-4] ;COL and ROW
mov cx, 1 ;Replication count
mov bh, [bp-1] ;PAGE
mov bl, 07h ;WhiteOnBlack
:Next: mov ah, 02h ;BIOS.SetCursor
int 10h
mov al, [bp+si]
mov ah, 09h ;BIOS.WriteCharacterAndAttribute
int 10h
inc dl ;Next column
inc si ;Next character
dec di
jnz :Next ;Process all of the input box
mov dx, [bp-4] ;COL and ROW
add dl, [bp-8] ;CIX
sub dl, [bp-5] ;SHIFT
mov ah, 02h ;BIOS.SetCursor
int 10h
:Ready: ret
; - - - - - - - - - - - - - - - - - - -
.BS: cmp byte [bp-8], 0 ;CIX
jne :1
ret
:1: call .s4B ;<LEFT>
; --- --- --- --- --- --- --
; <DELETE>
.s53: movzx di, [bp-8] ;CIX
movzx cx, [bp-7] ;LIX
sub cx, di
je :2 ;Cursor behind the current input
:1: mov dl, [bp+di+1] ;Move down in edit buffer
mov [bp+di], dl
inc di
dec cx
jnz :1
dec byte [bp-7] ;LIX
:2: ret
; - - - - - - - - - - - - - - - - - - -
.RET: xor si, si
mov bx, [bp+256+10] ;pusha.DX -> DS:BX
mov al, [bp-7] ;LIX
inc bx
mov [bx], al ;2nd byte is size of new string
inc bx
jmps :2
:1: mov dl, [bp+si]
mov [bx+si], dl ;Storage space receives new string
inc si
:2: sub al, 1
jnb :1
mov byte [bx+si], 13 ;Terminating CR
push bx ;(1)
call .ESC ;Wipe clean the input box
call .Show ; and reset cursor
pop si ;(1) -> DS:SI
:3: lodsb ;Final unrestricted display,
mov dl, al ; expanding tabs
mov ah, 02h ;DOS.DisplayCharacter
int 21h ; -> AL
cmp dl, 13 ;Cursor ends in far left column
jne :3
lea sp, [bp+256] ;Free locals and edit buffer
popa
ret
; - - - - - - - - - - - - - - - - - - -
.ESC: mov di, 256 ;Fill edit buffer with spaces
:1: sub di, 2
mov word [bp+di], " "
jnz :1
mov [bp-8], di ;DI=0 -> CIX=0 LIX=0
mov byte [bp-5], 0 ;SHIFT=0
ret
; - - - - - - - - - - - - - - - - - - -
.Asc: cmp al, 8 ;<BACKSPACE>
je .BS
cmp al, 13 ;<RETURN>
je .RET
cmp al, 27 ;<ESCAPE>
je .ESC
cmp al, 10 ;Silently ignoring linefeed
jne :1 ; in favor of input redirection
ret
:1: movzx di, [bp-8] ;CIX
movzx si, [bp-7] ;LIX
lea dx, [si+1]
cmp dl, [bp-2] ;STORE
jb :3
jmp .Beep ;Storage capacity reached
:2: mov dl, [bp+si-1] ;Move up in edit buffer
mov [bp+si], dl
dec si
:3: cmp si, di
ja :2
mov [bp+si], al ;Add newest character
inc byte [bp-7] ;LIX
; --- --- --- --- --- --- --
; <RIGHT>
.s4D: inc byte [bp-8] ;CIX
mov al, [bp-7] ;LIX
cmp [bp-8], al ;CIX
jbe .Shift
mov [bp-8], al ;CIX
ret
; - - - - - - - - - - - - - - - - - - -
; <LEFT>
.s4B: sub byte [bp-8], 1 ;CIX
jnb .Shift
; --- --- --- --- --- --- --
; <HOME>
.s47: mov byte [bp-8], 0 ;CIX
jmps .Shift
; - - - - - - - - - - - - - - - - - - -
; <END>
.s4F: mov al, [bp-7] ;LIX
mov [bp-8], al ;CIX
; --- --- --- --- --- --- --
.Shift: mov dl, [bp-5] ;SHIFT
mov al, [bp-8] ;CIX
cmp al, dl
jb :1
add dl, [bp-6] ;INBOX
sub al, dl
jb :2
inc al
add al, [bp-5] ;SHIFT
:1: mov [bp-5], al ;SHIFT
:2: ret
; - - - - - - - - - - - - - - - - - - -
; <CTRL-HOME>
.s77: call .BS
cmp byte [bp-8], 0 ;CIX
ja .s77
ret
; - - - - - - - - - - - - - - - - - - -
; <CTRL-END>
.s75: call .s53 ;<DELETE>
mov al, [bp-8] ;CIX
cmp al, [bp-7] ;LIX
jb .s75
ret
; --------------------------------------
buf: db 255, 16, "I'm an OldString", 13, 255-16-1+2 dup (0)
msg1: db 'Choose color ? ', 0
msg2: db 10, 'You chose ', 0
; --------------------------------------
无法理解来源?我使用的汇编程序:
- 将以点 (.) 开头的标签视为一级本地标签
- 将以冒号 (: ) 开头的标签视为二级本地标签
- 是单指令多操作数(SIMO),所以
push cx si
转换为push cx
push si
.
For a really high performing input procedure, look at Rich Edit Form Input, a Code Review contribution.