DOS程序员手册(十四)
附录A ASCII字符集
十进制 十六进制 二进制 AscII 控制 按键
X10 X16 X2 字符
000 00 0000 0000 null NUL ^@
001 01 0000 0001 SOH ^A
002 02 0000 0010 STX ^B
003 03 0000 0011 ETX ^C
004 04 0000 0100 EOT ^D
005 05 0000 0101 ENQ ^E
006 06 0000 0110 ACK ^F
007 07 0000 0111 BEL ^G
008 08 0000 1000 BS ^H
009 09 0000 1001 HT ^I
010 0A 0000 1010 LF ^J
011 0B 0000 1011 VT ^K
012 0C 0000 1100 FF ^L
013 00 0000 1101 CR ^M
014 0E 0000 1110 SO ^N
015 0F 0000 1111 SI ^O
016 10 0001 0000 DLE ^P
017 11 0001 0001 DC1 ^Q
018 12 0001 0010 DC2 ^R
019 13 0001 0011 !! DC3 ^S
020 14 0001 0100 DC4 ^T
021 15 0001 0101 NAK ^U
022 16 0001 0110 - SYN ^V
023 17 0001 0111 ETB ^W
024 18 0001 1000 CAN ^X
025 19 0001 1001 EM ^Y
026 1A 0001 1010 SUB ^Z
027 1B 0001 1011 ESC ^[
028 1C 0001 1100 FS FS ^\
029 1D 0001 1101 GS GS ^]
800页
十进制 十六进制 二进制 ASCII 控制 按键
X10 X16 X2 字符
030 1E 0001 1110 RS
031 1F 0001 1111 US
032 20 0010 0000 SP
033 21 0010 0001 !
034 22 0010 0010 "
035 23 0010 0011 #
036 24 0010 0100 $
037 25 0010 0101 %
038 26 0010 0110 &
039 27 0010 0111 '
040 28 0010 1000 (
041 29 0010 1001 )
042 2A 0010 1010 *
043 2B 0010 1011 +
044 2C 0010 1100 ,
045 2D 0010 1101 -
046 2E 0010 1110 .
047 2F 0010 1111 /
048 30 0011 0000 0
049 31 0011 0001 1
050 32 0011 0010 2
051 33 0011 0011 3
052 34 0011 0100 4
053 35 0011 0101 5
054 36 0011 0110 6
055 37 0011 0111 7
056 38 0011 1000 8
057 39 0011 1001 9
058 3A 0011 1010 :
059 3B 0011 1011 ;
060 3C 0011 1100 <
061 3D 0011 1101 =
062 3E 0011 1110 >
063 3F 0011 1111 ?
064 40 0100 0000 @
065 41 0100 0001 A
066 42 0100 0010 B
067 43 0100 0011 C
068 44 0100 0100 D
801页
十进制 十六进制 二进制 ASCII 控制 按键
X10 X16 X2 字符
069 45 0100 0101 E
070 46 0100 0110 F
071 47 0100 0111 G
072 48 0100 1000 H
073 49 0100 1001 I
074 4A 0100 1010 J
075 4B 0100 1011 K
076 4C 0100 1100 L
077 4D 0100 1101 M
078 4E 0100 1110 N
079 4F 0100 1111 O
080 50 0101 0000 P
081 51 0101 0001 Q
082 52 0101 0010 R
088 53 0101 0011 S
084 54 0101 0100 T
085 55 0101 0101 U
086 56 0101 0110 V
087 57 0101 0111 W
088 58 0101 1000 X
089 59 0101 1001 Y
090 5A 0101 1010 Z
091 5B 0101 1011 [
092 5C 0101 1100 \
093 5D 0101 1101 ]
094 5E 0101 1110 ^
095 5F 0101 1111 _
096 60 0110 0000 `
097 61 0110 0001 a
098 62 0110 0010 b
099 63 0110 0011 c
100 64 0110 0100 6
101 65 0110 0101 e
102 66 0110 0110 f
103 67 0110 0111 g
104 68 0110 1000 h
105 69 0110 1001 i
106 6A 0110 1010 j
802页
十进制 十六进制 二进制 ASCII 控制 按键
X10 X16 X2 字符
107 6B 0110 1011 k
108 6C 0110 1100 l
109 6D 0110 1101 m
110 6E 0110 1110 n
111 6F 0110 1111 o
112 70 0111 0000 p
113 71 0111 0001 q
114 72 0111 0010 r
115 73 0111 0011 s
116 74 0111 0100 t
117 75 0111 0101 u
118 76 0111 0110 v
119 77 0111 0111 w
120 78 0111 1000 x
121 79 0111 1001 V
122 7A 0111 1010 Z
123 7B 0111 1011 {
124 7C 0111 1100 |
125 7D 0111 1101 }
126 7E 0111 1110 ~
127 7F 0111 1111 DEL
128 80 1000 0000
129 81 1000 0001
130 82 1000 0010
131 83 1000 0011
132 84 1000 0100
133 85 1000 0101
134 86 1000 0110
135 87 1000 0111
136 88 1000 1000
137 89 1000 1001
138 8A 1000 1010
139 8B 1000 1011
140 8C 1000 1100
141 80 1000 1101
142 8E 1000 1110
143 8F 1000 1111
144 90 1001 0000
803页
十进制 十六进制 二进制 ASCII 控制 按键
X10 x16 X2 字符
145 91 1001 0001
146 92 1001 0010
147 93 1001 0011
148 94 1001 0100
149 95 1001 0101
150 96 1001 0110
151 97 1001 0111
152 98 1001 1000
153 99 1001 1001
154 9A 1001 1010
155 9B 1001 1011
156 9C 1001 1100
157 9D 1001 1101
158 9E 1001 1110
159 9F 1001 1111
160 A0 1010 0000
161 A1 1010 0001
162 A2 1010 0010
162 A3 1010 0011
164 A4 1010 0100
165 A5 1010 0101
166 A6 1010 0110
167 A7 1010 0111
168 A8 1010 1000
169 A9 1010 1001
170 AA 1010 1010
171 AB 1010 1011
172 AC 1010 1100
173 AD 1010 1101
174 AE 1010 1110
175 AF 1010 1111
176 B0 1011 0000
177 B1 1011 0001
178 B2 1011 0010
179 B3 1011 0011
180 B4 1011 0100
181 B5 1011 0101
182 B6 1011 0110
804页
十进制 十六进制 二进制 ASCII 控制 按键
X10 X16 X2 字符
183 B7 1011 0111
184 B8 1011 1000
185 B9 1011 1001
186 BA 1011 1010
187 BB 1011 1011
188 BC 1011 1100
189 BD 1011 1101
190 BE 1011 1110
191 BF 1011 1111
192 C0 1100 0000
193 C1 1100 0001
194 C2 1100 0010
195 C3 1100 0011
196 C4 1100 0100
197 C5 1100 0101
198 C6 1100 0110
199 C7 1100 0111
200 C8 1100 1000
201 C9 1100 1001
202 CA 1100 1010
203 CB 1100 1011
204 CC 1100 1100
205 CD 1100 1101
206 CE 1100 1110
207 CF 1100 1111
208 D0 1101 0000
209 D1 1101 0001
210 D2 1101 0010
211 D3 1101 0011
212 D4 1101 0100
213 D5 1101 0101
214 D6 1101 0110
215 D7 1101 0111
216 D8 1101 1000
217 D9 1101 1001
218 DA 1101 1010
219 DB 1101 1011
220 DC 1101 1100
221 DD 1101 1101
805页
十进制 十六进制 二进制 AsCII 控制 按键
X10 X16 X2 字符
222 DE 1101 1110
223 DF 1101 1111
224 E0 1110 0000
225 E1 1110 0001
226 E2 1110 0010
227 E3 1110 0011
228 E4 1110 0100
229 E5 1110 0101
230 E6 1110 0110
231 E7 1110 0111
232 E8 1110 1000
233 E9 1110 1001
234 EA 1110 1010
235 EB 1110 1011
236 EC 1110 1100
237 ED 1110 1101
238 EE 1110 1110
239 EF 1110 1111
240 F0 1111 0000
241 F1 1111 0001
242 F2 1111 0010
243 F3 1111 0011
244 F4 1111 0100
245 F5 1111 0101
246 F6 1111 0110
247 F7 1111 0111
248 F8 1111 1000
249 F9 1111 1001
250 FA 1111 1010
251 FB 1111 1011
252 FC 1111 1100
253 FD 1111 1101
254 FE 1111 1110
255 FF 1111 1111
806页
附录B 选中的内存位置
介绍选中的内存位置表的目的是帮助读者了解系统功能的操作方式。直接访问这些
位置中的任何一个都会使得程序极其不可移植,所以,如果不是没有任何别的办法得到所
需的特性和响应,就不应对其进行直接访问。
弄清楚了这些信息在内存中的位置,首先能有助于获取信息。但是改变BIOS和DOS
数据区中的信息将产生极大的破坏(当然,如果对冲毁系统不介意的话,这样做也极其有
趣)。
B.1中断表
绝对地址的00000至003FF为中断地址表。该表由中断00至FFh的中断地址组成,
每个地址占用4个字节。因此,为了在中断地址表中找到某个指定中断的地址,只需将中
断号乘以4就可以了。例如,用于中断C3h的绝对地址为030Ch。在表B.1中,前两列的
号采用的十六进制表示方法。
表B.1 中断00h至FFh的中断地址
中断号 地址 含 义
Int 00 00000 硬件除0
Int 01 00004 硬件单步捕获
Int 02 00008 不可屏蔽
Int 03 0000C 调试程序断点设置
Int 04 00010 算术溢出
Int 05 00014 BIOS打印屏幕
Int 06 00018 无效操作码
Int 08 00020 IRQ0—时钟计数
Int 09 00024 IRQ1—键盘动作
Int 0A 00028 IRQ2
Int 0B 0002C IRQ3-COM2
Int 0C 00030 IRQ4-COM1
Int 0D 00034 IRQ5—PC/XT硬盘
Int 0D IRQ5-PCATLPT2
Int 0E 00038 IRQ6—软盘
Int0F 0003C IRQ7-LPT1
Int 10 00040 BIOS视频服务
807页
中断号 地址 含 义
Int 11 00044 BIOS设备列表服务
Int 12 00048 BIOS内存大小服务
Int 13 0004C BIOS磁盘/软盘服务
Int 14 00050 BIOS通信服务
Int 15 00054 BIOS系统服务
Int 16 00058 BIOS键盘服务
Int 17 0005C BIOS打印机服务
Int 18 00060 执行ROMBASIC
Int 19 00064 重引导系统
Int 1A 00068 BIOS当天时间服务
Int 1B 0006C Ctrl-Break处理程序地址
Int 1C 00070 供Int 08处理程序调用
Int 1D 00074 视频初始化参数表
Int 1E 00078 磁盘初始化参数表
Int 1F 0007C 图形字符表
Int 20 00080 DOS程序终止
Int 21 00084 DOS功能服务
Int 22 00088 程序终止地址
Int 23 0008C DOSCtrl-Break
Int 24 00090 关键出错处理程序
Int 25 00094 DOS绝对磁盘读
Int 26 00098 DOS绝对磁盘写
Int 27 0009C DOSTSR
Int 28 000A0 DOS空闲中断
Int 29 000A4 DOS快速存放字符
Int2A 000A8 MS-Net访问
Int 2E 000B8 DOS主外壳程序装入程序
Int 2F 000BC DOS多路复用中断
Int 33 000CC 鼠标中断
Int 3F 000FC 覆盖管理程序
Int 40 00100 如果安装了硬盘则为软盘中断向量
Int 41 00104 硬盘1参数表
Int 42 00108 EGA BIOS将其用于重定向视频中断
Int 43 0010C EGA初始化参数表
Int 44 00110 EGA字符表
Int 44 Novell NetWare API
Int 46 00118 硬盘2参数表
Int 4A 00128 PCAT:Int 70报警
Int 5A 00168 簇
Int 5B 0016C 由簇程序使用
Int 5c 00170 Net BIOS接口
808页
中断号 地址 含 义
Int 67 0019C 扩展内存管理程序服务
Int 70 001C0 IRQ8——PCAT实时时钟
Int 71 001C4 IRQ9-pc AT重定向为Int 0A
Int 72 001C8 IRQ10
Int 73 001CC IRQ11
Int 74 001D0 IRQ12—鼠标中断
Int 75 001D4 IRQ13-PCAT数学协处理器
Int 76 001D8 IRQ14——硬盘事件
Int 77 001DC IRQ15
Int 7A 001E8 NoVEll NetWare API
B.2 BIOS数据区
绝对磁盘范围00400至004FF为ROM BIOS的数据区。这一区域是一个大杂烩,其
中包括了各种数据结构,表、地址和标志。在下表中,第一列为数据的绝对地址,第2列为
从BIOS数据区开始算起的偏移值——即从段地址0040处开始的偏移值。之所以将这两
列的信息部列举出来是为了便于选择一个更适合不同编程风格的值(头两列的值都采用
的是十六进制表示法)。其余两列是作为解释用的。
注意地址00500处的标志。尽管从表面上看该标志位于BIOS数据区之外,但由于它
是由打印屏幕例程使用的,因此,它实质上属于这个数据块。
表B.2 BIOS数据区
地址 偏移值 长度 含 义
00400 00 字 COM1地址(0表示未使用)
00402 02 字 COM2地址(0表示未使用)
00404 04 字 COM3地址(0表示未使用)
00406 06 字 COM4地址(0表示未使用)
00408 08 字 LPT1地址(0表示未使用)
0040A 0A 字 LPT2地址(0表示未使用)
0040C 0C 字 LPT3地址(0表示未使用)
0040E 0E 字 LPT4地址(0表示未使用)
00410 10 字 设备标志
00412 12 字节 初始化标志
00413 13 字 以千字节为单位的基本内存大小
00415 15 字节 在I/O通道中的内存量
00416 16 字节 未使用
00417 17 字 键盘状态标志
00419 19 字 ALT-键小键盘存储空间
0041A 1A 字 键盘打印超前缓冲区头
0041C 1C 字 键盘打印超前缓冲区尾
809页
地址 偏移值 长度 含 义
0041E 1E 16字节 键盘打印超前缓冲区
0043E 3E 字节 软盘数据
0043F 3F 字节 软盘电机状态
00440 40 字节 软盘电机超时计数
00441 41 字节 最后一次软盘操作状态
00442 42 7字节 磁盘命令工作区域
00449 49 字节 当前显示模式
0044A 4A 字 屏幕列数
0044C 4C 字 以字节为单位的显示内存页大小
0044E 4E 字 相对于当前显示页的偏移值
00450 50 字 显示页0的光标位置
00452 52 字 显示页1的光标位置
00454 54 字 显示页2的光标位置
00456 56 字 显示页3的光标位置
00458 58 字 显示页4的光标位置
0045A 5A 字 显示页5的光标位置
0045C 5C 字 显示页6的光标位置
0045E 5E 字 显示页7的光标位置
00460 60 字 当前光标模式
00462 62 字节 当前视频页号
00463 63 字 活动显示卡的端口地址
00465 65 字节 视频硬件方式选择寄存器值
00466 66 字节 彩色调色板设置
00467 67 双字 磁带数据
0046B 6B 字节 中断发生标志
0046C 6C 双字 从零点开始的计时器计数
00470 70 字节 零点标志(如果自上次读取以来经过了零点,则该标
志置位)
00471 71 字节 中断标志
00472 72 字 复位标志
00474 74 字节 最近一次硬盘操作的状态
00475 75 字节 硬盘驱动器数
00476 76 字节 硬盘控制字节
00477 77 字节 硬盘I/O端口偏移值
00478 78 字节 LPT1超时计数器
00479 79 字节 LPT2超时计数器
0047A 7A 字节 LPT3超时计数器
0047B 7B 字节 LPT4超时计数器
0047C 7C 字节 COM1超时计数器
0047D 7D 字节 COM2超时计数器
0047E 7E 字节 COM3超时计数器
810页
地址 偏移值 长度 含 义
0047F 7F 字节 COM4超时计数器
00480 80 字 相对于键盘缓冲区开头的偏移值
00482 82 字 相对于键盘缓冲区末尾的偏移值
00484 84 字节 EGA/MCGA/VGA屏幕上的行数(以0为基数)
00485 85 字 在EGA/MCGA/VGA屏幕上的字符高度
00487 87 字 EGA/VGA控制标志
00489 89 字节 MCGA/VGA控制标志
0048A 8A 字节 MCGA/VGA数据偏移值指针
0048B 8B 字节 软盘介质控制标志
0048C 8C 字节 硬盘控制卡的状态
0048D 8D 字节 硬盘控制卡的出错标志
0048E 8E 字节 硬盘中断控制标志
0048F 8F 字节 软盘控制器信息
00490 90 字节 软盘驱动器0当前介质状态
00491 91 字节 软盘驱动器1当前介质状态
00492 92 字节 软盘驱动器0起始介质状态
00493 93 字节 软盘驱动器1起始介质状态
00494 94 字节 软盘驱动器0磁道号
00495 95 字节 软盘驱动器1磁道号
00496 96 字节 键盘状态
00497 97 字节 键盘LED控制
00498 98 双字 用户等待例程标志指针
0049C 9C 双字 用户等待计数器
004A0 A0 字节 用户等待活动标志
004A8 A8 双字 EGA/MCGA/VGA视频保存表指针
00500 100 字节 打印屏幕状态标志
811页
附录C 一种标准的TSR标识技术
对于在同一时间里同时使用几种TSR程序来说,长期以来一直存在着一个问题,这
就是:当这些程序之间出现冲突时,我们找不到任何可遵循的标准来管理冲突。
早在1986年,一大群TSR开发者组织起了一支队伍,旨在建立这样一种标准。大多
数在TSR开发上居于领导地位的开发者参加了这一组织。
然而,在经过首次努力尝试以生产出一种可供独家程序和商业化程序使用的完整应
用程序接口(这种努力未能得到商用TSR出版部门的足够支持)之后的一段时间时,许多
成员离开了这一队伍,并转而从事其它方面的研究工作。不过,他们在共同研究期间已开
发并发行了一种接口,该接口允许开发者与他们自己的(或其它人的)TSR进行通信,此
外,他们的设计部门还把用于Microsoft和Turbo C、TurboPascal版本4和版本5以及汇
编语言程序的原有例程库(即众所周知的TesSeRact)设计成了一种可用的共享件。
TesSeRact标准指定了一组链接着DOS的多路复用中断(2Fh)的功能。由于DOs使
用这种中断来与其自己的TSR(如ASSIGN、PRINT和SHARE)通信,因此,TesSeRact
开发队伍感到最合适的方法是用相同的接口来服务于不同的TSR程序。并且通过把AX
设置为某个特殊代码(5453h或ASCII中的TS)来调用中断2Fh便可访问这些TesSeRact
功能。
但这种用法与已公开的Int 2Fh标准用法不相符,在采用标准用法时,DOS规定:标
识代码必须在AH寄存器中(并在80h-FFh范围内),AL=0表示该中断用于已“安装
否?”的功能。
在公开TesSeRact的正式用法之前,这支开发队伍已开发出了TesSeRact的标准,并
且初始拷贝的用途太广泛了,以致不允许对这样一种基本特征作出改变。不相容性也不会
造成任何问题,因为目前尚不知道哪个功能当前正使用54h代码(即使有程序在使用,那
么它还同时拥有53h功能的机会几乎没有)。
还值得一提的是,自从版本3.3引入了APPEND之后,DOS就开始不遵循它自己的
规则;它使用了B7h代码,而这个代码的正式身份是作为非DOS应用程序的保留代码。
为了实现减少不同来源的TSR之间彼此产生的冲突这一原始目标,这支开发队伍还
鼓励所有开发者在各自的代码中支持TesSeRact标准。具体做法是通过开发一个用于Int
2Fh的处理程序,以便支持用户参数块和本附录将通过特殊的TS标识代码来介绍的两
种功能。
812页
C.1用户参数块
与TesSeRact兼容的要求之一是必须支持UserParms数据区。该区域的描述如下(这
一区域必须处在用于Int 2Fh的TSR处理程序的开头):
New_2F: Jmp 0verParms ; Int 2F vector points here
UserParms db 8 dup l(' ') ; 8-byte Program ID string
IdNum dw 0 ; TSR identification number
FuncFlag dd 0ffffffffh ; supported function bit map
HotKey db 0 ; scan code of hot key to use
ShiftSt db 0 ; shift state to use for popup
HotFlag db 0 ;which hot key iS in use
ExtCnt db0 ; number of extmhot keys
ExtHot dd 0 ; pointer to extra hot keys
Status dw 0 ; TSR Status flags
OurPSP dw 0 ; ourPSP segment
ourDTA dd 0 ; our DTA region
DSeg dw 0 ; user's default data Segment
注意,这里仅仅只是列举该数据区结构的一部分;而没有列举由TesSeRact库保持并
使用的元素。
FuncFlag是一个位映象的4字节变量,显示着TSR所支持的所有多路复用功能。每
当调用这种功能时,必须通过BX把其功能号传送给处理程序,而不是像标准情况下通过
AL来传送,其原因我们曾解释过。该变量的映象如下:
位 0 功能00h(检查安装—必需功能)
位1 功能01h(返回用户参数—必需功能)
位2 功能02h(检查热键)
位3 功能03h(置换INT24h)
位4 功能04N(返回数据指针)
位5 功能05h(设置附加热键)
位6-7 未定义—留待将来使用
位8 功能10h(允许TSR)
位9 功能11h(禁止TSR)
位10 功能12h(从RAM中释放TSR)
位11 功能13h(重新启动TSR)
位12 功能14h(获取当前状态)
位13 功能15h(设置TSR状态)
位14 功能16h(获取弹出式打印)
位15 未定义 —留待将来使用
位16 功能20h(调用用户进程)
位17 功能21h(填充键盘缓冲区)
位18- 31 未定义—留待将来使用
813页
如果TSR支持所用的功能,那么必须把对应的位置为1,否则必须将其置为0,使用
TesSeRact库例库的程序在返回时会把FuneFlag置为FFFFFFFFh。其它的TSR则必须
把未定义的变量设置为0,以便同它们自己的变量区别开来。
C.2功能00h(检查安装)
检查安装(功能00h)可确定系统是否安装了某个程序。该功能可通过以下方式来调
用:
mov ax , 5453h ; TS code
mov si , offset IDStr ; see below
mov ds,seg IDStr
xor cx ,cx ; handle counter
xor bx,bX ; function 0000
int 2Fh
IDStr是一个8字节数据区,它保存着一个独一无二的TSR标识符串。 Int 2Fh例程
必须把传送给它的字符串(如下例所示)与它自己的8字节字符串进行比较。如果这两个
字符串相符合,TSR在返回时就必须把CX设置为它自己的TSR句柄,并把AX寄存器
设置为0FFFFFh。
如果标识字符串与传送的字符串不匹配,那么TSR应恢复所有寄存器,增加一个cx
寄存器,并继续沿着程序链往下传送中断。在使用该功能时如果在链接到下一个TSR之
前,系统中每个TesSeRact兼容的TSR都增加了一个CX寄存器,而在此时,中断进程又
实际上返回到调用者这边,那么在这种时候,CX寄存器要么为被找到的TSR的句柄,或
者在没有找到与IDStr相匹配的TSR时为下一个可用的句柄。
如果没有能找到匹配,在返回时AL就不会等于FFh;这样,TSR安装程序就可把
CX的内容用作句柄,以通过它来进行自我标识,并将它保存在参数块的IdNum字中。
下面的代码是根据TesSeRact库的Int 2Fh处理程序编辑而成的,它显示出了处理程
序必须做哪些事情才能证明两个程序是完全相同的:
overparms:
cmp ax,5453h ;ax=5453h for TesSeRact
jne not_our_2F ; some other multiplex function
push dS ; Save for next handler
push CS
pop ds ; set DS while here
push ax
push bx
or bx, bx ; do check for install first,
jnz test_for_1 ;tny other function test
;following is the CHECK INSTALL code
;DS:SI points to ID string
;CX iS current number in chain
push CX
push Si ; Save SI for next one
lea di,UserParms ;the program copy of IDStr
814页
push CS
pop es
mov cx,8 ;test for match
rep cmpsb
pop Si
pop cx
jnz next_one ;no match, not us
pop bx ;matched, empty stack
pop ax
mov cX,es:[di] ;return IdNum in cx
xor ax,ax
dec ax ;AX=-1 means already her
jmp shor done_2F
next_one ; try next higher ID code :
Inc CX
pop bx ;restore for next to use
pop ax
pop ds
not_our_2f: ;chain to next 2F handler
jmp dword ptr[oldint2F] ; via saved pointer to it
done_2F: ; return tO caller
pop ds
iret
C.3功能01h(返回用户参数指针)
若要支持TesSeRact标准,至少还得需要的一种功能便是功能01h,它必须返回一个
指向用户参数块区域的远指针。该功能的调用方法如下:
mov ax, 5453h
mov bX,01h ; function number
mov cx,TsrIdNum ; identmcation number
int 2Fh
如果CX中的标识号与TSR的ID号(由功能00h返回的)相符合,则该功能在返回
时必须让ES:指向用户参数(UserParms)区,并让AX等于0。下面的代码段列举了这样
做的一种方法:
cest_for_1:
cmp cx,IdNum ;if not Function 00h
jne not_our_2F ;the program copy
push cs ;got it, set up for return
pop es
lea bx,UserParms
xor ax, ax ;success status code
pop ds
jmp done_2F
815页
C.4其它TesSeRact功能
完整的TesSeRact文档对其它TesSeRact功能进行详细的介绍,这种文档可从
TesSeRact开发队伍中获取或通过从BBS中卸载下来,以及可从专用于TesSeRact库的
联机服务中获得。如果希望得到一份TesSeRact的Int 2Fh处理程序的源代码,用户可按
下面的地址寄上一封信,并在信封上注明自己的地址:
TesSeRact Development Team
1657 The Fairwavs, Suite 101
Jenkintown,PA 19046
若想了解更为详细的信息,读者可按照CompuServe 7073120或MCIMAIL 315-5415
(TESSERACT)这两个地址与TesSeRact Development Team取得联系。
816页
附录D 保留的DOS功能
本书的DOS参考手册一章已经详细地介绍了与Int 21h相对应的所有虚拟功能;这
些功能中包括那些被正式标记为“保留的”且是大多数参考手册中所没有公开的功能。其
余各参考手册章节介绍了一些标记为保留的或未公开的功能。但是,由于现有的编程体系
保持了高度的编程连续性,因此,不可能保证在此讲到每一个已知的功能。
不仅如此,本书介绍保留功能的材料都来源于非正式的途径。在许多情况下,这些介
绍来源于DOS代码的反汇编部分和对它可能实现的内容进行分析的结果。鉴于此,这些
描述有可能不是百分之百的准确,尽管作者已尽了最大的努力以便为读者提供最好的可
用信息。
这些功能可从总体上分成四个组,我们将在本附录中对它们分别进行介绍。介绍完成
这四个功能组之后,我们还将为读者介绍一对程序,这对程序可使用最令人感兴趣的保留
功能之一,以提供在其它情况下不能获得的信息。
这些功能之所以被“保留”,其中一个最主要的原因是留下余地以便供设计者们能在
不同的版本中,按照任何方式来改变它们。这种情况在每个新的DOs版本中都不同程度
地出现过。这类现在不仅有可能,而且在用到这些未公开的功能时,往往成为现实;但是,
当围绕着它们去设计一道程序时,往往意味着将承担极大的风险。值得欣慰的是,随着这
些未公开的功能被越来越多地投入使用,它们在市场上也受到了越来越多人的欢迎,并且
Microsoft十分乐意将它们的目的和用途变得大众化。
D.1未公开的DOS功能的种类
现将四种未公开的DOS功能列举如下:
·有用的功能,其中包括诸如SetPID和GetDPB之类的事情
·未使用的功能,它们往往无功而返
·用于挂接“将来”功能的功能,它们可能曾经被实现过或未被实现过
·其特征尚未得到确定的功能
每一个DOS版本都拥有各自未公开的功能。例如,DOS版1中只有1种未公开的功
能。而到了DOS版本2,这个数字至少上升到了9,并且在DOS版本4出台前后,这个数
字已发展到超过了50。一种功能所适合的范围很大程度上与取决于编程者是谁。
817页
D.2覆盖范围
在市场上大量发行的书籍都十分详细地介绍了未公开的DOS功能。如果读者是一位
在访问DOS功能方面持严谨态度的程序员,那么就应该添置这类书刊(参见附录E提出
的建议)。
《DOS程序员参考手册》第4版介绍了大量未公开的DOS功能。它们分别出现在本
书的各个主要部分和DOS参考手册一章里。表D.1列出了本书所介绍的这些未公开的
DOS功能。但本附录并不是要介绍所有这些功能,相反,该附录将着重介绍其中一些用户
极有兴趣的,甚至是发现它极其有用的功能。
表D.1《DOS程序员参考手册》中介绍的未公开的功能
Int Func Sub 名 称 版 本
21 37 00h 获取切换字符 版本2或更高
21 37 01h 获取切换字符 版本2或更高
21 37 02h 读取设备可用性 版本2或版本4.0
21 37 03h 设置设备可用性 版本2
21 52 获取磁盘列表 版本2或更高
21 53 把BPB转换成DPB 版本2或更高
21 55 创建PSP 版本2或更高
21 5D 00h 把数据保存到DOS保存区 版本3或更高
21 5D 06h 获取关键出错标志地址 版本3或更高
21 5E 01h 设置机器名 版本3.1或更高
21 60 扩展路径名字符串 版本3或更高
21 64 设置当前国家字节 版本3或更高
21 6A 分配内存 版本4或更高
29 快速放置字符 版本2或更高
2A Microsoft网络接口 版本3或更高
2E 主外围程序装入程序 版本3或更高
2F 05 获取外围关键出错处理程序安装状态 版本3或更高
2F 08 获取DRIVER.SYS安装状态 版本3或更高
2F 12 00h 获取DOS安装状态 版本3或更高
2F 12 01h 清除文件 版本3或更高
2F 12 02h 获取中断向量地址 版本3或更高
2F 12 03h 获取DOS数据段 版本3或更高
2F 12 04h 常规化路径分隔符 版本3或更高
2F 12 05h 输出字符 版本3或更高
2F 12 06h 调用关键出错 版本3或更高
2F 12 07h 移动磁盘缓冲区 版本3
2F 12 08h 减少用户计数 版本3或更高
2F 12 0Ch 供DOS使用的IOCTL打开 版本3或更高
2F 12 0Dh 获取关闭文件的数据和时间 版本3或更高
818页
Int Func Sub 名 称 版 本
2F 12 0Eh 搜索缓冲区链 版本3
2F 12 10h 寻找已修改的缓冲区 版本3
2F 12 10h 时间拖延 版本4或更高
2F 12 11h 常规化ASCIIZ文件名 版本3或更高
2F 12 12h 检查ASCIIZ字符串长度 版本3或更高
2F 12 13h 字体和国家转换 版本3或更高
2F 12 14h 比较32位的数字 版本3或更高
2F 12 16h 获取DCB地址 版本3或更高
2F 12 17h 获取LDT地址 版本3或更高
2F 12 18h 获取用户堆栈地址 版本3或更高
2F 12 19h 设置LDT指针 版本3或更高
2F 12 1Ah 从路径名中获取驱动器代码 版本3或更高
2F 12 18h 闰年调整 版本3或更高
2F 12 1Ch 计算从月头开始的天数 版本3或更高
2F 12 1Dh 计算日期 版本3或更高
2F 12 1Eh 比较字符串 版本3或更高
2F 12 1Fh 初始化LDT 版本3或更高
2F 12 20h 获取DCB号 版本3或更高
2F 12 21h 扩展ASCIIZ路径名 版本3或更高
2F 12 22h 转换扩展出错代码 版本3或更高
2F 12 24h 执行延迟 版本3或更高
2F 12 25h 获取ASCIIZ字符串长度 版本3或更高
2F 12 26h 打开文件 版本4或更高
2F 12 27h 关闭文件 版本4或更高
2F 12 28h 定位文件指针 版本4或更高
2F 12 29h 读取文件 版本4或更高
2F 12 2Bh IOCTL接口 版本4或更高
2F 12 2Dh 获取扩展出错代码 版本4或更高
2F 12 2Fh 保存DX 版本4或更高
2F 15 CD-ROM接口 版本3或更高
D.3使用功能52h—表中表
随着DOS版本3的问世,以及网络支持的应用,DOS发现它有必要标准化系统配置
信息,以便网络操作所求的全部新增加的任选功能都能获取各自所需的信息。Dr. Edwin-
Floyd(Microsoft和IBM之外最早发现这个标准的存在和用途者之一)将这一结果称为配
置变量表(CVD,并且慢漫地变成了众所周知的表中表(List of Lists)。该表中的大多数
在版本2中就已经存在了,并且在版本3中得到了巨大的扩展,并且它在RAM中的位置
也有了轻微的变动。
819页
表中表位于MSDOS.SYS程序的前端。该表含有指向内存控制块(MCB)链的指针,
和其它必需的控制信息,以及现有的或允许的驱动器数。
D.3.1内容和布局
在DOS版本2中,CTV只有25个字节长。而在版本3和版本4中,它占有42个字
节。在所有这三种版本中,CVT紧紧挨在NUL。设备驱动程序——总是程序链中的第一个
驱动程序的前面。(可安装的驱动程序适合于放在NUL驱动程序和后续驱动程序之间的
区域,也就是IO.SYS文件部分)。
要想定位CVT,可使用未公开的Int 21h功能52h。当在不设置任何参数的情况下调
用此功能时,它会通过ES:BX返回一个指向DOS所维持的第一个驱动器参数块的地
址。在版本2中,该项成了CVT中的第2个驱动器参数块的地址(在它之前的是一个2字
节的值,即内存控制块链链头的段地址)。在版本3或版本4中,这一地址变成了8个字
节,因为在MCB段字的前面加进了一个指向当前缓冲控制块的远指针和一个用于当前
缓冲区位置的16位偏移值。
因此,在版本2中,必须把该功能返回的BX值减去2,并且在版本3和版本4中把这
个值减去8,才可达到CVT的前端。
既然已掌握了怎样访问CVT,不妨来看看它究竟保存着一些什么样的内容。
版本2的VCT
下列列举的配置变量表布局显示了该表在DOS版本2中是怎样首次出现的。星号表
明所在的地址是未公开的Int 21h,功能52h返回的地址,并且表中列出的字节偏移值都
是相对于这一地址而言的。
偏移值字节 字段长度 含 义
-02h 字 第一个内存控制块的地址
*00h 双字 指向第一个驱动器参数块的指针
04h 双字 指向第一个DCB(系统文件表设备控制块)的指针
08h 双字 指向CLOCK$设备驱动程序的指针
0Ch 双字 指向CON设备驱动程序的指针
10h 字节 逻辑驱动程序器数
11h 字 在任意块设备上每扇区最多的字节数
13h 双字 指向磁盘缓冲区链链头的指针
17h NUL设备驱动程序头;设备驱动程序链中的第一个设备
偏移值-02h处的字所指定的内存控制块的格式比较简单,现列举如下:
偏移值字节 字段长度 含 义
00h 字节 MCB标识符;M表示除最后一个外的每一个MCB,Z代表
最后一个内存控制块。总是位于段内的偏移值0000h处
820页
偏移值字节 字段长度 含 义
01h 字 拥有该MCB的进程的PSP地址,或为0000h以表明没有
在内存控制块中指定这种内存。
03h 字 以节为单位的后继内存块大小,其中不包括这一16字节
的MCB区域。
04h 字节 余下未使用的节
由CVT中偏移值00h处的22字节所指定的驱动器参数块是本书的参考手册部分所
介绍的与未公开的中断21h,功能52h相关联的结构。它在版本2与版本3之间保持了原
样。由于参考手册部分已全面介绍了驱动器参数块,这里就不再重复了。在系统中的每个
驱动器(包括软盘系统中的驱动器B)都有各自的DPB;并且紧挨着最后一个DPB的块指
针含有FFFF:FFFFh以标志链路的结束。
系统中的DCB数目是通过CONFIG.SYS中的FILES=行来建立的。如果没有指定
FILES=值,则缺省数目为5。 DCBs是以“链接”方式组合起来的,每条链路前面都有一个
3字节链路头:
偏移值字节 字段长度 含 义
00h 双字 指向下一个链路头的远指针,或为FFFF:FFFFh,表示此
处为链中的最后一条链路
04h 字 链路中的块数
在CVT中,偏移值04h处的双字指向含有DCB链中第一个设备控制块的链路头部。在
DOS版本2中,这种40字节的DCB布局与已公开的FCB结构略有不同:
偏移值字节 字段长度 含 义
00h 字节 DCB的当前用户数
01h 字节 打开的DCB所处在的模式
02h 字节 目录项中的属性字节,或设备的00h
03h 字节 驱动器代码(1=A,以及等等)
04h 8字节 文件或设备名
0Ch 3字节 文件扩展名,或空白
0Fh 13字节 没有破解;可能与用于文件的FCB相同
1Ch 双字 指向用于设备的设备驱动程序的远指针;而对于文件来
说,这仍然是个谜
20h 8字节 未能破解
偏移值11h处的字指示出了在任何块设备上最大的扇区大小。它是在安装了块设备
821页
驱动程序后进行加电初始化期间被设置的,并且它建立了配置本章创建的缓冲区的大小。
这一进程能确保每个缓冲区有足够大的空间以保存系统中现有的最大扇区,而且大小正
好够用。
在CVT中,偏移值13h处的双字所指向磁盘缓冲区链分布如下(而用于版本2中的
十分细小的缓冲区组织至今仍未能有人译解得开):
偏移值字节 字段长度 含 义
00h 双字 指向下一个缓冲区的远指针,或为FFFF:FFFFh以指示
链的末端
04h 4字 控制信息没有被全部译成代码,包括标志字节、逻辑扇区
号和驱动器代码
0Ch 双字 指向与缓冲区相关的DPB的远指针
10h 未知 缓冲区自己,通常为512字节,但实际大小是通过保存在
CVT中偏移值11h处的值设置的。
版本3的CVT
在DOS版本3中,配置变量表分别向两端进行了延伸,如下所示。如同在版本2中一
样,带星号处为未公开的中断21h,功能52h返回的地址,表中所有字节偏移值都是相对
于这一地址而言的。
偏移值字节 字段长度 含 义
-08h 双字 在BUFFERS=链中的当前缓冲区
-04h 字 当前缓冲区内的偏移值
-02h 字 第一个内存控制块的段
* 00h 双字 指向第一个驱动器参数块的指针
04h 双字 指向第一个DCB(系统文件表设备控制块)的指针
08h 双字 指向CLOCK$设备驱动程序的指针
0Ch 双字 指向CON设备驱动程序的指针
10h 字 在任何块设备上每扇区的最多字节数
12h 双字 指向磁盘缓冲区链开头的指针
16h 双字 指向逻辑驱动器表的指针
1Ah 双字 指向DOS的FCB链开头的指针
1Eh 字 在交换时应保存的FCB数
20h 字节 块设备数
21h 字节 逻辑驱动器数,由CONFIG.SYS中的LASTDRIVE值设
置(如果不指定,系统会自动缺省为5)
22h NUL设备驱动程序的开头;设备驱动程序链中的第一个
设备
位于偏移值-08处的双字所指的是磁盘缓冲区链中的当前缓冲区;缓冲区控制块的
布局容以后再述。偏移值-04处的字是当前缓冲区内相对于缓冲区中第一个字节的字节
822页
偏移值;值0000h表示已经用到了该缓冲区的最后一个字节,并且应该访问链中的下一个
缓冲区了。
与在版本2中一样,在这里,偏移值-02处的字是MCB链中第一个内存控制块的段
地址。并且,实际内存控制块的布局也与在版本2中的布局相同。此外,DPB链(其第一项
为偏移值00h处的双字节指定)也与版本2的相同。
版本3的驱动器控制块在版本2的基础上进行扩展,以便适应联网和多进程共享相
同文件或设备的使用。但是,同版本2中一样,驱动器控制块仍然被收集到链路中,并且每
条链路都带了一个链路头,而且链路头的布局也没变。每个DCB的新的结构布局如下:
偏移值字节 字段长度 含 义
00h 字 该DCB的用户数
02h 字 每个打开DCB的访问模式
04h 字 磁盘属性字
05h 字节 设备属性
06h 字节 第2个设备属性字
07h 双字 指向设备驱动程序的远指针
0Bh 字 第一个簇号
0Dh 字 文件时间字
0Fh 字 文件日期字
11h 双字 总文件大小
15h 双字 当前字节位置
19h 字 总簇数计数
1Bh 字 当前簇号
1Dh 字 目录扇区
1Fh 字节 目录项索引(在扇区内)
20h 8字节 设备或文件名
28h 3字节 文件扩展名或空白
2Bh 字 未知
2Dh 字 未知
2Fh 字 未知
31h 字 所有者的PSP段地址
33h 字 未知
偏移值10h处的字指示了任何块设备上最大的扇区大小。该大小是在安装了块设备
驱动程序之后进行加电初始化期间被设置的。它可用来建立在配置期间创建的缓冲区的
大小,以确保每个缓冲区能有足够大的空间来保存系统中现有的最大扇区,并且又不留更
多的余地。
位于偏移值12h的双字指向磁盘缓冲区器链的开头。在磁盘缓冲区链中,每个缓冲区
的大小为10h的字所指定的值(通常为512个字节),加上在该数据区前面的16个字节的
链首。其布局如下:
823页
偏移值字节 字段长度 含 义
00h 双字 指向下一个缓冲区的远指针,或为FFFF:FFFFh以表示
到了链的末端
04h 字节 逻辑驱动器代码
05h 字节 动作码
06h 字 逻辑扇区号
08h 字节 FAT数或01
09h 字节 每FAT的扇区数,或00
0Ah 双字 指向与该缓冲区相关的DPB的远指针
0Eh 字 未用
10h 不固定 缓冲区自己,通常为512字节,但其实际大小是通过保存
在CVT中的偏移值10h处的值来设置的
在版本2中,当前目录曾是DPB的一部分,而自版本3开始它就变成了一个独立的
表,即逻辑驱动器表。在CVT中,位于偏移量16h处的双字指向LDT,这个LDT保存着
一个用于每个系统逻辑驱动器的项,直到设置在CONFIG.SYS文件的LASTDRIVE中
的值为止。从驱动器A开始,最小的项数在缺省时为5(A:至E:)。在版本3中,每个设置
驱动器表为81个字节,它们在内存中彼此首尾相连。每个表的布局如下:
偏移值字节 字段长度 含 义
00h 2字节 实际驱动器标志符和:
02h 65字节 为ASCIIZ字符串的驱动器当前路径(包括根目录斜线和
用于终止00h字节的空间)
43h 字 驱动器的当前状态(位图):
8000h=未知,可以表示远程网络驱动器
4000h=可以使用
2000h=未知
1000h=SUBST单元
45h 双字 指向用于驱动器的DPB的指针
49h 字 当前目录的第一个簇
4Bh 字 未知
40h 字 未知
4Fh 字 在报告目录时要跳过的字节数;0002表示普通驱动器,更
大的值表示为被替代的单元
位于偏移值1Ah处的双字以及尾随其单字都与CONFIG.SYS中的FCBs=行相关。
它们分别是一个指向系统FCB链的远指针和在请求交换时应保护它们以防止交换的
FCB数。虽然当初设计FCB的目的主要是供网络使用,但程序也常常将它们用在单用户
环境中,系统FCB采用了与DCB相同的布局,并且,像DCB一样,它们是通过链接方式
824页
组织起来的,每条链路中含有多个文件控制块。
在版本3里CVT中的最后两项(位于偏移值20h和21h)分别是块设备数和逻辑驱
动器数。第一项是根据安装块设备时系统所遇到的总单元之数而建立的,第二项则是通过
CONFIG.SYS中的LASTDRIVE=行来设置的。
版本4的CVT
同版本2和版本3之间的差别相比,在版本3和版本4之间,对CVT及其相应的各
个表所进行的改变显得有点微不足道。但是,在EMS支持方面版本4确实对CVT区域
动了些手术,其中主要的有把16位的逻辑扇区号变成了32位的号。此外,版本4还重写
了缓冲区算法,并因此对缓冲区结构作出较大幅度的调整。下表列出的就是在版本4中使
用的CVT,唯一的重大改变是位于偏移值12h处的旧缓冲区链首指针变成了一个指向新
的EMS链接记录的指针。
偏移值字节 字段长度 含 义
-08h 双字 在BUFFERS=链中的当前缓冲区
-04h 字 当前缓冲区内的偏移值
-02h 字 第一个内存控制块的段
*00h 双字 指向第一个驱动器参数块的指针
04h 双字 指向第一个DCB(系统文件表设备控制块)的指针
08h 双字 指向CLOCK$设备驱动程序的指针
0Ch 双字 指向CON设备驱动程序的指针
10h 字 在任意块设备上每扇区的最多字节数
12h 双字 指向通往DOS缓冲区链的EMS链接记录的指针(版本3
和版本4之间的唯一变化)
16h 双字 指向逻辑驱动器表(其布局见下面的讨论,与版本3略有
不同)的指针
1Ah 双字 指向DOS的FCB链首的指针
1Eh 字 在交换时应保存的FCB数
20h 字节 块设备数
21h 字节 逻辑驱动器数,由CONFIG.SYS中的LASTDRIVE值设
置(如果不指定,系统会自动缺省为5)
22h NUL设备驱动程序的开头;设备驱动程序链中的第一个
设备
在版本4中,低于中断21h,功能52h返回的地址的继续保存未变,但是,由偏移值-
08h处的双字指定的缓冲区控制块却有很大的改变。在版本4之前,每个缓冲区都有其自
己独一无二的段地址,并且远指针链只沿着朝前的方向链接缓冲区。而在版本4中,通过
单EMS连接块(下面将要介绍)所能接触的缓冲区在同一段内,并且,是通过在末端链中
的近指针将它们双向地链接起来的(即向前和向后)。
为了适应新的缓冲算法,版本4已将缓冲区控制块大小从16个字节扩展到了20个
字节。其结构如下:
825页
偏移位字节 字段长度 含 义
00h 字 链中前一个缓冲区的偏移值,是段内或EMS物理页内的
偏移值
02h 字 链中下一个缓冲区的偏移值,是段内或EMS物理页内的
偏移值
04h 字节 逻辑驱动器代码(同版本3一样)
05h 字节 动作代码(不一定与版本3相同)
06h 双字 逻辑扇区号,是从版本3中扩展而来的
0Ah 字节 FAT数,或为01;完整的含义尚不清楚
0Bh 字 如果FAT在缓冲区内则为每FAT的扇区数;其它情况下
含义不明
0Dh 双字 一远指针,指向与所在缓冲区相关的物理驱动器
11h 字 含义不明
13h 字 含义不明
14h 512字节 缓冲区自己;同在版本3中一样,大小是通过CVT中的字
设置的,但通过常情况下为512字节
为了加速缓冲区搜索,版本4已将一种全新的连接记录加给了缓冲区运算。这种11
字节记录保存着一种散列值,并指向相关的BCB。其详细情况目前尚未完全弄清楚,但已
弄清楚了它的结构,其结构如下:
偏移值字节 字段长度 含 义
00h 字 散列值
02h 双字 指向相关BCB的远指针
06h 字节 应用计数器
07h 双字 含义不明
在版本4中,为适应新的保存缓冲区功能而增加的第二个新记录是在EMS而不是常
规内存中。该记录通过一种连接记录而把一个EMM句柄和物理页联系在一起。由于系统
软件和非IBM的EMS硬件之间存在兼容性问题,这样便不可能在EMS是活动的时候去
仔细检查系统是如何使用这种记录的;下面介绍的情况是针对非EMS环境的:
偏移值字节 字段长度 含 义
00h 双字 指向前面介绍的连接记录的远指针
04h 字 通过连接记录控制的页数
06h 双字 含义不明
0Ah 字 含义不明
0Ch 字节 标志;当不在使用EMS时,值为FF
0Dh 字 EMS句柄值
0Eh 字节 EMS物理页号
826页
在版本4中,还有一个遭到改变的区域就是DPB结构,通过CVT中偏移值00h处的
指针即可达到这一结构。在驱动器参数块中,唯一的改变是“每FAT的扇区数”由8位变
成了16位,所增加的8位是由于增加了一个用于随后各项的偏移值字节造成的。
在版本4中,介于DPB指针之上一直到偏移值12h之间的各项保持未变。不过,在偏
移值12h处的一个远指针已变成了一个指向用于磁盘缓冲区的EMS记录的指针,这一
点我们已经在介绍BCB的改变时顺便提到过。
此外,通过CVT中的偏移值16h的远指针可以达到的LDT也被扩展了7个字节。至
于这7个字节的用途目前尚未得到确定。新的LDT结构如下:
偏移值字节 字段长度 含 义
00h 2字节 实际驱动器标志符和:
02h 65字节 为ASCIIZ字符串的驱动器当前路径(包括根目录斜线和
用于结尾00h字节的空间)
43h 字 驱动器的当前状态(位图):
8000h=未知
4000h=可以使用
2000h=未知
1000h=被替代的单元
45h 双字 指向用于驱动器的DPB的指针
49h 字 当前目录的第一个簇
48h 字 未知
4Dh 字 未知
4Fh 字 在报告目录时要跳过的字节数;0002表示普通驱动器,若
是替代的单元则为更大的值
51h 字节 未知
52h 字 未知
54h 字 未知
56h 字 未知
CVT的余下内容与版3相同,这一点读者可通过反汇编和进行测试来证实。
D.3.2 CVT3.PAS一个用于版本3的程序
伴随程序(CVT3.PAS)能起到两方面的作用,一方面作为一个实例,可让读者了解未
公开的Int 21h,功能52h实际使用;另一方面,它可作为一种工具来探索清楚在版本3中
运行的系统是怎样布置CVT的。该程序如列表0.1所示:
列表0.1
{cvt3.pas Turbo Pascal V5.0 version for DOS 3.x
* Copyright 1987, 1988, 1989 Jim Kyle All Rights Reserved
* The program, and the information it contains, may be freelY
* distributed and uSed as long as this entire comment section
* is not altered.
827页
}
PROGRAM CVT3;
{$A-, B-, D+, E-, F-, I+, L+, N+, O-, R-, S+, V- }
{$m 8192,0,0 }
USES DOS;
TYPE
bcb = RECORD { Buffer Control Block format }
nxcb : pointer; { +00 next one in chain }
ldrv : byte; { +04 logical drive code }
action: byte; { +05 action code }
lsect : word; { +06 logical Sector # }
nf : byte; { +08 nbr FATs or 01 }
secf : byte; { +09 sectors/FAT or 00 }
pdry : pointer; { +0A phys dry tbl ptr }
fill : integer; { +0E unknown }
buf : array[0..511] of byte; { +10 the buffer itself }
END ;
bcbptr =^bcb ;
mcb = RECORD { Mem Alloc Block header format }
flag : char; { +00 must be M or Z }
owner : word; { +01 PSP seg or 0000 }
siz : word; { +03 Number paragraphs }
END ;
mcbptr = ^mcb ;
dpb = RECORD { Drive Parameter Block format }
drvc : byte; { +00 drive code }
dunit : byte; { +01 unit number }
bps : integer; { +02 bytes per sector }
spc : byte; { +04 sec per cluster -1 }
pwr2 : byte; { +05 power of 2 }
rsrvs : integer; { +06 Reserved sectors }
nfats : byte; { +08 number of FATs }
dirsiz : word; { +09 root dir size }
fus : word; { +0B first usable sectr }
tcc : word; { +0D total clstr ct +1 }
spf : byte; { +0F sec per FAT }
fds : word; { +10 first dir Sector }
drvr : pointer; { +12 driver pointer }
mcode : byte; { +16 media code }
accflg : byte; { +17 access flag }
nxt : pointer; { +18 next table ptr }
lastused : wordi; { +1C last used cluster }
filler : word; { +1E usually FFFF }
END ;
dpbptr = ^dpb ;
chn = RECORD { Chain links for DCB, FCB chains }
nxtlnk : pointer; { +00 next link or FFFF }
nmbr : integer; { +04 number blocks here }
END ;
Chnptr = ^chn ;
dcb = RECORD { Device Control Block format }
nusers : integer; { +00 users for this DCB }
mode : integer; { +02 0,1,2 per OPEN }
datrb :byte; { +04 disk attrib byte }
dvatr : byte; { +05 device attrib this }
atrb2 : byte; { +06 2nd device attrib }
pdrvr : pointer; { +07 points to driver }
frstc : word; { +0B ,first cluSter nbr }
828页
modtm : word; { +0D file time word }
moddt : word; { +0F file date word }
totsiz : longint; { +11 total file size }
curpos : longint; { +15 current byte pos }
clsctr : word; { +19 total cluster ctr }
curcls : word; { +1B current cluster }
dirsec : word; { +1D directory sector }
dirndx : byte; { +1F dir entry index 0..}
name : array[0..7] of char; { +20 dev/file name }
ext : array[0..3] of char; { +28 file extension }
fill2 : word; { +26 unknown }
fill3 : word; { +20 unknown }
fill4 : word; { +2F unknown }
owner : word; { +31 PSP of owner proc }
fill5 : word; { +33 unknown }
END ;
dcbptr = ^ dcb ;
ldt = RECORD { Logical Drive Table format }
name : array[0..67] of char;{ +00 drive and path }
code : byte; { +44 drive in use code }
mydpb : dpbptr; { +45 DPB for this drive }
dirclu : word; { +49 directory cluster }
filler2: word; { +48 FFFF }
filler3: word, { +40 FFFF }
patlen : word; { +4F SUBST path length }
END ;
ldtptr= ^ldt ;
cvt = RECORD { Configuration Variable Table }
curbfr : bcbptr; { current buffer pointer }
memchn : mcbptr; { start of MCS chain }
pdrvs : dpbptr; { Fn $52 points to here }
dcbchn : dcbptr; { set up by FILES= }
clkdev : pointer; { set by DEVICE= (clock$)}
condey : pointer; { set by DEVICE= (con) )
secsiz : integer; { maximum block size }
bfrchn : bcbptr; { set up by BUFFERS= }
ldrvs : ldtptr; { set up by LASTDRIVE= }
fcbchn : Chnptr; { set up by FCBS= }
filler : integer; { number of FCBs to keep }
npdrvs : byte; { set by driver list }
nldrvs : byte; { set by LASTDRIVE= }
END ;
Cvtptr = ^cvt ;
dtstr = string[8]; {date-time strings }
pstrg = string[9]; { pointer-conversion fmt }
memst = string[2]; { memory usage string }
VAR
cvtbase : cvtptr;
curbuf : bcbptr;
Cunmcb : mcbptr;
curdpb : dpbptr;
curchn : chnptr;
curdcb
curfcb : dcbptr;
curldt : ldtptr;
bcbctr ,
dcbctr : integer ; { counters }
829页
b : Registers;
PawsFlag: Boolean, { controls screen-full pausing }
lctr : integer; { counts lines displayed }
function hexnlb:byte) : char; { converts nibble to char }
begin
b := b and 15; { force to only 4 bits }
if b > 9 then inc(b,7); { adjust for hex digits }
hexn := chr(b+48); { convert to character }
end ;
function hexb(b:byte) : string;
begin
hexb := hexn(b shr 4) + hexn(b); { assemble the nibbles }
end ;
function hexw(w:word) : string;
begin
hexw := hexb(w shr 8) + hexb(w); { assemble the bytes }
end ;
function hexl(l: longint) : string;
begin
hexl := hexw(l shr 16) + hexw(l); { assemble the words }
end ;
PROCEDURE holdup; { count lines and wait if fUll }
BEGIN
IF PawsFlag THEN
BEGIN
inc ( lct r ) ;
if lctr > 23 then
BEGIN
lctr := 0;
readln ;
END
END
END ;
FUNCTION xp( p : pointer ) : pstrg; { displays pointer P }
BEGIN
xp := hexwlseg(p^)) + ':' + hexw(ofs(p^)) ;
END ;
PROCEDURE dmplf : pointer); { display hex dump of data to CRT }
VAR
x : ^byte;
i : integer;
c : char;
BEGIN
x : = f ;
write( xp(f), '>' ) ;
FOR i:=0 to 15 DO
BEGIN
write( hexb(x^) ) ;
if i=7 then write( '-' )
else write ( ' ' ) ;
x := pointer(longint(x) + 1);
END ;
write ( ' ' ) ;
x : = f ;
FOR i:=0 to 15 DO
BEGIN
830页
c := char($7f AND x^);
if c< ''then c : = '.';
write( c );
if i=7 then write( ' ' ) ;
x := pointer(longint(x) + 1 );
END ;
writeln ;
holdup ;
END ;
PROCEDURE dpbtrc(a: pointer) ; { trace and report DPB data }
VAR
ofsv : word;
PROCEDURE dpbrpt; { reports each DPB's content }
BEGIN
writeln;
holdup ;
write( 'Drive', char (curdpb^ .drvr+ord('A') ) ) ;
write(': (unit ' , curdpb^.dunit, of driver at ' , xp(curdpb^.drvr) ) ;
writeln( ' ) media code =', hexb(curdpb^ .mcode) ) ;
holdup ;
write( ' ' , curdpb^ .bps:3,'bytes per sector, ) ;
write( ' ',curdpb^ .spc+1 :2,'sectors per cluster,') ;
writeln(' ', curdpb^ .tcc-1 :4, , total cluster count') ;
holdup ;
write( ' Res sectors: ' , curdpb^ .rsrvs,' ') ;
writet(' ',curdpb^ .nfats, ' FAT' ' s, ',curdpb^ .spf :2, ' sec ea ' ) ;
write( ' FAT entry : ' ) ;
IF (curdpb^.tcc) > $0FFC
THEN write( 16 )
ELSE write( 12 );
writeln('bits Root: ' , curdpb^ .dirsiz:3, ' entries');
holdup;
write(' First root sector: ' , curdpb^.fds:3 );
write(' First data sector: ' , curdpb^.fus:3 );
writeln( ' Last cluster used : ' , hexw(curdpb^ . lastused) ) ;
holdup ;
END; { of dpbrpt proc }
BEGIN
curdpb := a;
ofsv := 0;
writeln ;
holdup ;
writeln( 'DRIVE PARWETER BLOCK (DPB) DATA- -') ;
hOldup ;
WHILE (ofsv <> $FFFF) DO
BEGIN
ofsv := word(longint ( curdpb^ . nxt ) ) ;
dpbrpt ;
curdpb : = dpbptr(curdpb^ .nxt) ;
END ;
write ( #12 ) ;
END; { of dpbtrc proc }
PROCEDURE bcbtrc(a : pointer) ;
VAR
ofsv : word;
PROCEDURE bcbppt(a : bcbptr);
VAR
i : integer;
831页
x : pointer;
BEGIN
writeln ;
holdup ;
inc ( bcbctr ) ;
writeln( 'Buffer Control Block ' , bcbctr:2, ' at ' , xp(a) ) ;
holdup ;
write ( ' Logical ' ' ' , char ( ord ('A ' ) +a^ . ldrv) ) ;
write( ' :'' , Sector ' , hexw(a^ . lsect) ) ;
writeln( ' Action code : ' , hexb (a^ . action ) ) ;
holdup ;
write( ' NFATS:', hexb( a^ .nf) ) ;
write( ' SPF: ' , hexb(a^ . secf ) ) ;
write( ' DPB: ' , xp(a^ .pdrv) ) ;
writeln( ' FILL: ' , hexw(a^ .fill) ) ;
holdup ;
x:= addr(bcbptr(a) ^ .buf[O] ) ;
FOR i:=0 to 31 Do
dmp (pointer ( longint ( x ) + ( i SHL 4) ) ) ;
END ; { of bcbrpt proc }
BEGIN { bcbtrc routine }
bcbctr := 0;
ofsv := 0;
WHILE (ofsv <> $FFFF) DO
BEGIN
ofsv := word(longint( bcbptr(a)^ .nxcb ) ) ;
bcbrpt (a);
a := bcbptr(a) ^ .nxcb;
END ;
wnite (#1 2 ) ;
END; { bcbtrc routine }
PROCEDURE dcbtrc(t : dtstr; a : pointer);
VAR
ofsv : word;
FUNCTION f_tm(n : word) : dtstr;
VAR
buf : dtstr;
b : byte;
BEGIN
b := ((n SHR 11) AND 31);
buf[1] :=char((b DIV 10) + 48) ;
buf[2] :=char((b MOD 10) + 48);
buf[3] := ' : ' ;
b := ((n SHR 5) AND 63);
buf[4] :=char((b DIV 10) + 48);
buf[S] :=char((b MOD 10) + 48);
buf[6] :=': ';
b := ((n SHL 1) AND 63);
buf[7] :=char((b DIV 10) + 48);
buf[8] :=char((b MOD 10) + 48);
f_tm := buf;
END ;
FUNCTION t_dt(n : word) : dtstr;
VAR
buf : dtstr;
b : byte;
BEGIN
832页
b :=((n SHR 5) AND 15);
buf[1] :=char((b DIV 10) + 48);
buf[2] :=char((b MOD 10) + 48);
buf [3] : = '1';
b := ( n AND 31);
buf[4] :=char((b DIV 10) + 48);
buf[5] :=char((b MOD 10) + 48);
buf[6] := '/';
b:=((n SHR 9) AND 15) +80;
buf[7] :=char((b DIV 10) + 48);
buf[8] :=char((b MOD 10) + 48);
f_dt := buf;
END ;
PROCEDURE dcbnpt( var t : dtstr; n : integer );
type
acctyp = array[0..3] of string[7] ;
const
actyp : acctyp = ( ' READ' , 'WRITE ' , ' R/W' , ' unknown ' ) ;
var
isdvc : Boolean;
BEGIN
WHILE (n > 0) DO
BEGIN
INC (dcbCtr) ;
writeln ;
holdup ;
wnite (t, '',dcbctr:2 );
IF (curdcb^ . name[0] = #0) THEN
BEGIN
writeln( ' at ' , xp(curdcb) , ' not used since bootup' );
holdup ;
END
else
BEGIN
isdvc := (curdcb^.dvatc and 128) <> 0;
write( ' for ' ) ;
IF isdvc
THEN write('device ' )
ELSE write( 'file ' );
write ( curdcb^ . name [ 0 ] , curdcb^ . name [ 1 ] , curdcb^ . name [ 2 ] ) ;
write ( curdcb^ . name [ 3] , curdcb^ . name [ 4 ] , curdcb^ . name [ 5 ]) ;
write ( curdcb^ . name [ 6 ] , curdcb^ . name [ 7 ] );
IF (NOT isdvc) THEN { block driver }
write( ' . ' , curdcb^ .ext[0] , curdcb^ .ext[ 1 ] , curdcb^ .ext[2] ) ;
write( ' at ' , xp( curdcb ) );
write( ' shows ' , curdcb^ .nusers) ;
writeln( ' OPENs') ;
holdup ;
write( ' opened for', actyp[3 AND (curdcb^.mode)] ) ;
write( ' access' ) ;
IF ($FFFC AND (curdcb^ .mode) )<>0 THEN
write( ' ( ' , hexw(curdcb^ .mode) , ' ) ' ) ;
writeln('by process ' , hexw(curdcb^ .owner) ) ;
holdup ;
IF( isdvc ) THEN { Device }
BEGIN
write( ' Device driver at', xp(curdcb^ .pdrvr) ) ;
write( ' is in ' ) ;
if ( (curdcb^ .dvatr) AND 32)<>0 THEN write( ' Raw' )
ELSE write( 'Cooked ' ) ;
write('mode and is ' ) ;
if ( (curdcb^ .dvatr) AND 64)=0 THEN write( ' not ' ) ;
833页
writeln ('ready');
holdup ;
END
else { File }
BEGIN
write(' File is on drive ' , char(ord( 'A' )+( (curdcb^ .dvatr) AND 31 ) ) ) ;
write(': (driver at ' , xp(curdcb^ .pdrvr) ) ;
write(' ) and has ' ) ;
if ( (curdcb^ .dvatr) AND 64)<>0 THEN write( ' not ' ) :
writeln( ' been written to. ' ) ;
holdup ;
writeln( ' File ' ' s attribute byte = ' , hexb(curdcb^ .datrb) ) ;
holdup ;
END ;
write (' Mod Time/date:');
write (f_tm(curdcb^.modtm) , ' , ' ) ;
writeln ( f_dt ( curdcb^ . moddt ) ) ;
holdup ;
write ('Dir Sector: ' , hexw(curdcb^.dirsec) , ' ' ) ;
writeln( ' Dir index : ' , curdcb^ .dirndx :4, ' ' ) ;
holdup ;
write ( ' First Cluster: ' , hexw(curdcb^ .frstc) , ' ' );
write ( ' Prev Clusters: ' , curdcb^ .clsctr:4, ' ' ) ;
writeln('Current Cluster : ',hexw(curdcb^ . curcls) , ' ') ;
holdup ;
write ( ' Directory size: ' , curdcb^ .totsiz:6, ' ' ) ;
writeln( ' Curr byte count : ' , curdcb^ .curpos :6 ) ;
holdup ;
write( ' Fill2= ' , hexw(curdcb^ . fill2) , '') ;
write ( ' Fill3=', hexw(curdcb^.fill3) , ' ' ) ;
write ( ' Fill4=' , hexw(Curdcb^.fill4) , ' ' ) ;
writeln( ' Fills= ' , hexw(curdcb^ .fill5) ) ;
holdup ;
END ;
curdcb := pointer(longint(curdcb) + sizeof(dcb) - 1 );
DEC( n );
END; { n >= 0 loop }
END; { of dcbrpt }
BEGIN
curchn := chnptr(a) ;
dcbctr := 0;
ofsv := 0;
WHILE (ofsv <> $FFFF) DO
BEGIN
ofsv : = word ( longint (curchn^ . nxtlnk ) );
curdcb : = dcbptr ( longint (curchn) +sizeof (chn ) ) ;
writeln ;
holdup ;
write ( 'Link at , xp(curchn),' contains ' );
writeln( curchn^ . nmbr, ' ' , t,'s--' ) ;
holdup ;
dcbrpt (t, curchn^ .nmbr) ;
curchn := chnptr(curchn^ .nxtlnk) ;
END ;
Write (#12) ;
END ;
PROCEDURE memtrc(a : pointer) ;
VAR
z : longint;
PROCEDURE memrpt ( s , o , a : word) ;
834页
FUNCTION memu(a : word) : memst ; { determine memory use }
var
x : char;
BEGIN
x := char( mem[a:0] );
CASE X OF
#$CD :
memu : = ' Program' ;
'A' . . 'Z' :
memu := ' Environment ' ;
else
memu :=' Data ';
End ;
END ;
BEGIN
z := longint(s) SHL 4;
write( z:6, ' bytes ' );
IF (o<>0) THEN
writeln('USED by proc ' , hexw(o) , ' , at ' , hexw( a ) , ' :0000, for ' , memu(a) )
else
writeln( 'FREE at ' , hexw( a ) , ' :0000' ) ;
holdup ;
END; { of memrpt }
BEGIN
curmcb := mcbptr(a);
writeln ;
holdup ;
writeln( 'MEMORY ALLOCATION CHAIN ' ) ;
holdup ;
WHILE (curmcb^ .flag = 'M' ) DO
BEGIN
memrpt (curmcb^ . siz , curmcb^ .owner, SEG(curmcb^ )+1 ) ;
curmcb := ptr( seg(curmcb^) + (cunmcb^.siz + 1), 0 );
END ;
IF (curmcb^ .flag <> 'Z' ) THEN
BEGIN
writeln(#13, #10, 'MEMORY ALLOCATION ERROR at ' , xp(curmcb) ) ;
halt (255 ) ;
END ;
memrpt ( curmcb^ . siz , curmcb^ .owner , SEG(curmcb^ )+1 ) ;
write ( #12 ) ;
END; { of memtpc )
PROCEDURE ldttrc( a: ldtptr; n: byte );
PROCEDURE ldtppt( l: ldtptr; d: byte );
VAR
ldrive : char;
i : integer;
BEGIN
ldrive := chr( $41 + d );
if (1^.code AND byte($40) )=0 then
writeln( 'Logical Drive ' , ldrive, ' not yet defined' )
else
begin
write ( 'Logical Drive ' , ldrive );
writeln( ' = Physical drive ' , l^.name[0] ) ;
holdup ;
write ( 'The current (full) pathspec is: ' );
i : = 0 ;
repeat
write ( l^.name[i] );
inc( i );
835页
until (l^ . name[i]= #0) ;
writeln ;
end ;
holdup ;
if (l^ . code = $50) then
writeln( 'Code = 0*50 - - result of SUSST command ' )
else if(l^ .code = $40) then
writeln( 'Code = 0*40 - - physical (or aliased) device' )
else
writeln ( ' Code =0x ' , hexb( l^ .code) , ' - - unknown ' ) ;
holdup ;
writeln( 'Directory Cluster = ' , hexw( l^ .dirclu ) ) ;
holdup ;
writeln( 'Path Length to ignore = ' , hexw( l^.patlen ) ) ;
holdup ;
writeln ;
holdup ;
END; { of ldtcpt }
VAR
o : byte;
BEGIN
curldt := a;
writeln ;
holdup;
writeln( ' LOGICAL DRIVE TABLES (Set by LASTDRIVE=, SUBST, etc. ) : ' ) ;
holdup ;
dec( n ) ; { convert for zero-based reference }
for o := 0 to n do { loop through continuous tables }
begin
ldtrpt (curldt , o) ;
curldt := ptr(seg(curldt^) , ( ofs(curldt^) + sizeof(ldt) ) ) ;
end ;
write ( #1 2 ) ;
END; { Of ldttpc }
{ main }
SEGIN
b.ah := $30, { Check for correct DOS version }
MsDos ( b ) ;
if b.al <> 3 then { If not verSion 3.x, get right out }
begin
writeln( 'Wrong DOS version: ' , b.al,'.', b.ah );
halt(255) ; { return ErrorLevel=255 }
end ;
PawsFlag := ParamCount = 0; { else set up for paging output }
lctr := 0;
writeln( 'Configuration Variables, DOS version ' , b.al, ' .', b.ah ) ;
holdup ;
b.ah := $52; { Get CVT pointer set up }
msdos (b) ; { (using undocumented function ) }
cvtbase := ptr(b.es, b.bx-8) ; { hold pointer to CVT }
writeln ;
holdup ;
writeln( 'CVT is located at ' , xp(cvtbase) ) ;
holdup ;
write ( 'No. of Phys Drives (at', xp(@cvtbase^.npdrvs)) ;
writeln ( ' ) : ' , cvtbase^ . npdrvs) ;
holdup ;
write ( 'No. of Log. Drives (at ' , xp(@cvtbase^.nldrvs) ) ;
writeln( ' ) : ' , cvtbase^ .nldrvs) ;
hOldup ;
write ( ' Clock Device (ptr at', xp(@cvtbase^ .clkdev)) ;
836页
writeln ('):', xp(cvtbase^ . clkdev ) ) ;
holdup ;
write ('CON Device (ptr at', xp(@cvtbase^.condev) ) ;
writeln( ' ) : ' , xp(cvtbase^ . condev) ) ;
holdup ;
write (' Sector Size(?) (at', xp(@cvtbase^.secsiz)) ;
writeln ( ' ) : ' , hexw( cvtbase^ . secsiz) ) ;
holdup ;
write (' FCBs to keep (at ' , xp(@cvtbase^.filler));
writeln ( ' ) : ' , hexw(cvtbase^ . filler) ) ;
holdup ;
write ( '1. Memory Chain (ptr at ' , xp(@cvtbase^.memchn)) ;
writeln ( , ) : ' , xp(cvtbase^ . memchn ) ) ;
holdup ;
write ( '2. DCB Chain (ptr at ' , xp(@cvtbase^.dcbchn)) ;
writeln(') : ' , xp(cvtbase^ . dcbchn) ) ;
holdup ;
write ( '3. DPB Chain (ptr at', xp(@cvtbase^.pdrvs)) ;
writeln ( ' ) : ' , xp (cvtbase^ . pdrvs ) ) ;
holdup ;
write ( '4. FCB Chain (ptr at', xp(@cvtbase^.fcbchn) );
writeln ( ' ) :', xp( cvtbase^ . fcbchn) );
holdup ;
write ( '5. LDT Chain (ptr at , xp(@cvtbase^.ldrvs) ) ;
writeln( ' ) : ' , xp(cvtbase^ . ldrvs) ) ;
holdup ;
write ('6. Current Buffer (ptr at', xp(@cvtbase^.ourbfr));
writeln ( ' ) : ' , xp (cvtbase^ . curbfr ) ) ;
holdup ;
write ( '7. Buffer Chain (ptr at ' , xp(@cvtbase^.bfrchn));
writeln ( ' ) :', xp( cvtbase^ . bfrchn) ) ;
holdup ;
writeln ;
holdup ;
writeln ('TRACING MCB Chain=== ) ;
holdup ;
memtrc ( cvtbase ^ . memchn ) ;
writeln ;
holdup ;
writeln ('TRACING DCB Chain===');
holdup ;
dcbtrc( ' DCB' , cvtbase^ .dcbchn) ;
writeln ;
holdup ;
writeln ('TRACING DPB Chain=== ) ;
holdup ;
dpbtrc ( cvtbase ^ . pdrvs ) ;
writeln ;
holdup ;
writeln ( ' TRACING FCB Chain===' ) ;
holdup ;
dcbtrc ( ' FCB', cvtbase^ . fcbchn ) ;
writeln ;
holdup ;
writeln (' TRACING LDT Chain===');
holdup ;
ldttrc ( cvtbase^ . ldrvs , cvtbase^ . nldrvs ) ;
writeln ;
holdup ;
(已经到附录了!未完。)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端