1. 键盘扫描码获取工具解析
在开发需要键盘交互的应用程序时,获取准确的键盘扫描码和ASCII码是基本功。这个由Luo Yun Bin在1995年开发的ScanCode Viewer工具,至今仍是学习键盘输入处理的经典案例。它不仅能显示普通按键的扫描码,还能处理组合键和特殊功能键。
键盘扫描码是键盘控制器发送给计算机的原始数据,每个按键都有唯一的扫描码。当按键按下时发送"按下扫描码",释放时发送"释放扫描码"(通常是按下扫描码+80h)。这个工具的价值在于:
- 实时显示按键的按下/释放扫描码(16进制和10进制)
- 支持显示ASCII码(AH和AL寄存器值)
- 完整支持功能键、控制键和组合键
- 采用TSR(终止驻留程序)方式实现,不干扰其他程序运行
提示:扫描码与ASCII码不同,扫描码是物理按键的标识,而ASCII码是字符编码。例如'A'和'a'的ASCII码不同,但扫描码相同。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心实现原理与技术细节
2.1 中断接管机制
程序的核心是通过接管INT 09H键盘硬件中断来捕获扫描码:
assembly复制int9:
pushf
pusha
push ds
push es
in al,60h ; 从端口60h读取扫描码
mov key_buff,al ; 保存扫描码
test al,80h ; 检查是否是释放码
jnz int9_quit
or al,80h
mov key_buff1,al ; 保存释放码
; 显示处理...
int9_quit:
pop es
pop ds
popa
popf
jmp far ptr [off9] ; 跳转到原中断处理程序
这段代码展示了经典的DOS下硬件中断处理流程:
- 保存所有可能被修改的寄存器
- 从键盘端口(60h)读取扫描码
- 判断是按下(bit7=0)还是释放(bit7=1)事件
- 保存扫描码供后续显示使用
- 恢复环境并跳转到原中断处理程序
2.2 查表法优化分支逻辑
程序展示了汇编语言中处理多分支的高效方法 - 查表法。传统方式使用一系列cmp/jz指令:
assembly复制cmp al,value1
jz offset1
cmp al,value2
jz offset2
; 更多比较...
当分支很多时,这种写法既冗长又难以维护。程序改用查表法:
assembly复制table db value1,value2,value3...
table1 dw offset1,offset2,offset3...
mov di,offset table
mov cx,table长度
repnz scasb ; 在表中搜索AL值
jnz not_found
dec di
sub di,offset table ; 计算匹配位置
mov si,di
shl si,1 ; 乘以2(因table1是字数组)
add si,offset table1
jmp [si] ; 跳转到对应处理程序
这种方法的优势:
- 添加新分支只需扩展表,不增加代码复杂度
- 执行效率高,搜索使用repnz指令
- 代码可读性和可维护性大幅提升
2.3 特殊键处理
程序通过两个表处理特殊功能键:
- KEY_TABLE - 普通按键的ASCII码映射
- KEY_TABLE1 - 特殊功能键的扫描码列表及其对应的描述字符串
assembly复制; 特殊键扫描码表
KEY_TABLE1 DB 3bh,3ch,3dh,3eh,3fh,40h,41h,42h,43h,44h,57h,58h
DB 0eh,0fh,3ah,2ah,1dh,38h,36h,1ch,39h,1
DB 52h,53h,47h,4fh,49h,51h,48h,50h,4bh,4dh,46h,45h
; 对应的描述字符串指针表
DW f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12
DW bks,tab,cap,lsh,ctr,alt,rsh,ent,spa,_esc
DW _ins,del,hom,_end,pgu,pgd,up,dow,lef,rig,sco,num
这种设计使得特殊键的显示文本可以轻松扩展,而不影响核心处理逻辑。
3. 程序使用与扩展指南
3.1 基本使用方法
- 运行scancode.com程序
- 按下任意键查看其扫描码和ASCII码
- 按下Esc键退出程序
程序会显示三部分信息:
- 按下扫描码(十六进制和十进制)
- 释放扫描码(十六进制和十进制)
- 按键的ASCII码(AH和AL寄存器值)
3.2 自定义扩展
如需添加对新特殊键的支持:
- 在KEY_TABLE1中添加新扫描码
- 在指针表中添加对应的字符串标签
- 定义新的字符串描述(以0结尾)
例如添加对F13键的支持:
assembly复制; 在KEY_TABLE1末尾添加
DB 5Ah ; 假设F13的扫描码是5Ah
; 在指针表末尾添加
DW f13
; 定义新的字符串
f13 db 'F13',0
3.3 实际应用场景
这个技术可以应用于:
- 键盘宏录制工具开发
- 游戏中的按键配置功能
- 虚拟键盘实现
- BIOS调试工具
- 键盘输入测试程序
4. 关键问题与解决方案
4.1 扫描码与ASCII码混淆
常见错误是将扫描码直接当作ASCII码使用。实际上:
- 扫描码标识物理按键(如'A'和'a'相同)
- ASCII码表示字符('A'和'a'不同)
解决方案:
- 使用INT 16H获取ASCII码(AH=扫描码,AL=ASCII码)
- 对于无ASCII码的功能键,检查AH值
4.2 组合键处理
组合键如Ctrl+Alt+Del需要特殊处理:
- 分别捕获各控制键的按下/释放事件
- 记录各键状态(按下/释放)
- 检测特定组合条件
程序中的状态机设计可以扩展来处理这类复杂组合。
4.3 中断重入问题
在中断处理程序中调用DOS功能(如显示输出)可能导致系统崩溃,因为DOS不可重入。
本程序的解决方案:
- 在INT 09H处理中只做最低限度的处理(保存扫描码)
- 主循环中使用INT 16H获取按键并显示
- 避免在中断处理中调用复杂功能
5. 性能优化技巧
5.1 查表优化
对于固定表,可以使用二分查找代替线性搜索:
assembly复制; 假设表已排序
mov cx,count
mov di,offset table
mov al,key
binary_search:
mov si,di
add si,cx
shr si,1 ; 取中间位置
cmp al,[si]
jz found
jb lower_half
; 处理上半部分...
lower_half:
; 处理下半部分...
found:
; 处理找到的情况
5.2 位操作技巧
检测释放码的位操作:
assembly复制test al,80h ; 检测bit7
jnz is_release
比使用cmp指令更高效:
assembly复制cmp al,80h
jae is_release
5.3 寄存器使用优化
在中断处理中最小化寄存器使用:
assembly复制int9:
push ax ; 只保存实际使用的寄存器
push ds
in al,60h
; 处理...
pop ds
pop ax
iret
比使用pusha/popa更节省栈空间和执行时间。
这个键盘扫描码查看器虽然小巧,但包含了汇编语言编程的许多精华:硬件中断处理、查表优化、位操作技巧等。通过研究它的源代码,可以深入理解键盘输入的工作原理和高效汇编编程的技术要点。
