本章讨论视频显示器和打印机这两种基本的输出设备。它们可能是计算机编程中最 重要的设备,因为它们在程序和程序员之间担当着相互作用点的角色。 大多数计算机书籍都将辅助设备(RS-232端口)看作字符输出设备,并在与本章类似 的章节中进行描述。但在这本书中,将对RS-232端口单独介绍,因为它们具有独一无二 的特性和多种功能(参见第7章“串行设备”)。 与别的章节一样,本章着重介绍与最高编码级别一起工作的实用程序。通常,应该使 用直接来自高级语言的服务;但在许多情况下,则必须使用较低级的服务,如DOS或 BIOS级的服务,来完成特定的任务。本章描述控制视频显示器和打印机的DOS和BIOS 服务。 5.1基本的字符设备 编写涉及视频显示器和打印机设备的程序可以是简单的过程,也可能是相当复杂的。 在简单的字符I/O级别之上,编程会很快就成变一个复杂的过程,特别是与图形相关时更 是如此。 尽管此书并非是有关图形方面的完整手册,但它还是提供了图形工作的基本原则。在 这一章里,我们已开发了几个用于系统编程的有效例程,构造了几个相当重要的、有效的 输出工具。 在C语言中,已经有足够的服务能与显示器和打印机一起工作,以完成典型的编程 琐事。当构造程序时,这些腋务通常是最好用的,这有两个原因: .通过使用标准的库函数,可以减少程序对于DOS设计发生改变的敏感性。 . 如果恰当地使用了标准调用,这些调用还能使程序与UNIX或XENIX系统兼容。 如果需要进行超出标准库函数的编程,那么必须审慎地权衡可能的得失。从标准库函 数移向DOS服务,会获得对输出操作的更大控制,并保持对系统设计的一定的非敏感性。 同时,则失去了标准库函数的便利和与UNIX或XENIX的兼容性。 那么程序员在什么时候要着重关注非敏感性和兼容性呢?显然,如果与DOS和UNIX 或XENIX一起工作,有程序代码(它在各种操作系统之下可不加修改地进行成功的编 译)能使程序开发和维护工作简单化。这些程序是典型的简单实用程序或交互作用程序。 这种双重方法不能与实时交互作用程序一起很好地工作。一些程序如1-2-3和Mi- 80页 crosoft Word会受此方法的制约。采用此方式来写这些类型的程序是不可接受的,除非在 编写时尽量采用最快的可能途径。在许多实例中,因为是直接对硬件编程的,所以虽然破 坏了兼容性,但却使速度(并使生产率)达到了最大值。 5.2看看显示系统的工作方式 PC机显示系统已从简单的开始形式演化成如下所示的现今使用的不同的标准: ·单色显示器适配卡(MDA) ·彩色图形适配卡(CGA) ·Hercules图形适配卡(HGA) ·增强图形适配卡(EGA) ·多彩色图形阵列(MCGA) ·虚拟图形阵列(VGA) IBM借助标准BIOS和DOS服务,承认并支持以上标准(除HGA外)。 MDA、CGA 和EGA 用于计算机的PC系列,MCGA和VGA则用于Personal System/2系列。HGA 的 普及性实际上使HGA在PC系列上成为高分辨率单色图形的标准,但要利用它的图形功 能则需要特殊的驱动程序。BIOS或DOS都没有内在的服务,能够去充分利用HGA。HGA 的这种独一无二的特性和它缺乏直接来自BIOS或DOS服务的支持,就说明了它的编程 超出了本书的范围。 读者也许会问为什么超级VGA(SVGA)没有作为PC机上的图形标准而被提到。这 是因为还没有建立明确的标准。而且,与这种高分辨率屏幕定义相竞争的许多方式已经产 生。也许要到将来(即使有也很少)才会形成一个明确的标准。 显示监视器的类型 已经有了许多类型的显示监视器,并且一直都有更多的类型在不断地投入使用。本章 并不一一列举,以下所列只是其中的一部分: 直接的单色监视器,能显示高分辨率文本和字符水平的图形。MDA能驱动这些监视 器,HGA或EGA卡可以模拟单色适配卡。 合成单色监视器是经济的单色(通常是琥珀色或绿色)监视器,能被CGA输出所驱 动。它们能显示CGA图形,但不是彩色的。其中一些监视器能借助阴影来表示颜色的差 异。 合成彩色监视器中,它们的分辨率特别差。该卡支持的设置处在该范围的低端,但它 们的低分辨率在文本显示模式里不会产生令人满意的结果,除非使用40列的显示行。 RGB监视器使用独立的电子束(三枪)来分别显示每个原色(红,绿和蓝),从而在文 本和图形模式下,都能产生清晰而又丰富的彩色输出。 增强型RGB监视器能提供彩色文本和图形,并且输出结果比通常的RGB监视器产 生的结果要优秀一些。这些监视器使用同样的技术(独立的RGB电子束),但使用了较高 级的显示电路技术,以便提供更高质量的图像。 81页 多路同步监视器是目前所提供的能显示最高质量的文本和图形显示器,并具有灵活 性。借助RGB连接,这类监视器能超过通常的增强的功能。这类监视器还可以模仿其他 类型的监视器,并提供了增强的显示功能。 视频显示器能以下列三种途径中之一来进行存取: 借助DOS功能调用。这是一种最兼容,但却是最慢的存取形式。在DOS2.0和更 高的版本下,ANSI.SYS 驱动程序让使用该存取方式的程序能通过控制编码系列 而控制屏幕。 借助BIOS 功能调用。这种存取显示的方法相当兼容,并比DOS更快。大多数系 统,但并非全部系统都与该方法兼容。借助此类调用,用户可以使用DOS级不能 使用的图形和其他屏幕效果。 在硬件级上直接进行编程。这种方法最不兼容,因为在系统中会存在广泛的硬件 差异。使用这种方法的程序通常与被考察的PC兼容的所有系统不兼容。该方法 在多用户或任务系统中也是不兼容的。它的长处以及被频繁使用的原因都源于 在这种上编程的敏捷显示和快速操作。 打算建立复杂显示的程序员不一定要从硬件级开始编程(实际上该级别是“最后避难 所级别”)。大多数好的程序都起始于该范围的另一端点,即高级语言。重要的商业程序, 都采用像BASIC原型之类的语言作为开发的起点(如Visicalc,它是原始的电子表格程 序,最初编码就是在BASIC中进行的,它是为AppleII而开发的)。程序运行正常后,可以 提高其速度和复杂程序,使其尽可能地快速和紧凑。使一个正常的程序加快速度要比使一 个快速的程序变正常更容易。 借助DOs 和 BIOS的屏幕存取能够编写复杂的且有用的程序。完成复杂过程的程序 能在不直接存取屏幕显示的局部下运行。当开始与多任务环境如Windows或DESQview 一起工作时,就会开始对这个存取级别感到满意。 5.2.1存储和显示视频数据 PC机显示系统原来是以Motorola 6845 阴极射线管控制器(CRTC)芯片为基础的。 该芯片已用在MDA、CGA和HGA视频卡中。 EGA、VGA和最近的系统都使用定制的专 用芯片,它能执行6845所提供的所有基本功能,并能显著地增强6845的功能。视频控制 器芯片管理着许多重要的显示任务,以便程序员不必去管理它们: ·探测光笔信号 ·递增视频缓冲区地址计数器 ·使显示与计时同步 ·选择视频缓冲区 ·确定硬件光标的大小和地址 PC机显示系统的设计,在理论上是很简单的。PC机显示器是内存映射设备,在其中, 屏幕显示的正是计算机内存中的内容(见图5.1)。内存缓冲区存储屏幕显示的信息。内存 缓冲区的起始地址以及长度会随着所使用的视频显示器的类型、当前显示方式以及分配 82页 用于显示的内存数目而发生改变。 图5.1一个显示系统的显示内存映射 显示适配卡通常有4至256K的内存,VGA适配卡一般都提供512K或1M内存。因 为用来限定显示屏的数据可以比这些数目明显地少占空间,所以一些显示适配卡能控制 不止一个的显示屏幕。注意这里说的是显示屏,而不是显示监视器。显示屏或页是出现在 屏幕上的内容的内存代表。表5.1显示了起始的内存缓冲区地址、缓冲区长度以及显示页 的数目。 表5.1显示适配卡的内存配置 显示器类型 视频方式 MDA 文本 CGA 文本 图形 HGA 图形 EGA 单色 文本 图形 MCGA 文本 图形 VGA 单色 文本 图形 缓冲区段地址 缓冲区长度 显示页数 B000h 4K 1 B800h 16K 4/8 B800h 16K 1 B800h 64K 1 B800h 变化 变化 B800h 变化 变化 A000h 变化 变化 B800h 32K 8 A000h 64K 1 B000h 变化 变化 B800h 变化 变化 A000h 变化 变化 对于所有显示适配卡而言,文本方式的有效页数就是每个屏幕位置乘上两个字节去 除总内存数的结果。再结合每行80个文本字符,那么就是2*80*25,等于4000字节,或 大约4K。如果使用每行40个文本字符(2*40*25),那么每个屏幕就占据2000个字节, 或大约2K空间。利用这些计算,能够很容易地看清为什么CGA能从16K的缓冲区空间 中获取8个显示页。 EGA和VGA卡的缓冲区大小会变化,是因为它们能在64K到1M的内存中占据任 83页 何位置。该RAM是屏幕图象的一个视频缓冲区,并且也拥有1024显示字符那么大的模 式(字形)。前面介绍的计算方法能帮助用户确定可用的显示页数。 表5.1显示出EGA、MCGA和,VGA都有两个不同的图形显示缓冲区起始地址。这 些适配卡,可以模拟CGA(段地址为B800h)和它们自己的起始段地址A000h。 CRTC芯片,独立于计算机系统的操作之外,对显示内存区域进行描述并以存储在那 里的信息为基础来更新视频显示。实际的屏幕显示由电子束来产生,该电子束能在对屏幕 的每一行进行扫描时,打开或关闭小的屏幕点(称作象素)。电子束从左到右,从上到下地 扫过整个屏幕。 要提供稳定的图像,屏幕就要以每秒60次的速度更新(即电子束对整个屏幕进行一 个完整的扫描)。在每行的结尾,电子束必须从屏幕的右端移到左端,这段时间称作水平回 扫间隔(HRI)。类似地,电子束完成一个周期后,它要从右下部移到左上部开始一个新的 周期。这段时间称作垂直回扫间隔。在HRI和VRI期间,电子束都被关闭,屏幕上看不到 任何东西。 直接将程序书写到显示内存中去的程序员应该警惕一些类型的显示适配卡的HRI 或VRI,因为适配卡利用显示内存的方式各不相同。在某些显示适配卡中使用的内存是特 殊的双重端口内存,计算机能在该内存中书写数值,同时CRTC可以阅读它们。因为这类 内存比“普通的”RAM要昂贵,所以其它的显示适配卡则可能忽略了这个细节。如果你的 计算机碰巧要安装视频内存地址到一个非双重端口适配卡中,且此时CRTC正在此位点 阅读该数值,那么你会看到一个称作“雪花”的显示屏幕扭曲出现。 当用户与优秀的IBM CGA或CGA电路的任何复制口一起工作时,这个问题特别重 要。这些类型的系统中“雪花”是如此糟糕,以至于用户用这样一个特殊的词来描述它:色 彩变味”(Chromablizzard)。要在这类系统中防止这个问题出现,应该只在HRI或VRI期 间存取屏幕内存。 在I/O端口3DAh查询CRTC状态寄存器可以知道HRI或CRI状态是否存在。0位 指示HRI是否存在;3位则反应有关VRI的同样的信息。当回扫间隔开始时,相应的位就 打开了,问隔时间要完成时,位也随之关闭。因为编程时,HRI出现得更多并且更容易探 测到,所以大多数直接的屏幕内存途径测试只用于HRI条件。当位一直打开时,用户有时 间将一个字符放进显示内存(假定标准的4.77MHz的系统速度)而没有产生屏幕中断。 要多次获得这种结果,则必须在查询时使所有的中断失效;否则,其他的活动就会偷走用 户正等待的间隔时间。 以上有关屏幕中断的指导在向屏幕内存书写内容和从屏幕内存阅读内容时都是有用 的(尽管为什么应该中断阅读并非那么显而易见,但经验却显示它确实是在许多CGA卡 上这么进行的)。 5.2.2视频显示格式 显示适配卡对视频数据的解释决定于显示方式,该方式控制了数据在屏幕上出现的 途径。表5.2给出了在各个不同的显示卡上所能使用的显示方式。 84页 表5.2视频方式 显示卡 方式号 方式 颜色数 分辨率 MDA CGA EGA MCGA VGA Pcjr 00h 文本 16 40*25 X X X X 01h 文本 16 40*25 X X X X 02h 文本 16 80*25 X X X X 03h 文本 16 80*25 X X X X 04h 图形 4 320* 200 X X X X X 05h 图形 4 320*200 X X X X X 06h 图形 2 640*200 X X X X X 07h 文本 单色 80*25 X X X 08h 图形 16 160*200 X 09h 图形 16 320*200 X 0Ah 图形 4 640* 200 X 0Bh — 保留 — 0Ch — 保留 — 0Dh 图形 16 320*200 X X 0Eh 图形 16 640*200 X X 0Fh 图形 单色 640* 350 X X 10h 图形 16 640*350 X X 11h 图形 2 640*480 X X 12h 图形 16 640*480 X 13h 图形 256 320*200 X X 分辨率一栏中的数目代表文本方式的行和列,代表图形方式的象素。 MDA只支持一个屏幕显示方式(方式7),CGA支持7个,EGA支持12个。最复杂的 适配卡是VGA系统,它支持15个显示方式。 VGA还支持单色显示、每屏幕43行的显示 以及有256种颜色的彩色调色板等的图形。 BIOS跟踪当前显示方式并将方式号保存在内存地址0400:0049中。每行的列数保 存在0040:004A处。尽管用户可以直接改变这些数值,但这样做并不聪明,因为BIOS不 仅在这些内存位置上改变了数目,而且执行了正确设置视频方式所必须的其它操作。 现在让我们看看两类显示方式:文本和图形。 一、文本方式显示 文本方式也叫做字母数字方式,大多数IBM文件都是这样提到的。文本方式中,内存 的两个字节安排在屏幕显示的每个字符位置上:一个字节拥有该字符,另一个则拥有其属 性。字符就在与它同一地址的那个字节里,属性就在另一字节里。字符属性向显示适配卡 指示出字符应该怎样显示。表5.3显示单色文本方式的字符属性位的含义;表5.4则显示 彩色文本方式位的含义。 85页 表5.3单色字符属性 位 含义 76543210 0······· 正常字符 1······· 闪烁字符 ·000···· 黑背景(正常) ·111···· 白背景(逆向) ····0··· 正常强度 ····1··· 高强度 ·····000 白色前景(正常) ·····001 下划线白色前景 ·····111 黑色前景(逆向) 表5.4单色字符属性 位 含义 76543210 0······· 正常字符 1······· 闪烁字符 ·XXX···· 背景(见表5.5) ···· XXXX 前景(见表5.5) 注意表5.4中背景颜色只允许三位,前景颜色则为四位。原因是标准的视频显示线路 设置背景域的高位为1,从而提供使每个字符闪烁开和关的功能。但是,通过修饰输送给 视频适配卡的方式显示寄存器的值,就可以得到背景亮度值的完整范围(以失去闪烁能力 为代价)。 对于CGA,要解除前景的闪烁特性并加上强度控制给前景,就必须读取BIOS储存在 0040h:0065h位置上的值,0DFh的字节使闪烁位变清晰,并将结果输出到端口03D8h (即CGA的MDR地址)。对于HGA或MDA,执行同样的操作,但将结果输送到端口 03B8h。 下面的实例汇编语言程序允许明亮的CGA背景: push es ;save the register mov ax , 40h :address BIOS work area mov es,ax mov al,es:65h ; get last value sent to mode control and al,0DFh ;clear blink control bit mov eses : 65h,al ;save value for future reference mov dx , 03D8h get CGA mode control port address out dx,al ; send to CGA mode control port pop es ; restore saved segment register 这种修改只有当视频方式被BIOS再次改变时才能保持有效;要使之对于所有的视 频方式都有效就需要改变Int 1Dh向量所指示的视频表。因为视频表通常驻留在ROM 中,要将它们拷贝给RAM并保证它们一直没有改变,这不是一个普通的任务;而只改变 所需寄存器是比较容易的。 对于EGA和最近的适配卡,触发闪烁位要简单得多。可以使用BIOS Int 10h处的接 口,并设置AX为1303h: 86页 mov ax,1303h int 10h 表5.5列举了每种颜色可能的位置。但是,注意,因为通常的前景是由32位决定的, 只有超过7的值才能存储在前景之中,除非象刚才描述的那样触发了闪烁位。 表5.5彩色文本方式下可能的位设置 位 值 二进制 十进制 颜色 0000 0 黑色 0001 1 蓝色 0010 2 绿色 0011 3 青色 0100 4 红色 0101 5 品红色 0110 6 棕色 0111 7 白色 二进制 十进制 颜色 1000 8 灰色 1001 9 淡蓝色 1010 10 淡绿色 1011 11 淡青色 1100 12 淡红色 1101 13 淡品色 1110 14 黄色 1111 15 高强度白色 将字符的ASCII码值保存在字符内存位置,并在属性字节中设置其属性后,适配卡 的显示电路图就会产生字符的物量显示。在屏幕上每个字符就转化成一个点模式,该点模 式与显示适配卡所产生的字符相对应。字符是从保存在适配卡中的ROM字符发生器内 的数据转化而来的。 EGA和VGA卡也使程序员能够指定另外的用户—限定字符设置 用于字符显示。 除了单色和彩色文本显示,还有两种其它类型的文本显示。它们之间的区别在于每行 所显示的字符数。 有些显示适配卡每行能显示40或80个字符。基本的视频显示格式是80*25每屏 幕。因为40列格式通常只有当视频显示器是合成电视设备(在这种设备中每行40个字符 是大致可以阅读的)时才是有用的,所以本书的着重点放在80列格式上,它能与标准的 80*244计算机终端显示器紧密匹配。 二、图形式方式显示模式 IBM也提到了图形式方式显示模式,称它是APA,或称为所有点可以定位的方式,在 图形模式中,每个屏幕象素都由一系列的内存位所规定。每个位指示象素打开或关闭以及 是什么颜色。用于每个象素的位数决定于显示适配卡类型以及正在使用的图形方式。例 如,EGA系统能从64种颜色的调色板上显示16种颜色。要指示出某个特定象素应该是 其中的哪种颜色,那么需要4个位。每个象素所需的位数可由下列等式表示: 位数=log(颜色数)/log(2) 颜色数即是代表的颜色数,位数是所需的位数。对于每个EGA,屏幕象素任何时候有 16种颜色,那么它的等式就是: 87页 位数=log(16)/log(2)=1.20412/0.301003=4位 图形显示屏幕的分辨率(参见5.21)用象素来表示,有水平和垂直两个分辨率。例如, 表5.2列举的模式0Eh的分辨率为640*200,即宽640个象素,深200个扫描行(象素)。 这个数目表示显示屏幕总共有128000个象素。当进行图形方面的工作时,请记住分辨率、 有效颜色和内存需要量之间的关系。 5.2.3.识别视频显示适配卡 尽管已达到共识认为:运行优良的DOS应用程序应该使用BIOS,或者最好使用DOS 功能去操纵视频,但有时候是必须将规则手册和程序置之于脑后。例如,使用BIOS视频 功能阅读并书写象素到屏幕上,从而编写一个油漆刷程序。但是,如果知道正在使用的是 哪一个视频适配卡,那么就可以直接编写视频控制器程序,这会导致速度的戏剧性提高。 虽然有关单个视频适配卡的详细编程超出了本书的范围,但是确定出现的是哪一个视频 适配卡并未超出此范围。 本节列举了识别下述显示适配卡类型的一系列过程: MDA EGA HGA MCGA HGA+(Hercules图形卡增强型) VGA Hercules InColor卡 SVGA CGA卡 在SuperVGA适配卡中,要进一步识别生产厂家和芯片的类型。 下列显示器类型也能识别: 与MDA兼容的(单色) 与CGA兼容的 EGA兼容的 PS/2兼容、单色的 PS/2兼容、彩色的 最后,这些过程就探测到了两个视频硬件系统并区分哪个是活动的,哪个是非活动 的。 识别视频适配卡首先是尝试VGA或EGA特有的视频BIOS调用。如果这些调用成 功了,再继续完成下列两项任务:识别EGA或VGA BIOS没有注意的CGA或MDA卡, 识别(如果是VGA适配卡)潜在的SuperVGA适配卡。 通过探测CRTC。可以识别CGA或MDA适配卡。 MDA的CTRC状态端口通常是在I/O地址3B4h处,而CGA则在3D4h处。该程序 假定地址正确,那么就试着向光标位置低寄存器写入值。在短暂的耽搁后,如果数值能被 读回来,就可以假定CRTC状态端口已经找到并且CGA或MDA已经定位。 如果探测到了是MDA适配卡,那么可进一步识别它是不是一个普通的MDA或 HGA。这要利用MDA和HGA之间的差异:在MDA中,垂直同步位从不改变,而在HGA 88页 中则会改变。HGA能进一步划分为HAG+、Hercules InColor(内部颜色)卡,或规则的 HGA,要做到这一点可以观察状态端口的某些位。 探测并识别SVGA是一个有趣的问题。开发商们在开始仿制IBM的EGA和VGA 卡时,他们制成的适配卡确实不能与原来的IBM适配卡区分开来。但是开发商以他们自 己的方法来开发高级VGA(Super VGA)。每个适配卡都支持一个800*600视频方式(通 常这是确定一个适配卡是VGA或是SVGA的标准),但达到此目的却是以各自不同的、 独有的方式来进行的。每个适配卡还支持一些特有的分辨率或提供一些特殊功能,而别的 适配卡是做不到这些的。类似地,不同的开发商用来识别每个高级VGA适配卡的方法是 各不相同的。识别某个特定开发商的适配卡的技术将在每个适配卡的过程中进行描述。将 SVGA从VGA中分离出来需要尝试每一项SVGA识别技术;如果所有技术都未成功,那 么这就是一个简单VGA适配卡。 列表5.1是用于识别视频适配卡的程序(注意:这并非是一个唯一标准的程序)。 列表5.1 page 55,132 name video_id ; video_id.asm ;Procedure for identifying the video system(s) ;-----Equates----- ;Adapter types UNKNOWN_ADAPTER equ 000h CGA equ 001h MDA equ 002h EGA equ 003h MCGA equ 004h VGA equ 005h SVGA equ 006h VESA SVGA equ 007h ADAPTER_SYSTEM_ MASK equ 007h ;Monitor types UNKNOWN MONITOR equ 000h shl 3 MDA_MONITOR equ 001h SHL 3 CGA_MONITOR equ 002h SHL 3 EGA_MONITOR equ 003h SHL 3 VGA_MONO equ 004h SHL 3 VGA COLOR equ 005h SHL 3 MONITOR_MASK equ 007h SHL 3 ;SVGA BIOS types UNKNOWN_BIOS equ 000h SHL 6 AHEAD equ 001h SHL 6 ATI equ 002h SHL 6 CIRRUS equ 003h SHL 6 CTI BASED equ 004h SKL 6 GENOA equ 005h SHL 6 HEADLAND equ 006h SHL 6 EVEREX equ 007h SHL 6 PARADISE equ 008h SHL 6 TSENG BASED equ 009h SHL 6 89页 ; video chip types UNKNOWN_CHIP equ 000h SHL 10 HGC equ 001h SHL 10 HGCPLUS equ 002h SHL 10 HERCULESINCOLOR equ 003h SHL 10 AHEAD_VERSION_A equ 001h SHL 10 AHEAD_VERSION_B equ 002h SHL 10 ATI18800REV1 equ 001h SHL 10 ATI18800REV2 equ 002h SHL 10 ATI18800w18810 equ 003h SHL 10 CIRRUS_510_520 equ e01h SHL 10 CIRRUS_610_620 equ 002h SHL 10 CIRRUS_VSEVEN equ 003h SHL 10 CTI82c451 equ 001h SHL 10 CTI82c452 equ 002h SHL 10 CTI82C453 equ 003h SHL 10 TRIDENT_8800BR equ 001h SHL 10 TRIDENT_8800CS equ 002h SHL 10 ; Start of simplified directives ; Change the model size to match the program in which ; these will be used .model small . data PrimarySystem dw 0000 ; Primacy video system SecondarySystem dw 0000 ; Secondary video system adapter_table db UNKNOWN_ADAPTER OR UNKNOWN_MONITOR db MDA OR MDA_MONITOR db CGA OR CGA_MONITOR db UNKNOWN_ADAPTER OR UNKNOWN_MONITOR db EGA OR EGA_MONITOR db EGA OR MDA_MONITOR db UNKNOWN ADAPTER OR UNKNOWN_MONITOR db VGA OR vga_MONO db VGA OR VGA_COLOR db UNKNOWN_ADAPTER OR UNKNOWN_MONITOR db MCGA OR EGA_MONITOR db MCGA OR VGA_MONO db MCGA OR VGA_COLOR db (16 - 13) dup (0) ega_table db EGA OR CGA_MONITOR db EGA OR EGA_MONITOR db EGA OR MDA_MONITOR db EGA OR CGA_MONITOR db EGA OR EGA_MONITOR db EGA OR MDA_MONITOR db (8 - 6) dup (EGA OR UNKNOWN_MONITOR) AHEAD_BIOS_Sig db "AHEAD" ATI_BIOS_Sig db "761295520" ATI_BIOS_Sig2 db "31" CIRRUS_BIOS_Sig db "CL" GENOA_BIOS_Sig db 77h , 11h , 99h , 66h PARADISE_BIOS_Sig db"VGA=" VESA_BIOS_Sig db "VESA" scratch_pad db 256 dup (?) 90页 ;-----Program Code - - - - - . code ; Main procedure for identifying the video systems video_ident proc push bx push cx push ds ;save caller's DS mov ax , seg video_ident mov ds,ax ,get new DS ; Initialize structures mov PrimarySystem,0 mov SecondarySystem,0 ; Test for VGA presence mov ax,01A00h ; Read display code's function int 10h ; call video BIOS cmp al,01Ah ; Successful call? jne no_vga_present ; we have VGA at least or bh,bh ; Secondary system detected? jz no_secondary ; We have a secondary system present push bx ; Preserve bx mov al,bh ; Put secondary type in AL and al , 00Fh mov bx,offset adapter_table ; Get lookup table xlat xor ah,ah ; Clear byte or SecondarySystem,ax ; Set flags pop bx ; Restore bx ; what iS the primary System? no_secondary : mov al,bl ; Put primary type in AL and al , 00Fh mov bx,offset adapter_table ; Get lookup byte xlat xor ah,ah ; Clear byte or PrimacySystem,ax ; set flags and ax , ADAPTER_SYSTEM_MASK ; What adapter? cmp ax,VGA , Did we detect a VGA? je PVGA_detected mov ax,SecondarySystem ; How about the Secondary? and ax, ADAPTER_SYSTEM_MASK cmp ax , VGA jne noVGA_detected mov bx,offset SecondarySystem jmp short detect_SVGA pVGA_detected : mov bx,offset PrimarySystem ; Attempt to detect SVGA Systems detect_SVGA: call SVGA_detect ; Although enhanced video BIOS call succeeded, no VGA was found noVGA_detected : mov ax,SecondarySystem ; Is secondary system MDA? and ax , ADAPTER_SYSTEM_MASK cmp ax,MDA ; Is secondary system MDA? je clarify_MDA ; If it iS, identify further 91页 mov ax,PrimarySystem ;How about the primary? and ax ,ADAPTER_SYSTEM MASK cmp ax,MDA ; Is secondary system MDA? jne swap_systems clarify_MDA : call Hercules_detect ; Identify the specific MDA system jmp short swap_systems ; The enhanced video BIOS call failed no_vga_present : mov bl,010h ; Attempt tO find EGA mov ah , 012h int 10h cmp bl,010h ; Call failed? je no_ega_present mov bx,offset ega_table mov al,cl ; Get switch settings shr al , 1 and ax , 7 xlat or PrimacySystem,ax ; Set values and ax ,ADAPTER_SYSTEM_MASK ; What did we find? cmp ax ,MDA ; MDA? je seek_CGA ; Then look for a CGA call MDA_detect , Else look for MDA jmp short swap_systems Seek_CGA: Call CGA_detect jmp short swap_systems ; EffOrts to find EGA failed too no_ega_present : Call CGA_detect ; Seek CGA Call MDA_detect ; and MDA ; May need to swap primany/secondary systems swap_systems : mov ax,SecondarySystem , Get current mode and ax , ADAPTER_SYSTEM_MASK Cmp ax , UNKNOWN_ADAPTER je vid_exit cmp ax , MCGA jae vid_exit mov ax , Primarysystem and ax , ADAPTER_SYSTEM_MASK cmp ax , MCGA jae vid_exit mov ah,00Fh ; Get current video mode int 10h and al , 7 cmp al,7 , Current mode is mono? jne current_color mov ax , PrimarySystem and ax , MONITOR_MASK cmp ax , MDA_MONITOR je vid_exit dO_swap : mov ax , PrimarySystem xchg ax ,SecondarySystem mov PrimarySystem,ax jmp short vid_exit current_color: 92页 mov ax,primarySystem and ax , MONITOR_MASK cmp ax , MDA_MONITOR je do_swap ; Return to caller vid_exit: mov ax,PrimarYSystem ; Set return values mov dx,SecondarySystem pop ds ; Restore caller's DS pop cx pop bx ret ; Return video_ident endp ; Routine tO detect SVGA cards ; on entry, BX is a pointer to the VGA system word SVGA_detect proc push ax ; Save registers push cx PUSh di push dx push si push es mov ax,0c000h ; Point to ROM miv es , ax mov di,00025h ; Signature is at C000:0025 mov si , Offset AHEAd_BIOS_sig mov cx,5 ; Length of Signature cld repe cmpsb jne not_ahead_bios ; It's an AHEAd BIOS-now identify which chip version and word ptr [bx] , NOT ADAPTER_SYSTEM_MASK or word ptr [bx] ,SVGA OR AHEAD mov dx,003CEh ; Get i/o address mov al,00Fh , Get index of enable register out dx,al inc dX , Get I/O address Of data mov al,020h ; Get enable value out dx,al jmp $+2 in al,dx ; Get value cmp al,020h ; Version A? je ahead_a cmp al,021h ; Version B? jne test_vesa_isle or word ptr [bx] , AHEAd_VERSION_B jmp short test_vesa_isle ahead_a: or word ptr [bx] ,AHEAD_VERSION_A test_vesa_isle: jmp test_vesa ; Wasn't AHEAD. Test for ATI not+ahead_bios: mov di,00031h ; ATI signature at C000:0031 mov si , Offset ATI_BIOS_Sig mov CX , 9 repe cmpsb jne not_ati_bios 93页 ; So far, so good. . . mov di,00040h ; "31" at C000:0040 mov si , off set ATI_BIOS_sig2 mov cx , 2 repe cmpsb jne not_ati_bios ; It's an ATI BIOS- -now identify the chipset and word ptr [ bx ] ,NOT ADAPTER_SYSTEM_MASK or word ptr [bx] ,SVGA OR ATI mov al , es : 043h cmp al, '1' ; Which Chipset? je ati_1 cmp al , '2' je ati_2 cmp al , '3' jne test_vesa_isle or word ptr [ bx ] ,ATI18800W18810 jmp test_vesa ati_1 : or word ptr [bx] ,ATI18800REV1 jmp test_vesa ati_2: or word ptr [bx] ,ATI18800REV2 jmp test_vesa ; Now check for Cirrus not_ati_bios: mov di,6 mov si , Offset CIRRUS_BIOS_Sig mov cx , 2 repe cmpsb jne not_cirrus_bios ; It's a Cirrus BIOS- -now identify the chip and word ptr [bx] , NOT ADAPTER_SYSTEM_MASK or word ptr [bx] ,SVGA OR CIRRUS xor ax,ax , Find CRTC mov es , ax mov dx,es :00463h push dx mov al,00Ch , Get Start address Out dx,al inc dx in al,dx ; Read register mov ah,al ; Save what we read mov al , 00Ch push ax xor al,al ; Clear register Out dx,al dec dx mov al,01Fh , Get iD register out dx,al inc dx in al,dx ; Read unlock password mov ch,al ; Save it--it's the key to the chip ID mov dx,003C4h , Address of sequencer . mov al,006h , Extension control reg out dx,al inc dx mov al,ch ; Get unlock password out dx,al in al,dx ; Read it back cmp al, 1 ; Unlocked? 94页 jne not_cirrus_chip mov al,ch mov cl,4 ror al , cl out dx,al in al , dx or al,al ; Locked? jnz not_cirrus_chip cmp ch ,0ECh ; 510/520? jne not_cirrus_510 or word ptp [ bx ] , CIRRUS_510_520 jmp short not_circus_chip not_cirrus_510: cmp ch , 0CAh ; 610/ 620? jne not_cirrus_610 or word ptr [bx] ,CIRRUS_610_620 jmp Short not_Cirrus_Chip not_cirrus_610: cmp ch,0EAh ; Video seven? jne not_cirrus_chip or word ptr [bx] ,CIRRUS_VSEVEN not_cirrus_chip: pop ax ; Restore CRTC pop dx out dx,ax jmp test_vesa ; Check for CTI not_cirrus_bios: cli ; Disable interrupts mov dx,046E8h ; Put chip in setup mode in al , dx or al,010h out dx,al mov dx,00103h ; Read extended enable register in al , dx or al,080h ; Turn enable on out dx,al inc dx ; Read global ID in al , dx mov ah,al , Save it mov dx,046E8h ; Turn setup back off in al , dx and al , 0EFh out dx,al sti ; Reenable interrupts mov dx,003D6h ; Read version xor al,al out dx,al inc dx in al , dx cmp ah,0A5h ; Right global ID? jne not_cti_bios ; Seems to be a CTI chip. Which version? and al,0F0h ; Check version cmp al , 000h jne not_cti82C451 and word ptr [bx] , NOT ADAPTER_SYSTEM_MASK Or word ptr [bx] ,CTI82C451 OR SVGA OR CTI_BASED jmp test_vesa not cti82c451: cmp al , 010h jne not_cti82C452 and word ptr [bX] , NOT ADAPTER_SYSTEM_MASK 95页 or word ptr [bx] ,CTI82C452 OR SVGA OR CTI_BASED jmp test_vesa not_cti82c452 : cmp al , 030h jne not_cti_bios and word ptp [ bx] ,NOT ADAPTER_SYSTEM_MASK or word ptr [bx] ,CTI82C453 OR SVGA OR CTI_BASED jmp test_vesa ; Check for Genoa not_cti_bios: mov di,0037h ; Read pointer les di,es:[di] , Dereference it mov Si , Offset GENOA_BIOS_Sig mov cx , 4 repe cmpsb jne not_genoa_bios and word ptr [ bx ] , NOT ADAPTER_SYSTEM_MASK or word ptr [bx] ,GENOA OR SVGA jmp test_vesa Try Headland not_genoa_bios: push bx xor bx,bx ; Special Headland BIOS call mov ax , 06F00h int 10h cmp bx,'7' jne not_headland mov ax , 06F07h int 10h Cmp bl,070h jb not_headland cmp bl,07Fh ja not_headland pop bx and word ptr [bx] , NOT ADAPTER_SYSTEM_MASK or word ptr [bx] ,HEADLAND OR SVGA jmp Short test_vesa not_headland: pop bx ; Restore pointer ; Try Trident/Everex push bx , Save pointer mov ax,07000h , Extended BIOS call xor bx,bx int 10h cmp al,070h ; OK? jne not_everex_bios and dx,0FFF0h ; Get board number cmp dx,06780h jne_not_everex_bios pop bx and word ptr [bx] , NOT ADAPTER_SYSTEM_MASK or word ptr [bx] ,EVEREX OR SVGA jmp short test_trident not everex_bios: pop bx test_trident: ; Identify the chip itself mov dx,003C4h mov al , 00Bh 96页 Out dx,al inc dx in al , dx and al , 00Fh cmp al , 1 je found_8800BR cmp al ,2 jne not_trident and word ptr [bx] , NOT ADAPTER_SYSTEM_MASK or word ptr [bx] ,TRIDENT_8800CS OR SVGA jmp short test_vesa found 8800BR: and word ptr [bX] ,NOT ADAPTER_SYSTEM_MASK Or word ptr [bx] ,TRIDENT_8800BR OR SGA jmp short test_vesa ;Try Paradise not_trident: mov ax , 0C000h mov es ,ax mov di,0007Dh mov si , OffSet PARADISE_BIOS_Sig mov cx, 4 repe cmpsb jne not_paradise and word ptr [bx] ,NOT ADAPTER_SYSTEM_MASK or word ptr [bx] ,SVGA OR PARADISE jmp Short test_vesa ; Try Tseng not_paradise : mov dx,003CDh in al , dx mov ah , al and al , 0C0h or al , 055h Out dx,al in al , dx cmp al , 055h jne test_vesa mov al , 0AAh out dx,al in al , dx cmp al , 0AAh jne test_vesa and word ptr [bx] , NOT ADAPTER_SYSTEM_MASK Or word ptr [bx] ,SVGA OR TSENG_BASED ; Test for VESA BIOS test_vesa: mov ax,04F00h mov di,offset scratch_pad push ds pop es int 10h Cmp ax , 0004Fh jne svga_exit Cld mov Si , offset VESA_BIOS_Sig mov cx , 4 repe cmpsb 97页 jne svga_exit and word ptr [bx] , NOT ADAPTER_SYSTEM_MASK or word ptr [bx] , VESA_SVGA svga_exit : pop es pop si pop dx pop di pop cx pop ax ret SVGA_detect endp ; Routine tO detect Hercules MDA card Hercules_detect proc push si push dx push cx push bx push ax mov si , offset PrimarySystem mov ax , [ si ] and ax , ADAPTER_SYSTEM_MASK cmp ax , MDA je test_Hercules mov si,offset SecondarySystem test_Hercules: mov dx,003BAh ; Get status port in al , dx and al,080h ; Save VSYNC bit mov bl,al ; Save it XOP CX , CX VSYNC : in al,dx ; Read port mov ah , al and ah,080h ; Isolate VSYNC cmp ah , bl jne found_Hercules LOOp VSYNC jmp Short Herc_exit found_Hercules: ; Now that we found it, try to identify which board and al , 070h cmp al,010h ; HGCPlus? jne not_HGCPlus or word ptr [Si] ,HGCPLUS jmp Short Herc_exit not_HGCPlus: cmp al , 050h jne is_HGC or word ptr [si] , HERCULESINCOLOR jmp short Herc_exit iS_HGC: or word ptr [si] ,HGC Herc_exit : pop ax pop bx pop cx pop dx pop si ret 98页 Hercules_detect endp ; Attempt to detect MDA card MDA_detect proc mov dx,003B4h ; If MDA, CRTC iS 3B4h Call CRTC_detect jne no_MDA mov ax,PrimarySystem ; Found MDA; primary or secondary? and ax,ADAPTer_SYSTEM_MASK cmp ax , UNKNOWN_ADAPTER je MDA_Primary or SecondarySystem ,MDA OR MDA_MONITOR jmp short found_MDA MDA_primary: or PrimarySystem , MDA OR MDA_MONITOR found_MDA: call Hercules_detect ; See if it's a Hercules board no_MDA: ret MDA_detect endp ; Attempt to detect CGA card CGA_detect proc mov dx,003D4h ; If CGA, CRTC iS 3D4h call GRTC_deteCt jne CGA_exit mov ax,PrimarySystem ; Found CGA; primary or secondary? and ax , ADAPTER_SYSTEM_MASK cmp ax,UNKNOWN_ADAPTER je CGA_primary or SecondarySystem , CGA OR CGA_MONITOR jmp short CGA_exit CGA_Primary: or PrimarySyStem , CGA OR CGA_MONITOR CGA_exit : cet GGA_detect endp ; Attempt to detect the CRT controller ; on entry, DX is the alleged CRTC port ; If detected, r.etucns Z set; else Z ceset CRTC_detect proc push dx ; Save registers push cx push bx push ax mov al,00Fh ; Select (alleged) cursor low out dx , al ; register inc dx ; Select register in al,dx ; Read old value mov bl,al ; Save it in BL mov al,066h ; Write new value out dx,al xor cx,cx ; Loop a while CRTC_Delay : nop loop CRTC_Delay in al,dx ; Get new value mov bh,al ; Save it mov al,bl ; Get old value Out dx,al ; Write it out Cmp bh,066h ; Is new value correct? pop ax ; Restore registers 99页 pop bX pop CX pop dX ret CRTC detect endp end 5.3视频功能 目前,我们已经了解了屏幕显示的工作方式,那么就可以试一试简单的功能,来看看 这些功能是怎样发挥作用的。视频功能象本书介绍的其它主题一样,它们也是在DOS和 BIOS中才有用的。但与其它的编程范畴不同的是,视频功能的优势只与BIOS相关。没有 任何DOS服务能用于屏幕控制;只有几个DOS服务能用于信息的屏幕显示。 DOS服务用起来更简单。每个DOS服务提供简单的输出机制,它能够重定向并与所 有的系统操作兼容。 BIOS服务对于不直接存取视频内存的重要编程,它们通常是被选择的功能。它们不 仅提供视频系统的广泛控制,而且要比DOS服务更快和灵活得多。 BIOS服务提供了对光 标、显示属性和其他控制的存取。 当用户盼望得到给CRT的最快的、可能的输出并且想避免使用BIOS以保持与普通 的MS-DOS系统尽可能多的兼容性时,DOS的一个未公开的功能Int 29h已被证明是很 方便的功能。 Int 29h不象未公开的DOS输出功能那样,它将字符放到屏幕上时并不每次 都检查Ctrl-C,所以它比较快。 要使用Int 29h,可将要显示的字符放进AL。寄存器,然后引入中断: mov al,‘A’ int 29h 与其他的服务不同,Int 29h使用业已存在的任何屏幕属性并且只识别响铃(bell, ASCII码07),cR(ASCII码13)和LF(ASCII码10)字符;所有其他的“控制”字符就会显 示在屏幕上。字符放置在最后一行的最后一列时,或者光标在最后一行的任意位置,此时 送出了LF,这两种情况下,屏幕都会自动地卷起一行。 因为Int 29h并未公开,所以在未来的DOS版本中可以取消它,但这不太可能,因为 Int 29h是DOS使用得最多的方法(从2.0版本开始)并且因为它是ANSI.SYS替换显示 驱动程序系统的内存部分。可是在使用它之前,应该估计一下所冒的风险(这就象使用别 的未公开的功能一样)。 记住:DOS和BIOS只是为产生屏幕提供了构件块。程序员的想象力和技巧是使程序 运行正常或看起来简捷的主要原因。如果用户所希望产生的显示能力不能与编程技术相 一致的话,那么他的显示器就会是徒有其表的。 5.3.1利用DOS和BIOS视频功能编程 在这一节,读者可以构造一些简单的窗口功能,以便看看BIOS和DOS功能的使用 100页 是多么地容易。本章的目的并非想建立一个完整的窗口系统—这种努力超出了本书的 范围。依据一些用来分析BIOS和DOS功能使用的简单的窗口显示功能,可以检查屏幕 操作。 testscn.c 程序是一个简单的测试,它先在屏幕上填满数据,然后清除屏幕中心的那个 窗口(见列表5.2)。可以向屏幕书写更多的数据,然后将原来的窗口数据返回并卷起来。 列表5.2 /* Testscn.c Listing 5.2 in DOS Programmer's Reference*/ #include<stdio.h> #include<dos.h> /* Prototypes */ Void Savewin(int lr,int lc, int rr, int rc); void clearwin( int lr,int lc, int rr,int rc); void putwin( int lr, int lc, int rr,int rc); void border( int lr,int lc, int rr,int rc); void upwin( int n, int lr, int lc, int rr,int rc); void gotoxy( int r, int c); void cls(void); void main() { int i; /*Display a screen full of lines*/ Cls(); for(i=0;i<50; i++) printf("DOS Programmer's Reference "); /* Save the data in the rectangle(5,5) tO (12,40), and then clear that area and put a border around it.*/ Savewin(5,5,12,40); clearwin(5,5,12,40); border(5,5,12,40); /* Wait 5 seconds and then scroll the screen again. (Note: Everything scrolls, including the window.)*/ sleep(5); gotoxy(24,0); for(i=0; i<50; i++) printf("This is the Second Screen of the Demo "); /* Wait 5 seconds and then Clear the window and fill it.*/ sleep(5); clearwin(5,5,12,40); putwin(5,5,12,40); /* Scroll the inside of the window up one line every 2 seconds for 10 steps,*/ for(i=0;i<10; i++) { sleep(2); upwin(1,6,6,11, 39); } /* Finally,clear the screen again and then end the program. */ Cls(); } testscn.c 建立在文件windows.c、screen.c 和chario.c 所包含的3个简单功能集合体 101页 之上。window.c(见列表5.3) 操纵着testscn.c所调用的窗口功能。借助于window.c中的 函数,可以完成下列工作: savewin() 将窗口当前数据保存起来 clearwin() 清除窗口 putwin() 把数据放进某个窗口 border() 围绕窗口设置一个边界 upwin() 将窗口往上滚动 这个小的函数集合体没有进行高级复杂的尝试。它只操纵单一的、未覆盖的窗口,如 那些可能用于显示帮助信息的窗口。因为它没有使用比BIOS功能更低的东西,所以它与 其他环境,如DESQview兼容。 列表5.3 /* Window.c Listing 5.3 of DOS Programmer'S Reference*/ #include #include #define VIDEO 0x10 /* Basic screen-size definitions*/ #define LINES 24 #define COLS 80 /* Prototypes*/ void gotoxy(int i,int j); void rch(char *Ch, char *attr); void wch(char ch,char attr); void border(int lr,int lc, int rr, int rc); /* Structure for each Character position--character and attribute.*/ Struct charpos{ char ch; char att; }; /* The screen is made up of LINES*COLS character positions*/ Struct Charpos Screen[LINES][COLS]; /* Function: Savewin()*/ void savewin(lr,lc,rr,rc) int lr,lc,rr,rc; { int i,j; for(i=lr;I<=rr;i++) for(j=lc;j<=rc;j++){ gotoxy(i,j); rch(&screen[i][j].ch,&screen[i][j].att); } } /* Function: Clearwin()*/ void Clearwin(lr, lc, rr, rc) int lr, lc, rr,rc; { union REGS regs; 102页 regs.h.ah = 0x06, regs.h.al = 0; regs.h.bh = 7; regs.h.ch=lr; regs.h.cl = lc, regs.h.dh=rr; regs.h.dl=rc; int86(VIDEO, ®s, ®s); } /* Function: putwin() */ void putwin(lr,lc,rr,rc) int lr, lC, rr, rc; { int i, j; for(i=lr;i<=rr;i++) for(j=lc; j<=rc; j++) { gotoxy(i, j); wch(screen[i][j].ch, screen[i][j].att); } border(lr,lc,rr,rc); } #define VERTLINE 186 #define UPPERRIGHT 187 #define LOWERRIGHT 188 #define LOWERLEFT 200 #define UPPERLEFT 201 #define HORIZLINE 205 /* Function: border() */ void border(lr,lc,rr,rc) int lr,lc,rr,rc; { int i,j; for(i=lr;i<=rr;i++) { gotoxy(i,lc); wch(VERTLINE, 7); gotoxy(i, rc); wch(VERTLINE, 7); if(i==lr||i==rr) { for(j=lc; j<=rc;j++) { gotoxy(i,j); wch(HORIZLINE, 7); } if(i==lr) { gotoxy(lr, lc); wch(UPPERLEFT, 7); gotoxy(lr, rc); wch(UPPERRIGHT, 7); } if(i==rr) { gotoxy(rr,lc); wch(LOWERLEFT, 7); gotoxy(rr,rc); wch(LOWERRIGHT, 7); } } } } /* Function: upwin() */ void upwin(n,lr,lc,rr,rc) int n,lr,lc,rr,rc; { union REGS regs; regs.h.ah=0x06; regs.h.al=n; 103页 regs.h.bh=7; regs.h.Ch=lr; regs.h.cl=lC; regs.h.dh=rr; regs.h.dl=rc; int86(VIDEO,®s,®s); } screen.c中的功能操纵与屏幕相关的函数,如光标定位和清除屏幕(分别是gotoxy() 和cls()函数)。注意cls()只是clearwin(),它在屏幕的左上角和右下角设置数值,以对应 整个屏幕。 screen.c函数对于整个屏幕显示是全局的(见列表5.4)。它们在一个屏幕宽度的基础 上进行活动。window.c函数则在某个窗口内工作。另外的函数可以添加进来操纵一个窗 口内的工作。 列表5.4 /* screen.c Listing 5.4 of DOS Programmer's Reference*/ #include<Stdio.h> #include<dos.h> #define VIDEO 0X10 /* Function:gotoxy()*/ void gotoxy(r,c) int r,c; { union REGS regs; regs.h.ah=0x02; regs.h.bh=0; regs.h.dh=r; regs.h.dl=C; int86(VIDEO,&regs,&regs); } /* Function: cls()*/ void cls() { union REGS regs; regs.h.ah=0x06; regs.h.al=0; regs.h.bh=7; regs.h.ch=0; regs.h.Cl=0; regs.h.dh=25; regs.h.dl=80; int86(VIDEO,&regs,&regs); } 在程序的最低层,屏幕字符函数使用户能看看单个的屏幕位置或者使用BIOS屏幕 显示函数去改变这个位置(见列表5.5)。 104页 列表5.5 /*Chario.c Listing 5.5 of DOS Programmer's Reference*/ #inClUde<Stdio.h> #include #include #define BOOL int #define VIDEO 0x10 /* PrototypeS*/ void cls(void); static int cpage=0; /* Current display page*/ /*Function:gotoxy()*/ void gotoxy(r,c) int r,C; { union REGS regs; regs.h.ah=0x02; regS.h.bh=cpage; regs.h.dh=r; regS.h.dl=C; int86(VIDEO,®S,®S); } /*Function:cls()*/ void cls() { union REGS regs; regS.h.ah=0x06; regs.h.al=0; regs.h.bh=7; regS.h.Ch=0; regS.h.cl=0; regS.h.dh=25; regs.h.dl=80; int86(VIDEO,®s,®s); } /* Function:setpage()*/ void setpage(n,clrflg) int n; BOOL clrflg; { union REGS regs; cpage=n; regs.h.ah=0x05; regs.h.al=n; int86(VIDEO,®s,®s); if(clrflg) cls(); } /*Function:pgprint()*/ 106页 void pgprint(str) char*str; { union REGS regs; regS.h.ah=0x0e; regs.h.bh=cpage; while(*str){ regs.h.al=*str; int86(vIDEO,&Pegs,&regS); str++; } } 在列表5.7中,加上pgprint()函数使用户可以将线打印到当前页上。通过向屏幕功 能添加页控制,可以建立在其中使用了窗口和页功能的程序以保存显示内容,而只需要变 换显示页就能快速找到显示内容。 testpage.c样本程序能让用户尝试一下新的setpage() 和pgprint()函数(见列表5.8)。 列表5.8 /* Testpage.C Listing 5.8 of DOS Programmer'S Reference*/ #inClude<Stdio.h> #inClude<dOS.h> #define FALSE 0 #define TRUE !FalSE /*prototypes*/ void setpage(int n, int clrflg); void pgprint(char*str); void main() { int i; Setpage(1,TRUE); for(i=0;i<50;i++) pgprint("DOS Programmer'S Reference"); Sleep(5); setpage(0,FALSE); } 如果只有单色监视器系统,那么什么也看不到。当执行setpage()函数时,事实上屏幕 并未改变;用户只是看到有几秒钟所有的东西都保持静态,然后就回到下一个活动。但是 如果有CGA或EGA监视器或更好的监视器,那么执行程序时,屏幕会改变井显示测试 行,然后返回到屏幕已显示的相同内容上去。 对于所有的函数,当前显示页的使用远没有未显示页的工作那样令人难忘。将显示页 包含进函数变量之中,那么就可以将函数如gotoxy()用于尚未显示的页。然后可以构造一 个完整的显示,而且当它成为当前显示时,用户看到的是瞬间的显示。 5.4打印机功能 打印机是能够直接控制的输出设备之外的唯一的重要输出设备。它们的功能比屏幕 107页 功能要简单得多,因为它们只涉及字符输出,并最小程度地与打印机的输入有关。 输出给打印机的最简单的方法是利用DOS层的打印机输出功能(Int 21h,功能 05h),如列表5.9所示。该功能使用户能将字符输送给打印机设备。如果使用该功能出现 了错误,通过引入关键出错处理程序,DOS就可以处理出错情况。 列表5.9 /*prtout.c Listing 5.9 of DOS Programmer'S ReferenCe*/ #inClude<Stdio.h> #include<dOS.h> /*Prototypes*/ VOid OUtprt(Char*str); void main() { OUtprt("This is a line to the printer\012\015"); } void outprt(str) char*Str; { union REGS regs; regs.h.ah = 0x05; while(*str){ regS.h.dl=*str; intdos(&regs,&regs); str++; } } 还可以在更低的级别引入BIOS打印功能(Int 17h)来获得对打印功能的更大控制。 在这个级别上,如果需要,可以在程序中直接检查打印机的状态,并回答打印机错误。列表 5.10显示怎样使用BIOS功能去处理打印机接口的一个实例。 列表5.10 /*prtchk.C Listing 5.10 of DOS Programmer's Reference*/ #inClude<Stdio.h> #inClude<dOS.h> #include<stdlib.h> /* prototypes*/ VOid OUtprt(Char *str); int prtrdy(void); void main() { int i; for(i=0; i<10;i++){ if(!prtrDy())exit(1); outprt("This is a line to the printer\012\015"); } } 108页 #define PRINTER 0x17 void outprt(str) char*Str; { union REGS regs; regs.X.dX = 0; while(*str){ regS.h.ah=0x00; regs.h.al=*str; int86(PRINTER,&regs,&regS); putchar(*str); Str++; } } int prtrdy() { union REGS regs; regs.h.ah=2; regs.x.dX=0; int86(PRINTER,&regs,&regS); printf("printer Status:%x\n",regs.h.ah); if(regs.h.ah & 0x20) printf("printer out of paper\n"); if(regs.h.ah & 0x08) printf("printer I/O error\n"); return ((regs.h.ah&0x20)==0 && (regs.h.ah&0x08)==0); } 正如这些例子所示,可以直接从程序执行打印机输出,井同时了解打印机正做些什么 事情。 复杂的打印机控制功能如图形和字体控制,对于正在使用的打印机,它们是特定的。 除了将字符输送给打印机,BIOS和DOS都没有内在的功能,用于操纵打印机。有关特殊 化的打印机控制功能的讨论,超出了本书的范围。 5.5小 结 本章讨论了输出方面的众多功能,基本上都与视频显示有关。我们已经了解了可用的 视频方式以及可以使用的不同屏幕显示器。 在DOS中,屏幕控制局限于一些允许向屏幕进行基本输出的简单功能,别的很少使 用。更复杂的控制需要对Bios功能的存取,从而允许光标定位和字符属性控制。在BIOS 级别上,可以进行图形显示。屏蒂定位,甚至窗口功能。产生复杂屏幕的程序几乎总是必 须在BIOS或以下的级别上进行,是借助利用屏幕显示内存的直接访问来完成的。 打印机功能比屏幕功能受到了更多的限制。除了输出字符给打印机和检查打印机的 状态以外,别的任何事情都没有基本的功能。不能调用BIOS和DOs功能来完成打印机 图形输出,或特殊的打印机功能,如字体改变。任何特别的控制都必须由用户的程序来执 行。 第6章讨论输入设备,它是对本章内容(输出功能)的补充。