缓冲输入的工作原理

Sep*_*and 5 assembly dos input x86-16

下一个程序中的输入工作正常,但是当我要求显示输出时,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 : ', '$'
; --------------------------------------
Run Code Online (Sandbox Code Playgroud)

Sep*_*and 8

看看你如何定义你的输入缓冲区 ( buf: db 20 dup ('$')),我知道你想偷工减料,并且输入已经 $-terminated 准备好重新显示它。遗憾的是,这弄乱了 DOS 输入函数 0Ah 所需的设置,并且您的程序存在潜在缓冲区溢出的严重问题。
此外,使用 $-termination 并不是您可以做出的最明智的选择,因为 $ 字符可能已经出现在输入的字符中。我在下面展示的所有示例程序都将使用零终止。

输入文本使用 int 21h AH=0Ah

缓冲 STDIN 输入函数从键盘获取字符并继续这样做,直到用户按下该Enter键。所有字符和最后的回车符都放置在存储空间中,该存储空间从调用程序通过 in 中的指针提供的输入缓冲区的第 3 个字节开始DS:DX
字符数(不包括最后的回车符)存储在输入缓冲区的第二个字节中。
调用程序有责任告诉 DOS 存储空间有多大。因此,在调用此函数之前,您必须将其长度放在输入缓冲区的第一个字节中。要允许输入 1 个字符,请将存储大小设置为 2。要输入 254 个字符,请将存储大小设置为 255。
如果您不想从模板中调用任何以前的输入,那么最好也将第二个字节归零。基本上,模板是调用程序提供的输入缓冲区中预先存在(和有效)的内容。如果预先存在的内容无效,则模板不可用。

令人惊讶的是,此功能的编辑功能有限。

  • Escape从当前输入中删除所有字符。
    当前输入被放弃但保留在屏幕上,光标位于下一行,在输入第一次开始的位置下方。
  • Backspace从当前输入中删除最后一个字符。
    如果输入保持在屏幕上的单行内,则按预期工作。另一方面,如果输入跨越几行,那么这种退格将停在屏幕的左边缘。从那以后逻辑输入和视觉输入之间就会出现严重的差异,因为逻辑上的 退格会一直持续到存储空间中的第一个位置!
  • F6在当前输入中插入文件结束符 (1Ah)。
    屏幕将显示“^Z”。
  • F7在当前输入中插入一个零字节。
    屏幕将显示“^@”。
  • ctrlEnter 转换到下一行(执行回车和换行),当前输入没有添加任何内容,并且无法返回。

还有更多的编辑键可用。它们都让人想起EDLIN.EXE,古老的 DOS 行编辑器,这是一个文本编辑器,其中每一行都成为您构建下一行的模板。

  • F1 将模板中的一个字符复制到新行。
  • F2+... 将模板中的所有字符复制到新行,直到指定的字符。
  • F3 将模板中所有剩余的字符复制到新行。
  • F4+...跳过模板中的字符,直到指定的字符。
  • F5 使新行成为新模板。
  • Escape 清除当前输入并保持模板不变。
  • Delete 跳过模板中的一个字符。
  • Insert 进入或退出插入模式。
  • Backspace 删除新行的最后一个字符并将光标放回模板中的一个字符。
  • Left 与退格键相同。
  • Right 和F1一样。

此功能可扩展选项卡。选项卡扩展是用一系列一个或多个空格 (ASCII 32) 替换 ASCII 9 直到光标到达 8 的倍数的列位置的过程。
此选项卡扩展仅发生在屏幕上。存储空间将保存 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
; --------------------------------------
Run Code Online (Sandbox Code Playgroud)

输入文本使用 int 21h AH=3Fh

当与预定义的句柄 0 (in BX) 一起使用时,此Read From File Or Device 函数从键盘获取字符并继续这样做,直到用户按下Enter。所有字符(不超过 127 个)和最后一个回车加上一个额外的换行都被放置在 DOS 内核中的一个私有缓冲区中。这现在成为新模板。
此后,该函数将在 提供的缓冲区中写入参数DS:DX中请求的字节CX数。如果CX指定一个小于此输入生成的字节数的数字,需要对该函数进行一次或多次额外调用才能检索完整的输入。只要还有剩余的字符要拾取,此功能就不会使用键盘启动另一个输入会话!甚至在不同程序或同一程序的会话之间也是如此。

上一节中描述的所有编辑键都可用。

选项卡仅在屏幕上展开,不在模板中展开。

这个函数做ctrlC/ctrlBreak 检查。

此功能完成后,光标将位于最左侧的列中

  • 如果终止换行符不在返回的字节中,则为当前行。
  • 如果终止换行符在返回的字节中,则为下一行。

示例 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
; --------------------------------------
Run Code Online (Sandbox Code Playgroud)

示例 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
; --------------------------------------
Run Code Online (Sandbox Code Playgroud)

输入文本使用 int 2Fh AX=4810h

只有安装了 DOSKEY.COM TSR,才能调用此DOSKEY 缓冲 STDIN 输入函数。它的操作很像常规的缓冲 STDIN 输入函数 0Ah(见上文),但具有与 DOS 命令行相同的所有编辑可能性,包括使用所有 DOSKEY 特殊键的能力。

  • Up 从历史中获取上一项。
  • Down 从历史中获取下一项。
  • F7 显示历史记录中所有项目的列表。
  • AltF7 清除历史记录。
  • ...F8 查找以 ... 开头的项目
  • F9 按编号从历史记录中选择一个项目。
  • AltF10 删除所有宏定义。

在 DOS 6.2 上,存储空间始终限制为 128 字节,允许输入 127 个字符并为强制回车留出空间。无法预加载模板,因此始终将输入缓冲区的第二个字节设置为零。
在 DOS Win95 上,如果您使用类似doskey /line:255. 可以使用模板预加载存储空间。这使得 Win95 版本非常接近输入函数 0Ah 的可行性。

这个函数做ctrlC/ctrlBreak 检查。

此函数完成后,光标将位于当前行的最左侧列中。如果字符计数为零,则表示用户键入了尚未展开的 DOSKEY 宏的名称。你看不到未展开的线!需要再次调用该函数,这次返回时,光标将位于扩展文本的最后一个字符后面。
一个特点是当一个多命令宏 ( $T) 被扩展时,你只能得到第一个命令的扩展文本。需要额外调用该函数来获取其他扩展文本。尽管所有这些在像 COMMAND.COM 这样的命令 shell 中非常有用,但在用户应用程序中,你不知道什么时候发生这种情况真的很烦人。

由于输入的文本被添加到命令历史中,因此历史不可避免地被无关项目填满。当然不是你想在 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
; --------------------------------------
Run Code Online (Sandbox Code Playgroud)

输入文本使用 int 21h AH=08h

由于堆栈溢出强加的 30000 字节限制,文本在下面的答案中继续......

理解来源有问题?我使用的汇编程序:

  • 将以点 ( . )开头的标签视为第一级局部标签
  • 将以冒号 ( : )开头的标签视为二级局部标签
  • 是单指令多操作数 (SIMO),因此push cx si 转换为push cx push si.