在無圖形介面、無背景列印服務的 DOS 環境下,將文字檔內容導向列印機 (LPT1) 是常見需求。這份 PRINT FILE PROGRAM 完整展示了如何:從 PSP (Program Segment Prefix) 解析命令列檔名、以 Handle 方式開啟/讀取/關閉檔案、呼叫 BIOS INT 17h 逐字元輸出至並行埠、並在列印過程中輪詢鍵盤允許使用者按 Esc 中斷。程式結構清晰,錯誤處理完善 (檔案開啟失敗、讀取錯誤、印表機離線/錯誤),是學習 DOS 檔案 I/O 與硬體級列印的經典範例。
filename 緩衝區,遇空白/控制字元 (≤20h) 結束,補 0 成 ASCIIZ。INT 21h, AH=3Dh, AL=0 (唯讀),取得 Handle 存入 file_handle。INT 21h, AH=3Fh 讀取 1024 位元組至 file_bufferINT 16h, AH=1 檢查鍵盤緩衝區 → 若有鍵再 AH=0 取碼 → 比對 Esc (1Bh)INT 17h, AH=0 輸出字元至 LPT1 (DX=0),檢查狀態位 (AH bit 0=Timeout, bit 3=I/O Error, bit 5=Not Selected) → 錯誤則跳轉錯誤處理mov al, [DS:80H] ; 命令列長度
cmp al, 1
ja get_PSP_filename
; 無參數 → 顯示錯誤訊息並結束
get_PSP_filename:
mov ds, psp_seg ; DS 指向 PSP
mov si, 82H ; 命令列起始
lea di, filename
cld
get_PSP_data_1:
lodsb
cmp al, 21H ; 控制字元/空白?
jb got_PSP_filename
stosb
jmp get_PSP_data_1
got_PSP_filename:
mov al, 0
stosb ; ASCIIZ 結尾
file_read:
mov ah, 3FH
lea dx, file_buffer
mov cx, 1024
mov bx, file_handle
int 21H
jnc file_read_ok
; 錯誤處理:關閉檔案、顯示訊息、結束
file_read_ok:
cmp ax, 0
je close_file
mov file_count, ax ; 實際讀取位元組數
lea bx, file_buffer
mov file_pointer, bx ; 當前處理指標
print_data_block:
mov ah, 1
int 16H ; 檢查鍵盤
jz print_data_block_1
mov ah, 0
int 16H ; 取鍵碼
cmp al, 01BH ; ESC?
je close_file
print_data_block_1:
mov si, file_pointer
mov al, [si]
mov ah, 0
mov dx, 0 ; LPT1
int 17H ; BIOS 列印
test ah, 25H ; 檢查錯誤位 (Timeout/Error/Not Selected)
jnz printer_error
inc si
mov file_pointer, si
dec file_count
jnz print_data_block
jmp file_read
INT 17h, AH=0 回傳狀態於 AH:Bit 0=Time Out, Bit 3=I/O Error, Bit 4=Selected, Bit 5=Out of Paper, Bit 6=Acknowledge, Bit 7=Not Busy。程式以 test ah, 25h (00100101b) 同時檢查 Timeout (bit 0) 與 I/O Error (bit 3) / Not Selected (bit 5) — 實際上 25h = bit 0 + bit 3 + bit 5,涵蓋主要錯誤狀態。
所有錯誤路徑 (檔案開啟失敗、讀取錯誤、印表機錯誤) 均:
INT 21h, AH=3Eh)terminate_display (INT 21h, AH=09h 列印 $ 結尾字串)terminate_program (INT 21h, AH=4Ch 結束)確保無 Handle 洩漏、無記憶體殘留。
.MODEL small
.STACK 500
.DATA
psp_seg dw 0
no_cl_mess db "This routine requires that a "
db "filename be on the command line for printing."
db 0dh,0ah,"Please try with a filename.",0dh,0ah,"$"
file_bad_open db "Bad file open",0dh,0ah,"$"
file_bad_read db "Bad file read",0dh,0ah,"$"
printer_bad_mess db "!! Printer Error !!!!",0dh,0ah,"$"
printing_mess db "A file is being printed,",0dh,0ah
db "To stop printing, Press ESC key",0dh,0ah,"$"
filename db 128 dup(0)
file_handle dw 0
file_count dw 0
file_pointer dw offset file_buffer
file_buffer db 1024 dup(0)
.CODE
start proc near
mov al,[DS:80H]
mov dx,@data
mov ds,dx
mov psp_seg,ES
cmp al,1
ja get_PSP_filename
lea dx,no_cl_mess
terminate_display:
mov ah,09
int 21H
terminate_program:
mov ah,4CH
mov al,00
int 21H
get_PSP_filename:
mov ax,ds
mov es,ax
mov ds,psp_seg
mov si,82H
lea di,filename
cld
get_PSP_data_1:
lodsb
cmp al,21H
jb got_PSP_filename
stosb
jmp get_PSP_data_1
got_PSP_filename:
mov al,0
stosb
push es
pop ds
mov ah,3DH
lea dx,filename
mov al,0
int 21H
jnc file_open_ok
lea dx,file_bad_open
jmp terminate_display
file_open_ok:
mov file_handle,ax
lea dx,printing_mess
mov ah,09
int 21H
file_read:
mov ah,3FH
lea dx,file_buffer
mov cx,1024
mov bx,file_handle
int 21H
jnc file_read_ok
mov ah,3EH
mov bx,file_handle
int 21H
lea dx,file_bad_read
jmp terminate_display
file_read_ok:
cmp ax,0
je close_file
mov file_count,ax
lea bx,file_buffer
mov file_pointer,bx
print_data_block:
mov ah,1
int 16H
jz print_data_block_1
mov ah,0
int 16H
cmp al,01BH
je close_file
print_data_block_1:
mov si,file_pointer
mov al,[si]
mov ah,0
mov dx,0
int 17H
test ah,25H
jnz printer_error
inc si
mov file_pointer,si
dec file_count
jnz print_data_block
jmp file_read
close_file:
mov ah,3EH
mov bx,file_handle
int 21H
jmp terminate_program
printer_error:
lea dx,printer_bad_mess
jmp terminate_display
start endp
end start
INT 17h 直接驅動並行埠,而非 DOS INT 21h, AH=05h (列印字元至 LST:),前者可精細控制狀態檢查與輪詢,後者由 DOS 管理列印佇列但無中斷機制。INT 16h, AH=1 (檢查狀態) + AH=0 (讀取) 模式,實現「長時間 I/O 操作中可響應使用者中斷」的交互模式,現代非同步 I/O / 事件迴圈的雛形。這份程式碼雖屬 DOS 時代產物,但其「資源獲取即初始化 (RAII) 前身」、「非阻塞輪詢」、「分層錯誤處理」等設計思想,在現代嵌入式韌體、裝置驅動程式、高效能網路伺服器中依然閃耀著工程智慧的光芒。