DOS程序员手册(八)
备,就可以从程序中访问驱动程序。可以用句柄功能调用来
打开设备(见列表12.9)
列表12.9
/*example.C
Listing 12.9 of DOS Programmer's Reference*/
union REGS regs;
regs.h.ah=0x3d; /*Open-file function*/
regs.h.al=0x01; /*Write access*/
regs.x.dx=(int)"drvr";/*Creates pointer to string*/
intdos(&regs,&regs);/*Call DOS function int*/
handle=regs.x.ax;/*Save the file handle*/
不管选择哪种方式来访问驱动程序,都能根据其设计来测试其操作。在drvr中,能测
试它是否能向打印机打印字符以及是否能从字符流中消除换行。
12.7小 结
本章讨论了程序员们所认为的DOS编程中最困难的部分:创建驱动程序。遵循下列
驱动程序的标准就能创建设备驱动程序。
所有驱动程序都由3个基本部分组成:
335页
·设备头
·策略例程
·中断例程
每部分都有它自己的结构。设备头包括驱动程序名和在系统驱动程序链中下一个驱
动程序的指针。策略例程只记住系统请求头内存的地址以备驱动程序与操作系统之间通
信时使用。
大多数驱动程序都包含在构成中断例程的各个功能(有21个)之中。所给的驱动程序
都只能执行几个功能而忽略其它功能,如果向驱动程序提出这样的请求,就会返回一个合
适的完成码。
第13章其它杂项功能
本章集中讨论三种基本类型的功能:DOS信息功能、日期与时间功能以及扩展的错
误处理。前两类很简单,不需作多的处理。第三类功能对于DOS的错误处理能力极为有
用。因为这类功能与某个特定程序的关系太密切,所以本章没有介绍它的使用实例。
13.1 DOS版本信息
DOS 2.0版中加上了一项功能来允许用户获取DOS版本号。这种信息对于了解功能
是很重要的。2.0版以前的DOS版本返回0,所以小于2的返回值都表明是DOS 1.x版系
统。虽然不能区分1.0版和1.1版,但至少可以知道,最主要的区别点已找到了。那就是
现在所碰到的并不是一个功能完整的DOS。
如果以为探测DOS版本看起来并不那么重要的话,那么请记住,这世界上确实存在
多样性,许多人还未从1.0版或1.1版升级过。例如,有两个人在Seattle计算机商店想买
额外的硬件,以便把他们的带有两个磁盘的初始化IBM PC扩大成带有硬盘的系统。但它
们还在运行DOS1.1版,而此版本并不能运行他们要买的设备。他们只好带着自己的硬
件和DOS 3.2离开了商店。
在不同的DOS版本中要保持良好的兼容性,就必须限制只用DOS 1.0版中有效的
功能来编程—一个值得称赞但却是天真的目标。当前需要的大多数编程环境至少是
DOS 2.0版;在较早的版本中,像目录这样的功能都是不能用的。
我们建议以用下列方式之一来使用以DOS版本号:
1.检查正确的DOS版本,并告诉用户它是否已高得能支持所用的程序。
2.检查DOS版本,并进行必要的补偿。
第二种方式会产生可观的系统开销,除非把DOS特定代码限制成覆盖(每个DOS版
本都采用独立的覆盖)或者进行一次安装来为正使用的DOS版本补进正确的代码版本。
下列代码段检查能完成的最少功能(至少是DOS 2.0版)。该功能返回两个数值;AL
是DOS的主要版本号(02h)为DOS 2.0版,03h则是DOS 3.0版)。AH是DOS的次版本
号(10是.10,20为.20,依此类推)。如果在调用后需要BX和CX寄存器,那么就应保存
它们—否则中断会破坏它的。
Int 21h, Function 30h: Get DOS Version Number
mov ah,30h ;DOS version
int 21h
337页
cmp al,2 ;Check for greater than or equal to V2
jl wrong ; Wrong version
我们可建立(或用作自己的库函数)一个提供DOS版本号的C子例程。例如,若只想
检查DOS的最小版本,可用列表13.1中的chkver()子例程。
列表13.1
/* chkver.c
Listing 13.1 of DOS Programmer'S Reference*/
#include<stdio.h>
#include<stdlib.h>
#include<dos.h>
void main()
{
int ver;
unsigned int chkver(void);
if((ver=chkver())<3) {
printf("ERROR--Version MUST use at least DOS 3.0\n");
printf(" Yours is only version %d\n",ver);
exit(0);
}
printf("Thanks... You have version %d of DOS\n",ver);
}
unsigned int chkver()
{
union REGS regs;
regs.h.ah = 0x30;
intdos(&regs, &regs);
if(regs.h.al==0)regs.h.al=1;
return (regs.h.al);
}
因为编写该程序所采用的是这样的方式,所以可以把chkver()子例程拿出来放进子
例程库中,并与其它C程序一起使用。在程序的开始,一定要加上下面的语句:
#include<stdio.h>
以便把标准的I/O功能公布给子例程。
子例程只调用功能30h。尽管该功能返回版本的主版本号和次版本号(分别在AL和
AH寄存器中),但只有前者是很重要的。
列表13.2是同一个程序的BASIC版本。
列表13.2
'chkver.bas
$INCLUDE:' REGNAMES.INC'
DEF fnchkver
'Determine the DOS version from Int 21h, Function 30h
ToRegs,ax = &h3000
call interrupt (&h21 , ToRegs, FromRegs)
338页
if FromRegs.ax and &h00ff=0 then FromRegs.ax=&h0001
fnchkver = FromRegs. ax and &h00ff
END DEF
‘MAIN PROGRAM
' Use the check version function to print the
‘ system’s version number
Ver=fnchkver
PRINT" You are using DOS" ;ver
IF fnchkver<3THEN
PRINT " OOPS--You have an operating System version"
PRINT;" Earlier than 3.0.You need to upgrade. "
ENDIF
END
若想显示出DOS版本号,可用getversion()子例程,见列表13.3。
列表13.3
/* getver.C
Listing 13.3 of DOS Programmer'S Reference*/
#include<Stdio.h>
#include<dos.h>
char *getversion()
{
static char buffer[5];
union REGS regs;
regs.h.ah = 0x30;
intdos(&regs,&regs);
if(regs.h.al==0)regs.h.al=1;
sprintf(buffer,"%d.%d",regs.h.al,regs.h.ah);
return (buffer);
}
该子例程使用了DOS版本的主版本号和次版本号,它比chkver()子例程更复杂。要
使版本号能被打印,可建立静态字符缓冲区,在其中给以合适的格式编写版本字符串。小
心,静态缓冲区只有5个字符(最后一个必须是NULL字符)。该缓冲区直到DOS版本有
3个数字的次要版本号或2个数字的主要版本号时才停止工作;如果版本号太高,就必须
使缓冲区更大(注意在OS/2的DOS功能盒中运行的程序,目前返回DOS版本号为
10!)。
因为能返回一个指向静态字符缓冲区的指针,所以主例程只需打印返回值并且——
我们有了DOS版本。可将getversion()子例程包括进库之中并可在必要时把它用于其它
程序。
列表13.4是该子例程的BASIC版本。BASIC功能实质上与C例程相同,但(由于
BASIC工作的方式)看起来却并不相同。
列表13.4
' getver.bas
' $INCLUDE:' REGNAMES. INC'
339页
DEF fngetver
‘Determine the DOS version from Int 21h, Function 30h
‘ vn=Version number
‘ rn=revision number
ToRegs.ax=&H3000
CALL interrupt (&H21 , ToRegs , FromRegs)
IF FromRegs. ax AND &HFF= 0 THEN FromRegs. ax = &H1
vn=( FromRegs·ax AND &HFF)
rn=( FromRegs·ax AND &HFF00/256
fngetver = vn +rn/100
END DEF
‘MAIN PROGRAM
“Use the get version function to print the
' system' s version number
' NOTE: You must use PRINT USING to get the
‘ proper number of decimal places
CLS
PRINT USING" Version Number: #:##" ;fngetver
END
13.2设备信息
我们想知道在我们的系统中除DOS版本号以外还有哪些设备。BIOS Int 11h返回一
个代码到AX寄存器中,指示计算机中已安装的有哪些设备。具体见表13.1。
要确定计算机中的设备,只需调用Int 11h,然后检查中断返回码。列表13.5中的C
程序在子例程中使用了中断(但是注意:该程序所返回的视频信息并不足以确定正使用的
视频系统的确切类型;如果这对于程序很重要的话,它也只是一个起点,来消除某些可能
性)。
表13.1 BIOS Int 11h的返回码
位 意 义
0 安装了磁盘驱动器时设置(6.7位指示数目)
1 安装了数学协处理器时设置(AT独有)
2—3 内存配置(对于AT没有意义)
0=16K系统板RAM 1=32K系统板RAM
3=48K系统板RAM 4=64K系统板RAM
4—5 初始视频方式
1=40*25,文本,彩色
2=25*25,文本,彩色
3=25*25,文本,单色
6—7 磁盘驱动器数减去1(0位为1时有效)
340页
位 意义
8 未使用
9—11 RS232 端口的个数
12 安装了游戏适配卡时设置(PC机独有)
13 安装了内部调制解调器时设置(AT独有)
14—15 连接的打印机数
列表13.5
/* equip.c
Listing 13.5 of DOS Programmer's Reference */
#include <stdio.h>
#include <dos.h>
#define BOOL int
#define FALSE 0
#define TRUE !FALSE
#define EQUIPMENT 0x11
void main()
{
unsigned int eqpt;
unsigned int equipment(int print);
eqpt = equipment(TRUE);
printf("Equipment Value iS %x\n",eqpt);
}
unsigned int equipment(print)
BOOL print ;
{
union REGS regs;
int eqpt;
int86(EQUIPMENT, ®s, ®s);
if(print) {
eqpt = regs.x.ax;
if(eqpt & 0x01)
printf("Floppy Drives are attached\n");
if((eqpt>>1 ) & 0x01)
printf("Math Coprocessor installed (AT only) \n");
switch((eqpt>>4)&0x03){
Case 1:
printf("Initial video mode 40*25 color\n");
break;
case 2:
printf("Initial video mode 80*25 color\n");
break;
case 3:
printf("Initial video mode 80*25 mono\n");
break;
}
if((eqpt>>6) & 0x01)
printf("Number of disk drives is %d\n",
((eqpt>>6)&0x03)+1);
printf("Number of RS-232 ports iS %d\n,
(eqpt>>9)&0x07);
341页
if((eqpt>>12)&0x01)
printf("Game adapter installed\n");
if((eqpt>>13)&0x01)
printf("Internal modem installed (AT only)\n");
printf("Number of printers is %d\n",
(eqpt>>14)&0x03);
}
return (regs.x.ax);
}
equipment()函数能从这个程序中拿出来,并加到有用例程的函数库中。
注意,在中断调用中使用了EQUIPMENT定义。定义一些常量如EqUIPMENT
(0x11),就可以简单化程序的维护工作,并使之更易于阅读。
列表13.6显示了用BASIC编写的设备检测程序。
列表13.6
‘equip.bas
DECLARE SUB printeqpt (n%)
“$INCLUDE:' REGNAMES.INC' Get the installed equipment and interpret it
' with the subroutine printeqpt
CALL interrupt(&H11 , ToRegs, FromRegs)
eq% = FromRegs.ax
CLS
PRINT" System Equipment Installed"
PRINT“ Equipment code= ”;HEX$(eq%)
CALL printeqpt(eq%)
END
SUB printeqpt(n%)
“This procedure prints the installed equlpment list
given the equipment code number
IF n% AND &HI THEN PRINT“ Floppy drives attached”
IF n% AND &H2 THEN PRINT“Math coprocessor installed”
IF n% AND &H1 000 THEN PRINT "Game adapter installed"
vm=(n% AND &H30)/16
SELECT CASE vm
CASE1
PRINT " 40 * 25 text , color"
CASE2
PRINT" 80 * 25 text , Color"
CASE3
PRINT" 80*25text , mono"
END SELECT
IF n% AND&H1 THEN
dd = ( n% AND&HC0) /64+1
PRINT " Number of disk drives :" ; dd
ENDIF
rs=(n% AND&HE00)/512
PRINT“Number of RS- 232 ports :”;rs
END SUB
13.3日期和时间功能
在系统启动过程中,日期和时间被初始化成它们的默认值。如果系统没有内部硬件时
342页
钟(目前很少见),则默认日期为1/1/80,默认时间是00:00:00.00(午夜)。若有内部时
钟,就通过内部时钟的值来设置日期和时间。时间保存在BIOS数据区中,日期则保存在
COMMAND.COM中。如果没有内部时钟,系统的日期和时间可用DOS命令DATE和
TIME来重新设置。
在DOS V3的一些版本中,有个故障会阻止午夜变换时日期的改变。在早期版本中存
在这故障,后被排除,但又出现了。在MS-DOS 3.2版中,BIOS程序在被调用时向DOS返
回“通过午夜”的标记,但程序忽略了这个结果。
如果系统受到该故障的破坏,唯一可靠的解决办法是使用来自商业服务或BBS的公
用CLOCK$设备驱动程序,如CLKFIX.SYS。这些驱动程序能用改正的版本代替错误程
序,并把所有受影响的中断向量都指定到新的程序上。
但是,即使进行了改正,由于日期例程中的设计漏洞,会跳过完整的几天。例如,如果
让计算机在周未停止运行。然后超过24小时无人使用,这就包括了两次午夜的变换,结果
却只发现了一次变换。通过午夜是设置一个标记,而不是计数器;所以DOS在最后询问时
间之后,过了多个午夜时,DOS并不知道。这个故障没有简单的修改方法,最直接的方法
就是每个星期一都重新引导计算机,并设置正确的系统日期。
访问时间和日期的最好途径是使用为此目的而提供的DOS功能。在DOS 1.1版和
后来的版本中,利用系统日期功能获得日、月、年和星期几,如下所示:
Int 21h, Function 2Ah: Get System Date
mov ah , 2ah;Get date
int 21h
mov dow,al ;Day of week
mov mo , dh ;Month
mov dy,dl ;Day
mov yr,cx ; Year
返回值在下列范围之内:
日 1-31
月 1-12
年 1980-2099
星期 0-6(0=星期日,1=星期一,依此类推)
用DOS Int 21h的功能2Bh设置系统日期要用到这些范围。如果成功设置了日期,
DOS就返回AL=0,否则就返回FFh。
也可以在程序控制之下,使用下列DOS功能来设置系统日期:
Int 21h, Function 2Bh: set System Date
mov ah , 2bh ;Set date
mov cx,yr ;Year
mov dh,mo ;Month
mov dl ,dy ; Day
int 21h
343页
or al,al ;Test for invalid
jnz error ; Jump on error
除了为获得和设置系统日期而提供的功能以外,DOS还包括获得和设置系统时间的
功能。获得系统时间,DOS返回下列范围之内的值:
小时(CH) 0-23
分钟(CL) 0-59
秒(DH) 0-59
百分之一秒(DL) 0-99
由于一些计算机系统速度相对较低,所以真正的时间时钟可能没有精确的百分之一
秒的分辨率。在这些系统中,DL值不应该用于精确的或关键的计时。
执行下列DOS中断和功能,可获得系统时间:
Int 21h,Function 2Ch: Get System Time
mov ah,2ch ; Get time
int 21h
mov hr,ch ; Hour
mov mn,cl ; Minutes
mov sc,dh ;Seconds
mov hn,dl ; Hundredths of seconds
设置时间的范围限制与获得时间的范围限制是一样的。要设置时间,可用DOS Int
21h的功能2Dh。如果成功地设置了时间,系统就返回AL=0,否则返回AL=FFh。
Int 21h, Function 2Dh: Set System Time
mov ah, 2dh ;Set Time
mov ch,hr ; Hour
mov cl,mn ; Minutes
mov dx,0 ;Seconds=0
int 21h
or al,al ;Error?
Jnz error
列表13.7包含了获得时间和日期的一套C函数。 cdate()和ctime()子例程能从该表
中拿出,加到函数库中。每个子例程都返回字符串指针,指向该函数内部的静态缓冲区,并
将所得到的日期或时间以合适的格式放在该缓冲区中。
列表13.7
/* DateTime.C
Listing 13.7 of DOS Programmer'S Reference*/
#include <stdio.h>
#include <dOs.h>
void main()
344页
{
Char*cdate(void);
char*ctime(void);
printf("Date: %s Time: %s\n",cdate() , ctime());
}
char*cdate()
{
static char buffer[9];
union REGS regs;
regs.h.ah = 0x2a;
intdos(&regs,&regs);
sprintf(buffer,"%02.2d/%02.2d/%02.2d",
regs.h.dh, regs.h.dl, regs.x.cx-1900);
return (buffer);
}
char*ctime()
{
Static char buffer[9];
union REGS regs;
regs.h.ah = 0x2c;
intdos(&regs,&regs);
sprintf(buffer, "%02.2d:%02.2d:%02.2d",
regs.h.ch, regs.h.cl, regs.h.dh);
return (buffer);
}
将各个函数放在一起来保持日期或时间的字符串形式,直到用户能使用该字符串。这
样的静态变量能长期分配并占用大量不常使用的内存。因此要注意不要使用太多这样的
变量。
列表13.8是Pascal程序clock.pas,它是利用日期和时间类函数的另一个实例。该程
序显示屏幕时钟,直到我们按下Escape键(ESC)。
列表13.8
{ clock.pas}
{Turbo Pascal 4.0 or greater. For 3.0,omit next}
{ code line and declare the Registers record type. }
uses crt , Dos;
const cr:char=^M;
var hour,min,sec,month,day,year: byte;
Ch : char;
Procedure get_time( var hr,mi , se : byte) ;
var
I: Integer;
Regs:Registers ;
begin { get_time}
with Regs Do
345页
begin
AH :=$2c;
Flags:=0;
MsDos(Regs); {execute software interrupt}
hr:=SCH;
mi : =CL;
Se:= DH;
end ; { with Regs}
end; {end get_time}
Procedure get_date(var mo, da, yr: byte) ;
Var
I : Integer;
Regs : Registers;
begin { get_ date}
with Regs Do
begin
AH :=$2A ;
Flags:= 0;
MSDos(Dos·Registers(Regs) ) ; { execute software interrupt}
yr:= (CX mod 100) ;
da:=DL;
mo: =DH ;
end; { with Regs}
end: { end get_date}
procedure Pint_time(hr,mi,se,mo,dy,yr : byte) ;
begin { procedure Print_time}
Write(mo:2,'/',dy:2,'/' ,yr: 2);
Write(‘’,hr:2, ' :' , mi:2,' :' ,se:2);
write(cr);
end; {end print_time}
begin { Main Routine}
repeat
get_time(hour,min,sec);
get_date(month,day,year);
print_time(hour,min,sec,month,day,year);
delay(10) ;
until keypressed;
{ flush the input buf er}
while keypressed do ch := Peadkey;
{ for version 3.0, replace preceding line with }
{ while keypressed do read( kbd,ch) ; }
end. { end Main Routine }
clock. pas是个简单程序。其主要部分在结尾,即位于begin和end对之间标记为Main
Routine(主例程)的地方。该主例程建立一个连续循环(repeat-until),它获得日期和时间并
且每10秒钟打印它们,直到按下一个键(无论何时按键,keyprssed都会设置成TRUE,以
便Turbo Pascal能识别它)时钟循环结束后,只要keypressed保持TRUE,程序就通过读
字符来清除输入缓冲区中的任何击键。然后程序就结束了。
13.4扩展的出错处理
DOS3.0版本所带来的一个功能是,能确定扩展的出错信息。该信息提供了有关刚发
346页
生(在DOS服务调用之后)的错误的详细情况及建议纠正错误的方法。尽管这个功能在汇
编语言水平上很有用,我们还是可以用来自高级语言的例程来获得所建议的恢复行为。
如果正用汇编语言进行工作,那么应该在调用功能59h之前保存所有重要的寄存器,
因为这个特殊功能在处理过程中会破坏大多数寄存器(若用高级语言进行工作,则不必
保存寄存器)。
下面的一段程序先保存寄存器,然后获得返回的扩展的出错信息:
Int 21h,Function 59h: Get Extended Error information
push ax ; Save registers before call
push bx
push cx
push dx
push si
push di
push bp
push ds
push es
mov ah, 59h ; Extended error info
mov bx,0
int 21h
该例程返回下列代码:
AX=扩展的出错代码
BH=出错分类
BL=建议的行为
CH=出错场所
这些代码的意思详细见后面的表。表中对这些代码的含义都进行了很好的注释,对它
们进行处理的方式决定于程序以及产生问题的调用状态。
表13.2列举了各种出错代码,它们带有原始的出错指示——换句话说,就是“发生了
什么?”这类出错指示对于那些习惯于用操作系统调用来进行工作的程序员是很熟悉的。
信息性消息(可能应该叫它“半信息性”)会为用户提供一些情况,但对我们并没有大多帮
助,除非只有一个地方可能产生错误。
如果一个错误有多个可能的原因,那么这种信息性消息就很难有什么帮助。在大多数
情况下,如果能预测错误就应该在第一个位置上围绕它编程。
表13.2返回到AX中的扩展出错码
代码 意 义
1 无效功能
2 文件未找到
3 路径未找到
4 没有有效句柄
5 访问被禁止
6 无效句柄
7 内存控制块
8 内存不够
347页
代码 意 义
9 无效的内存块地址
10 无效环境
11 无效格式
12 无效访问码
13 无效数据
14 保留
15 无效驱动器
16 试图移动当前目录
17 不是同一个驱动器
18 无更多的文件
19 磁盘写保护
20 未知单元
21 驱动器未准备好
22 未知命令
23 CRC错误
24 错误的请求结构长度
25 寻道错误
26 未知的介质类型
27 未找到扇区
28 缺纸
29 写错误
30 读错误
31 一般性失败
32 共享违反
33 锁定违反
34 无效磁盘改变
35 无效FCB
36 共享缓冲区溢出
37 代码页不匹配
38 出错处理EOF
39 句柄磁盘满
40-49 保留
50 不支持网络请求
51 不接收远程计算机
52 复制网络名
53 未找到网络名
54 网络忙碌
55 网络设备不存在
56 超出Net BIOS
57 网络适配卡错误
58 不正确的网络应答
59 意外的网络错误
60 不兼容的远程适配卡
61 打印队列已满
62 无足够空间用于打印文件
63 打印文件已被删除
64 网络名被删除
65 访问禁止
66 网络设备类型不正确
67 未找到网络名
68 超出网络名限制
69 超出Net BIOS过程限制
70 暂停
71 不接收网络请求
72 暂停打印或磁盘重定向
73-79 保留
80 文件已存在
81 保留
82 不能创建目录项
83 在Int 24上失败
84 太多的重定向
85 复制重定向
86 无效口令
87 无效参数
88 网络数据错误
表13.3中的出错分类码则进一步扩展了出错码本身的含义。出错分类码是在操作系
统内部知识的基础上对错误进行分类的。
在大多数系统中都会发生操作系统错误,由于有很多的原因,所以如果程序想让用户
不受这些问题的困扰,就必须在它的出错处理过程中非常认真和细致。当富有经验的程序
员们碰到了一个怎么努力也对付不了的问题时,他们会认为“这个问题肯定是一个操作系
统故障”。 DOS试图告诉我们是否有这样的错误——但你相信DOS会承认它自己的错误
348页
吗?当运行的程序要作为商业软件来使用时,如果获得了一个不能用软件进行改正的错误
的指示,并且能肯定该程序清楚地告知了错误的来源,那么我们可以把自己的支持变得最
小。若能指出硬件失败之处,那么用户服务组织会感谢你,因为它不能进行任何调用。
表13.3返回到BH中的出错码分类
代码 意 义
1 资源不足
2 暂时情况
3 特许
4 内部的
5 硬件失败
6 系统失败
7 应用程序错误
8 未找到
9 错误格式
10
11 介质
12 已存在
13 未知
大多数经验丰富的程序员在发生某些错误时都想知道要做什么事情。表13.4中的建
议行为代码是一种帮助,但并非完全的解决办法。 DOS设计者给程序员提供了一些建议,
从而把设计者的一些有关系统的知识用在程序员的问题之中。我们可以在行为代码的基
础上采取合乎情理的行为。
表13.4在BL是返回的建议的代码
行为代码 意 义
1 再试;如果未清除掉合理的尝试次数,则提示用户放弃(Abort)或忽略(Ignore)。
2 等一会,然后再试;如果未清除掉合理的尝试次数,则提示用户选择Abort或Ignore
3 从用户处得到改正的信息(错误的文件名或磁盘驱动器)
4 退出并清除此应用程序
5 退出但不清除此应用程序(消除可能会增加问题)。
6 忽略错误
7 提示用户改正错误,然后再试
涉及用户的出错,特别容易陷入出错改正的困难之中,尤其当用户不懂出错信息和提
示(如Abort或Ignore出错)时。所建议的行为并不能减去程序员的责任,要编写尽可能对
用户友好的程序。
当找到了出错的场所时,应将行为代码用作发现出错的基础。大多数情况下,只有一
种或两种建议的行为有意义——可以忽视其它的建议行为。但要小心——发生出错(不能
用软件来改正的出错)时要采取得体的途径来退出程序。如果用户必须重新引导系统来退
出错改正循环,那么这个出错就不能改正了。要包含一个替换值来允许用户退出。
表13.5中的出错场所代码扩大了有关出错的信息,并试图告诉我们出错起源的一些
情况——换句话说就是哪个设备导致了这个出错(可以从出错起源来确定DOS区域,因
为进入DOS代码的每个功能性区域时,DOS就在内部保存了这些值。用这种方式,一个
公用的出错探测例程就能用不同的场所代码来处理错误)。
349页
表13.5返回到AH中的出错场所代码
代码 意 义
1 未知
2 块设备(磁盘或磁盘模拟器)
3 网络
4 串行设备
5 相关内存
在单用户系统中,出错场所代码信息是非常有用的,因为大多数情况下,初始出错码
已经告诉我们导致出错的原因。这类出错信息在涉及定向及与设备有关的内在可能性的
情况下是非常有用的。
例如要编写一个用标准输入和输出设备进行工作的程序,用户可将输出重定向于磁
盘驱动器、网络或RS-232端口,而不会让程序警惕这些变化。出错场所代码会提供钥匙
来中断并改正出错。
一些程序员已报告说,如果使用DOS 3.0版的字符设备(而不是块设备)上发生出错
以后调用扩展的出错功能,该功能会返回不合适的信息。对DOS 4.0中的真实代码的研
究表明,所返回的四项中的三项(分类行为及场所)都设置在到其它COS功能的入口上,
以便稍后若出现出错就使用它们,而没有出错时,从不明显地把它返回成0。并不是每个
功能都设置所有的项;有些功能只设置一个或两个项。
剩下的那一项,即扩展的出错代码本身,由来自旧出错代码的直接查表过程进行映
射,但只在找到一个出错时才这样做。该项在每个DOS功能开始时都设置成0以指示还
没有发生出错。
调用扩展出错功能时,它很少获得以前保存的4个值,也很少把它们返回给调用者。
这些值未执行分析。于是对该功能的调用,可能会返回一个由DOS功能所保存的值,它与
出错发生时的那个值不同。如果这样,这就会是DOS中的一个大故障,除了一些报道说这
可能应归因于调用程序中的错误外,还没报道这种能证实的故障。如果发现结果不稳定,
那么还是要小心别人已经报道了有问题的地方,与你所遇到的是否相同。
13.5未公开的功能
如果不涉及Microsoft和IBM在正式的参考手册中标记成“保留”的那些“未公开的
功能”,那么有关DOS杂项功能的讨论不会是完整的。从一开始,这些类型的功能就是
DOS的重要部分。专家告诉我们要避开这些功能,因为它们从未保证在不同版本之间保
持不变或存在于DOS的任何特定的OEM版本中。
这些功能所覆盖的范围很广泛。其中有一些与DOS内部的技术密切相关,以致到了
DOS外部就毫无作用。有些功能则为那些还没有完全实现的事情提供了接口。有一部分
功能则处于半公开的状态,如前面我们讨论的那样,有些是与TSR编程有关的。
本书的第五部分的参考手册中,列出了这些未公开的区域里已探明的部分。在每一个
功能里,只是不明确的说明处理表明此功能的行为可能不一致,甚至是在你的机器上是不
可用的。出现这种情况,在DOS2.0版上的可能性比3.0版要大得多,因为自3.0版开始
350页
引入了网络功能,并且Microsoft自此版本开始修改了原OEM版本中许多特定之处(包
括许多标明为“保留的”功能),其目的就是为了支持网络。
但是,要注意使用这些功能还是要冒风险的。尽管有大量的专业人士多年来已对这些
功能进行过细致的研究,但处在IBM或Microsoft之外的人,很难知道他们究竟想于什
么。这意味着,使用这些未公开的功能时,总是有些负面影响,只不过有些负面影响你已知
道怎么处理,问题是并不是什么都清楚。
因此,如果拿这些功能去做试验是要冒风险的。如果遵循前面介绍的用于测试设备驱
动程序的规则,可让错误减少到最小程度。这就是:限制程序的行为,并在出错时,保留好
正确的备份。
我们在本章不打算提供使用这些未公开的功能的使用例子,而将它们放到了本书的
附录D中。读者完全可以使用在那里介绍的程序去完成DOS内部的探测工作。
13.6小 结
本章讨论了一组不适于进行专门分类介绍的特殊功能:DOS版本功能、BIOS设备配
置功能、DOS日期和时间功能以及DOS出错处理功能。所有这些标准功能都是对个人功
能库的有益补充。对这些功能,我们还分别给出了实例例程(用C、BASIC及Pascal),告诉
读者如何去利用它们。
最后,本章扼要地介绍了所有正式参考手册中标明为“保留”的功能。到此为止,我们
便可以利用本书后面的知识,并用它们来设计和构造我们自己的程序。
(未完待续)
不要想你能为世界做什么,想想你该为世界做什么!