=========================== choice.com 应用推广三部曲 Will Sort - 2005/04/18 Updated:2005/05/14 =========================== 序言 ============================================================================== 在 choice.com 出现之前,用户控制批处理运行过程的途径通常被限制在命令行参 数和环境变量上,虽然有个别的编程者会使用debug 的汇编脚本或者 date 、time等一 些可以接受键盘输入的特殊命令来实现运行中接受用户输入的参数,但是它们不是使用 方法很复杂,就是适用的场合很有限,或者还有其他一些诸如此类的问题。 但是,自从 choice.com 出现后,菜单选择立即成为批处理程序交互的重要手段。 程序中开始大量出现由 echo/choice/if errorlevel goto 所组成的结构范式。然而, 随着 choice.com 不断深入的应用,大家在享受它所带来的便利时,也渐渐无法忍受它 所带来的不便。 这不便中最主要的,就是 choice.com 仅支持字母和数字等可以指定候选字符的按 键,而对经常会在菜单中用到的回车、空格、Esc 、光标移动、翻页等特殊按键却无法 用字母进行指定,也就无法在菜单中使用它们。 然而,事实真的如此吗?不妨看看下文会给我们带来哪些收获。 ============================================================================== =========================== 三部曲之一:自欺欺人 Will Sort - 2005/04/18 Updated:2005/04/23 =========================== ============================================================================== 首先,让我们进入命令行,当出现命令提示符后,尝试敲入并执行下面的语句: choice /n/s/c:Q Press [PageDown] to quit: 当按下回车后,我们看到了提示,然后根据提示按下 PageDown 键,然后我们发现 了什么?程序运行结束了!并且在提示语的后面出现了一个大写的字母 Q。 哦,太神奇了,我发现了新大陆!
不,这只是新大陆的一个小岛而已。事实上,大部分的特殊按键在 choice.com 中 都对应着一个字母、数字或者其他可以显示出来的字符。 以下是一些常用的特殊按键对应字符的简明列表: ------------------------------------------------------------------------ ; [F1] < [F2] = [F3] > [F4] ? [F5] @ [F6] A [F7] B [F8] C [F9] D [F10] R [Insert] G [Home] I [Page Up] S [Delete] O [End] Q [Page Down] H [UP Arrow] K [Left Arrow] P [Down Arrow] M [Right Arrow] 27 [Esc] 8 [Back Space] ------------------------------------------------------------------------ 我们可以再根据上表中提供的字符做新的尝试: choice /n/s/c:; Press [F1] to quit: 哈,又成功了!再试: choice /n/s/c:< Press [F2] to quit: 啊,出错了!找不到文件,怎么回事? 嗯,你用的小于号是系统定义的文件重定向符号,它把 Press当成文件重定向了。 那怎么办? 简单,在小于号两边加上双引号,系统不认识小于号就行了,就像这样: choice /n/s/c:"<" Press [F2] to quit: 哈,真是这样呢! 那么,你可以再多试一些练习一下。 好,我试,我试,我试试试。 choice /n/s/c:= Press [F3] to quit: ---〉成功! choice /n/s/c:> Press [F4] to quit: ---〉失败! 哦,不对,改: choice /n/s/c:">" Press [F4] to quit: ---〉成功! choice /n/s/c:? Press [F5] to quit: ---〉成功! 好了,我会用了,但是,这到底是怎么回事呢? 原因嘛,深究起来比较复杂;简单的说,是因为这些特殊的按键会产生一种系统可 以识别的扩展编码,一般是由0 或者224 和另一数字编码组成。而 choice.com 接受这 些按键时,会将编码中认为无效的0 或者224 过滤掉,而剩下后面的数字编码,而这部 分数字又恰好会被误认为是某个可显示字符的 ASCII编码。以开始提到的 PageDown 来 说,它的扩展编码就是“0;81”,而与此相对应的,字母 Q的 ASCII编码也是81,于是 choice.com就将这 PageDown 和 Q这两个键混淆了。其它“F1”和分号、“F2”和小于 号等等都是类似的原因。这也是本节篇名“自欺欺人”的由来。 等等,你说什么“扩展编码”,那是什么东东? “扩展编码”实际上是 MS-DOS 从键盘读取按键时,经由键盘扫描码略加修改转换 后的一种按键的内部编码,通过这种转换后的编码形式,它可以兼容地处理 ASCII字符 键和非 ASCII字符键(就是在 ASCII码表中找不到对应编码的按键)。 呵,似懂非懂啊,还是算了吧。 等一下,表里面最后一个 Esc ,前面怎么是个二位数? 哦,你观察的倒真仔细。是这样的, Esc还有 BackSpace,它们与 PageDown 有所 不同。那就是 Esc和 BackSpace它们都是 ASCII字符键,也就是说它们在 ASCII码表中 可以找到对应的编码,正是表中所列的 27 和 8。而这个数字同时也是它们的扩展编码 只有一串数字,没有0 或者 224打头,这就意味着,它们在独立地在 ASCII码表中占用 两个位置,也就没有可能冒充其他的字符。 同时,因为他们在命令行和各种编辑器中都被赋予了特殊的含义(命令行中 Esc用 来重写命令, BackSpace用来向前删除字符),所以就无法通过按键直接输入字符来指 定候选键。但是,实际上 choice.com 是可以接受 27 和 8这样的编码的,所以我们只 要想办法将这两个键变成字符输进命令行就可以完成任务了。 唔,那么怎么把它们输进去呢? 这就要请出我们命令行工具的不老悍将—— edit 了。 edit,就是那个DOS 下面打字的软件吗? 对,准确的说, edit 是一个文本编辑器;它有个很实用的功能,就是用 Ctrl+P 输入特殊字符。比如,我们要把 Esc当作字符输进去,就可以先按下Ctrl+P,放开后再 按Esc ,这时一个向左的小箭头就会出现了,它就是Esc 在 ASCII码表中对应的字符; 同样,按下 Ctrl+P 、 BackSpace后就会出现一个带圆孔的长方块,那就是 BackSpace 对应的字符了。 输进去以后怎么办呢? 这样,我们可以把上面的 choice.com 语句写进批处理程序中,然后将 /c 后面的 候选字符换成我们刚刚学会输入的那两个字符,就像下面这样: @choice /n/s/c: Press [Esc] or [BackSpace] to quit: 将上面这个句子保存为批处理程序,比如“Esc-BkSp.bat”。然后,在命令行中执 行它。然后按 Esc 和 BackSpace 试试看。 唉,终于成功了!好累啊,不过也蛮有成功感的。 嗯,到这里,我们的第一部曲也该就此结束了。现在,我们应该可以解决一些以前 用 choice.com 时无法解决的问题。但是,仍然还有一些问题,比如我们一直没讲到回 车键怎么在 choice.com 中使用,它无论在命令行还是在批处理中都被当作了语句结束 的标志,因而始终无法作为候选字符使用,还有很多其它的一些类似的特殊按键(例如 Tab )因为同样的原因而无法使用。 而这些,都将留在第二部曲“瞒天过海”中探讨和解决。 最后,作为本节内容的附赠品,一篇详细标明更多特殊按键扩展编码以及替代字符 的列表以附件形式列于其后,附件中还包括 EscBksp.bat和 PgDw.bat 用以测试是否支 持 [Esc]/[BackSpace]/[PageDown] 等特殊的按键。在 EscBksp.bat中使用了自动获取 [Esc]/[BackSpace]这两个特殊按键对应字符的方法。 ------------------------------------------------------------------------------ 编后语: 其实,就我自己而言,很不习惯也很不喜欢写这样风格的文字,短短的一篇技术短 文硬是被拖拽成了又长又臭的搞笑散文 :< 没办法,为了照顾大多数初学者,只好牺牲 一下我的个人品味了 :) 不过,在后两节文字中,随着技术含量的增加,同时也是为了 照顾我的个人情绪,我将会适当的提高文章的理解水平底线,低于此线的朋友们,在此 先说声抱歉了。 ============================================================================== =========================== 三部曲之二:瞒天过海 Will Sort - 2005/04/24 Updated:2005/04/30 =========================== ============================================================================== 在上节中我们留下一个题目,那就是如何让 choice.com 接受并正确处理回车键以 及其他一些系统保留的特殊键。我的答案是键盘重定义,就是将键盘上的特殊键重定义 为我们可以利用的常规键,比如字母或者数字。 这需要使用从 MS-DOS 时代起便出现的一个设备驱动程序 ANSI.SYS 。它是为了支 持美国国家标准学会(ANSI)所制订的一套关于键盘和屏幕控制的标准而设计的,具有 相当丰富的键盘和屏幕控制功能,详细内容可以参阅附件中的文档 ANSI_SYS.TXT 。利 用它所提供的重定义键(实际上是重定义键的扩展编码)的功能,可以将回车重定义为 字母‘y’,从而实现我们的初衷。但是要使用这个功能,我们需要预先做一些准备。 在 MS-DOS 和 Win98 中准备使用 ANSI.SYS -------------------------------------- 在 MS-DOS 和 Win98的命令行环境中,我们使用 ANSI.SYS 需要通过一个启动配置 文件来加载它,那就是 config.sys ,它是一个纯文本文件,通常位于系统所在盘的根 目录,可以用“记事本”或者上节提到的“edit”来编辑它。我们可以在这个文件的最 后添加一行如下的文字: device=c:\Windows\Command\ANSI.SYS /x 其中的 C:\Windows\Command 可能需要替换成 ANSI.SYS 文件实际所在的目录。比 如你是 MS-DOS 的用户,那么 ANSI.SYS 很可能在 C:\DOS 下。 编辑完成后,重新启动计算机就可以在 MS-DOS 或者 Win 98 的命令行环境下使用 ANSI.SYS 所提供的各种功能特性了。 在 Win2K 和 WinXP 中准备使用 ANSI.SYS ------------------------------------- 在 Win2K和 WinXP的命令行环境中,已不再使用 config.sys ,但是可以在系统路 径下的 System32 目录中找到一个替代品,那就是 config.nt,它用于配置 MS-DOS 模 拟环境(command.com)。它也是纯文本文件,所以也可以用记事本打开并编辑它。我 们需要给它加上与 MS-DOS 和 Win9x 系统相类似的文字: device=%SystemRoot%\system32\ANSI.SYS /x 其中的 %SystemRoot% 是个全局的环境变量,我们可以直接使用它引用系统路径, 而不需要人为地修改它。 与 MS-DOS 和 Win9x 的命令行环境不同的是,我们还需要修改 config.nt 文件中 的一个细节,那就是将“REM DOSONLY”一行中的“REM”去除。这样做的原因是,ANSI 是一个基于 MS-DOS 的终止并驻留(TSR)内存的程序,它需要一个纯粹的运行 MS-DOS 程序的环境,否则就会无法正常的使用。而 DOSONLY正是创建这个环境的必要开关,所 以我们去掉它前面的注释命令,以使它在 config.sys 中生效。 所有的设置修改完成后,就可以立即在运行中输入 “command”(不是 CMD),此 时有 ANSI 支持的命令行环境就被启动了。 开始体验 ANSI.SYS ----------------- 准备了这么久,我们终于可以使用 ANSI.SYS 带给我们的特性了,请将下面的代码 拷贝并保存到批处理文件中。 @echo off echo [13;'y'p-------- Start ------------- choice Press Enter or Y to quit: /c:y /n echo [13;13p--------- End -------------- pause 然后在我们刚才启动的有 ANSI 支持的命令行环境下,执行这个批处理,看到提示 后我们按回车键:此时,程序运行结束,并在提示语后出现一个大写字母 'Y'。 我们分析一下程序,第一行是将命令回显关闭,第二行就用了 ANSI 的重定义键功 能, echo 后面的向左箭头就是上节中提到的 Esc 键的 ASCII 字符,它和'[' 联合使 用,标志着随后的序列开始使用 ANSI 的功能,它们不会被 echo ,而会被 ANSI 发现 并转变其含义,因此这个序列被称为转义序列,Esc 也被称为转义字符。第三行是供我 们测试的 choice 语句,其中仅仅定义了一个候选字符‘y’;第四行是恢复回车原来 的定义;第五行实现暂停。 而转义序列“13;'y'p”的意思,我们应该可以猜得出来,那就是将回车重定义为 字母‘y',回车的表示正是上节中提到的扩展编码,而字母‘y'被单引号括了起来,表 示它是一个 ASCII 字符,而非字符的编码;至于紧跟其后的字母‘p’正是重定义键的 功能标志。而后面的 -Start- 并不是转义序列的一部分,将会原样输出。 我们也可以实现更多地重定义按键,比如制表符键、光标键、删除键、回删键等, 详细的代码请参阅附件中的 Remap2.bat ,我们可以在命令行中执行它,然后测试一 下我们的按键和输出结果是否一一匹配。 如何在我需要时才使用 ANSI.SYS ----------------------------- 以上对 config.sys 和 config.nt 的修改将会使以后运行所有的 MS-DOS 程序都 可以享用 ANSI.SYS 所带来的特性,但同时也会始终占用命令行环境下约 4K 的内存空 间。我们可能并不想这样,因为我们并不经常需要 ANSI.SYS 的支持,当我们不需要它 时,总是不希望它浪费我们宝贵的内存空间。 要解决这个问题,我们可以在系统启动以后再加载它。在 MS-DOS 环境中,当需要 使用 ANSI.SYS 的时候,一般使用 device.exe 或者其他有相似功能的第三方程序在命 令行加载 ANSI.SYS ,不需要的时候也同样可以卸载它。 在 Win9x的命令行环境中,我们多了一种选择,那就是为需要使用 ANSI.SYS 的程 序编辑自己专属的启动配置文件:在程序的右键“属性”菜单中,选择“程序->高级-> MS-DOS方式->指定新的 MS-DOS 配置”,然后在 config.sys 的对话框中添加 device 语句即可。但是这个方法的缺点是启动和退出这个程序时,都会重新启动系统以进入新 的命令行环境。而在WinXP 中,没有了 Win9x的缺点,我们可以为需要使用 ANSI.SYS 的程序创建一个指向 MS-DOS 程序的快捷方式(.PIF文件):在程序所在文件夹的空白 处点击右键,选择“新建->快捷方式”,在文本框中填入 command,然后一路回车,在 出现“MS-DOS方式”之后,在右键“属性”菜单中,修改程序名为 Remap2.bat ,修改 启动配置文件为我们自己制作的启动配置文件,比如附件中的 Remap_XP.PIF 使用的便 是 %SystemRoot%\System32 下的 ANSI_SYS.NT(对应CONFIG.NT)和 ANSI_BAT.NT(对 应AUTOEXEC.BAT),它们可以在附件中 ANSI_XP 文件夹中找到。 这一节我们讲述了如何通过 ANSI 的重定义键功能来实现 CHOICE 接受任意按键, 除了极个别的按键之外(如 Ctrl+Break 或者 Ctrl+Alt+Del ),我们已经可以处理各 种常用的特殊键。下一节,我们将涉及 choice 的另一个应用需求,那就是默认按键的 优化。 ------------------------------------------------------------------------------ 编后语: 可能对于很多人来说,本节是比较枯燥的一节,因为它的文字量远远大于代码量, 以至于让很多兄弟少了很多实践锻炼的素材,也许将 Remap2 贴上是个办法,但我并不 愿意如此做。下一节,这种情况就会完全反过来了,请大家也有所准备。 另外,本节的内容很久前就筹划好了,大概是在回复了“联合 DOS论坛”的一位站 友关于 choice.com 接受回车键的问题帖之后。只是后来觉得这种方法有很多的缺陷, 我们必须预先配置命令行环境,然后才能在这个环境中运行相应的程序;而对于 XP 等 NT类环境来说,这种配置无法全局有效,对 config.nt的修改只能影响 command.com, 而无法影响直接点击运行的批处理程序,因为它们默认是启用 cmd作为命令解释器的。 直到不久前发现 XP 中也可以借助 .PIF 文件进行启动配置,我才下定决心将此节继续 完成并公布于众。 因为花了很多时间编写并测试一些代码实例,以及其他一些无关的工作,所以本节 出得稍微晚了一些。如果没有意外,第三节可能会快一些,因为我将不会做太多的文字 说明。 ============================================================================== =========================== 三部曲之三:借花献佛 Will Sort - 2005/05/03 Updated:2005/05/15 =========================== ============================================================================== 在上一节中,我们基本完成了 choice 接受任意按键的任务,现在我们将目标定位 在 choice 的其他功能扩展上。而探讨的焦点就在 choice 的缺省按键功能上。 当我们用 /t 开关指定了缺省按键后,经常会有一个问题,那就是一旦按下了某个 候选字符未指定的按键,就会终止缺省按键的倒计时。这是程序固有的细节,我们很难 通过外部的办法来改变它,于是,我们所能使用的最佳方法就是从内部修改它。 修改一个可执行程序,并不是一件十分神秘高深的事情,虽然它有可能是一件枯燥 乏味的事情:)但是只要选对合适的工具,这项工作也将会进行的顺利和简单。可执行程 序是一个二进制文件,在 Windows下有很多人选择 UltraEdit或者 WinHex 来修改它, 但是,在这个案例中,为了兼顾 MS-DOS 的使用者,同时也考虑到修改过程的自动化, 我决定使用 DOS平台下的程序调试器—— DEBUG.EXE。 作为 DOS下的调试工具, DEBUG.EXE有着相当广泛的用途。一方面它是一个二进制 文件编辑器,另一方面它又是一个汇编与反汇编器,同时也担当着用户与底层硬件的交 互平台,特别是在早期的逆向工程领域中,它扮演着举足轻重的角色,因此有着“屠龙 宝刀”的诨号,我们在这里使用它来扩展 choice 的使用功能,只能说是牛刀小试。关 于它子命令的用法说明,可以在网络上找到许多相当优秀的教程文章。 修改程序之前,我们首先需要熟悉程序的流程,尤其是修改处的细节流程。我们运 用 debug的 d/u/g/t/p/ 等命令可以了解到 choice.com 的流程大致如下: ------------------------------------------------------------------------------ CS:0100 跳至0211程序区 jump 0211 program area CS:0103 数据区 date area CS:0211 检查系统版本号 Verify MS-DOS version is 4.0 or later CS:0236 获取大写字母表 Get Upper Case Table address/bias CS:0262 分析命令行 Parse command line CS:03C7 转换小写字母 convert choices and default to Upper Case CS:03E9 检查缺省选择 verify specified default is in Choices CS:0401 显示选择提示 Display prompt CS:0442 等待按键或倒计时结束 wait for timeout or keypress CS:0476 接受按键 get key CS:04A4 显示按键并设置错误码后退出 got key, set errorlevel, quit ------------------------------------------------------------------------------ 进而分析我们关心的倒计时终止部分: ------------------------------------------------------------------------------ CS:0442 计时秒数若为0 则跳至0476等待按键,否则取当前秒数后继续 CS:0451 如果有案件则跳至0476,否则继续 CS:045D 如当前秒数变化,则计时秒数减一后继续,否则跳至0451循环 CS:046B 若计时秒数为0 则模拟按下缺省键并跳至0485,否则跳至0451循环 CS:0476 接收按键 CS:0485 搜索按键是否候选,如果是跳至04A4,如果否跳至049C CS:049C 响铃后跳至0476(* 注意此处 *) CS:04A4 显示并设置按键后退出 ------------------------------------------------------------------------------ 从上面的分析可以看出,问题的关键在于 049C--04A4 之间的错误按键处理段。在 此处,程序响铃(即输出字符 07 )后,跳到了 0476 继续接收按键,从此不再计时。 解决的办法很简单,只需要将这个跳转地址改为 0451/045D的任意一处即可。通过 u命 令反汇编,确定这个跳转命令的地址 04A2 ,这样我们只要通过 a 4a2将汇编代码 jmp 476 替换为 jmp 451即可。 此外,我们还可以考虑其它的一些功能上的扩展。比如无效按键不能终止倒计时, 在用户需要谨慎选择的时候,就需要一个特有按键(比如 ESC)停止倒计时;又比如, 按下一个常用键(比如回车或者空格),可以直接选择任意设定的缺省按键。 这些功能的实现都不是很复杂,但前提是操作者要有一定的汇编编程和程序调试经 验。当然,谁也不可能要求所有的 DOS用户都能熟悉甚至了解 DEBUG子命令和 8086 代 码集。所以对于大多数人,本文提供了更为简洁的操作方式——批处理。这个程序可以 在附件的 X3 文件夹下见到,名为 ModChc.bat ,点击它即可以自动地根据可以找到的 choice.com生成一个新的 choicex.com。这个新的程序也可以在附件中的同样位置直接 找到,它业已实现了我在上面所提到的三项扩展。 关于 xchoice.com更详细准确的描述如下: ------------------------------------------------------------------------------ 1.如果指定缺省选择键,键入无效选择键不会终止取缺省选择时的倒计时 2.如果指定缺省选择键,可通过键入回车或空格键终止计时并立即返回缺省选择键 3.如果指定缺省选择键,可通过键入跳出键终止计时并等待至键入有效选择键 4.指定缺省选择键时,设置等待时间为 0,可实现仅在按回车或空格时取缺省选择 键而不会限时。如:choice /c:qwert /t:w,0 5.当指定跳出、回车或者空格键为选择键时,其终止计时的功能将相应失效 ------------------------------------------------------------------------------ 如果程序附件的链接失效,或者你无法得到附件,那么可以根据下文提供的修改脚 本自己用 debug修改 choice.com ,命令格式如下: debug choice.com < choicex.asd > nul 注意:在修改前请自己备份原程序 ------------------------------------------------------------------------------ :: ChoiceX.asd - Choice 扩展 DEBUG 汇编脚本 :: Will Sort - 14:48 2005-5-15 - Debug :: Modifition: :: 1.Not terminates timeout when press invalid choice key :: 2.press ESC to terminate timeout :: 3.press CR or SPACE to choose default choice key a 047D JNZ 0482 ; Call 0A80 when press control key CALL 0A80 ; get second byte of scancode of control key CALL 0A85 ; process event of press ESC, CR, SPACE a 04A2 JMP 0451 ; Not terminate timeout when press invalid choice key a 0A80 MOV AH,08 ; get char again INT 21 RET CMP AL,1B ; if press ESC JZ 0A94 ; YES, terminate timeout CMP AL,0D ; if press CR JZ 0A91 ; Yes, goto set default choice CMP AL,20 ; if press SPACE JNZ 0A99 ; No, goto return MOV AL,[018A] ; set default choice MOV BYTE PTR [0189],00 ; set timeout is zero RET n ChoiceX.com w q :: Please reserved this line. ------------------------------------------------------------------------------ 编后语: 第三部曲的准备,其实很早前就开始了,只是中途不耐于繁琐的文档整理而罢手, 直到这次执笔编写应用扩展三部曲,才又将它从故纸堆里翻了出来,花了几天时间重新 整理文档,又删除了一些复杂而不实用的修改(比如用十六进制的 ASCII编码指定候选 按键),简化了许多代码,终于成了现在的模样。 至此,《 choice.com 应用扩展三部曲》已全部结束。回顾近一个月的编写历程, 仍不免留有一些遗憾,但已基本达成我的初衷,应该可以给自己有所交待了。希望它能 够对大家有所启发和帮助,也希望大家能对它提出自己宝贵的意见和建议,联系地址可 通过帖子上方的邮件功能找到。 最后,在此真诚地祝愿所有的读者健康、快乐! ============================================================================== 
|