首页 > 技术文章 > DOS程序员手册(三)

Chaobs 2014-07-11 21:02 原文

56页
    第4章DOS和BIOS接口
    本章介绍了用户程序访问DOS内核和BIOS所提供的各种服务的方法。为了访问这
些服务,我们可以从任何编程语言中调用各个软件中断,这些中断便是我们在本书中要重
点地讨论的内容。用户当然不必了解访问系统资源的所有细节,但要入门,确实要学习相
当多的这方面知识。
      本书重点介绍的是四种编程语言:汇编语言、C(极c++)、Pascal和BASIC。所讨论
的实现程序分别有Microsoft Macro Assembler(宏汇编)、Microsoft C/C++、Borland C/
C++、Turbo Pascal 7.1版(以及对早期版本的少量说明)和Microsoft Quick BASIC。所
有四种语言都带有允许直接访问DOS和BIOS功能的特性。但是,因为不是每一个功能
都是由语言内带特性所能提供的,因此,有些操作读者不得不自己去编程实现。在本章中,
我们将了解到哪些是由所学语言本身所具备的,哪些是读者必须自己去实现的,以及如何
去实现它。
                4.1从程序中访问DOS和BIOS
      要访问DOS和BIOS资源,只需按照下述简单的步骤来操作:
      1.以相应的值装入CPU的寄存器。
      2.产生一个软件中断来调用所需的硬件中断。
      3.通过CPU的寄存器来返回中断结果(如果有)。
      正常情况下,寄存器中的值都是8位或16位的数值参数或大型数据结构的地址。在
本书中所讨论的所有语言,都有一种约定的方式来装入CPU的寄存器,产生中断,并读取
返口值。有关CPU寄存器及其它们的用途,可参见第2章“DOS系统的结构”。
      图4.1以图解方式列出了在一个系统调用之前或之后的寄存器中的内容。
      根据所要调用的资源,在装入寄存器和产生中断之前,可能需要做一些额外的工作。
例如,许多面向文件的服务,都要求在中断产生之前,在寄存器中装入一个串或其它数据
结构的段:偏移值地址。(如果不熟悉表示地址的段:偏移值形式,可参阅第2章中的内存
分段与8086)。
      首先,用户程序必须得到数据项的地址。得到一个项的地址是一个相对简单的过程:
而如果有什么复杂之处的话,恐怕是与用户所使用的编程语言有关。对本书所涉及的每一
种语言,本章后面都有一个节来专门介绍如何得到数据项地址的方法。
 
57页
            图4.1一个典型的系统调用之前和之后,寄存器中的内容
    数据串或其它的项必须以某种相应的固定形式组织起来。许多DOS功能都要求所使
用的带参数采用ASCIIZ(ASCII加零)格式:各个字符以ASCII代码格式设置,而该串的
最后一个字符则是ASCII字符零(在C语言中为\0,在BASIC中是CHR$(0),而在Pas- 
cal里是chr(0)。图4.2显示了一个ASCIIZ的结构。
                                图4.2一个ASCIIZ串的结构
    如果用户程序是用C来写成的,读者也许会知道ASCIIZ在C语言中是如何精确地
存放它的格式的。在BASIC或Pascal里,要创建一个ASCIIZ,还额外需要一个步骤。在本
章后面的一些例子则显示了是如何完成这些工作的。
    因为汇编语言能提供对CPU和系统资源的最直接访问,因此,在后面各节中的介绍
性例子都用汇编语言来写成。有关访问DOS和BIOS资源基本原则的简介,可阅读下面
的各个小节;这些例子都非常清楚,哪怕是读者对汇编语言并不十分熟悉。
    在本章后面,将介绍如何通过高级语言来访问操作系统。首先,让我们先看看一些简
单的汇编语言例子,来探讨一下DOS和BIOS接口。
4.1.1一个对DOS的简单调用
    在本书中所介绍的每种编程语言,都有几个服务能提供对基本的DOS和BIOS中断
的访问。在这些语言中,最简单的语言是汇编语言,因为汇编语言允许程序直接访问Int
(中断)指令,而Int指令能直接地产生对BIOS或DOS功能的访问。
 
58页
      下面的代码片断,使用Int 21h的功能02h,将字符X输出到控制台,这是一个典型的
对DOS的访问:
      mov ah,2        ;字符输出功能
      mov dl'X'       ;字符X
      int 21h         ;执行DOS功能中断
      使用Int 21h功能02h确实很简单:
    1.以相应的值装入所需的寄存器:将值2装入AH来选择DoS功能2(控制台字符
输出)。字符X装入到DL中。
    2.产生中断:汇编语言中的助记符Int后面所跟的值21h,是通用的DOS中断号。因
为Microsoft的Macro Assembler(宏汇编程序,MASM)是一种衡量汇编语言兼容性的标
准,因此,用户所选择的任何汇编语言中都应该可以使用Int指令。
      因为Int 21h的功能2不返回任何值,因此这里就少了DOS调用模型的第三部分。下
面的例子则演示了第三步,并介绍了如何得到和传递比16位“宽”的数据项的地址给
DOS的基本技巧。
4.1.2传递字符串地址给DOS
      前面已提到过,当数据项的内容比16位多时,就需要将数据项的段和偏移值地址放
入到cpu的寄存器中。下面的代码片断演示了传递字符串地址的一种方法:
        ;Path_name含有将要打开的文件的名字
        mov ax, seg Path_Namee  ;路径的段地址
        mov ds,ax              ;放入Ds
        mov dx,offset Path_Name ;路径的偏移值 
        mov al, c2h            ;打开文件的模式
        mov ah,3dh             ;打开文件
        int 21h                   ;DOS中断
        jc error                   ;如果出错,置进位位
        mov file_Handle,ax      ;保存文件句柄
      此代码片断使用Int 21h,功能3Dh来打开一个文件。该功能要求DS和DX相应地包
含文件路径名的段地址和偏移值。该代码段的前三行按要求装入寄存器。接下来的一行
将C2h放入AL;AL中的值说明该文件被打开的模式(有关此值的模式编码将在本书的
“DOS参考手册”部分里详细描述)。将各寄存器正确地装入后,便随后产生DOS中断。
      像大多数DOS服务一样,如果服务失败,就会设置进位位(此处表示文件不能打开);
如果已设置了进位位,程序于是便跳转到一个处理错误的例程(jc Error)。但是,如果进位
位清除,则表明打开文件成功,文件句柄(由AX返回)放入内存中的一个位置处,以备将
来使用。
      注意最初的DOS服务(即由DOS1.0版提供的)并不使进位标志来指示出错。就像
cp/M中的服务(DOS的祖先是CP/M)一样,它们在AL寄存器中返回它们的信息。自
DOS1.0以来,一致性并不总是保持得那么坚定,因为,自1.0版以后,也并不是所有的随
 
59页
后服务都用进位标志来指示出错。某些服务(如3.0版中可用的Get PID服务)是不可能
出错的,因为它只不过返回由DOS保存在内存中的内容给调用者。这些调用有时会清除
标志位,但是有些DOS版本则直接从服务中返回,而将进位标志仍保持为调用DOS前的
状态。
      因此有必要指出的是,标志位是否指示出错,要看调用的功能在文档中是否是这么说
明的。如果此功能并没有正式公开,那么可参看本书后面参考手册中的那一部分,看看此
标志位是否指示出错,如果是,再看看它指示的什么错误。
                    4.2高级语言资源
    高级语言提供了许多不同的方法来调用DOS和BiOS例程。每种语言都有一种独特
的方法,在其它的语言中往往不能重复这一方法。甚至是在同一种语言内,不同的实现厂
家及不同的版本,也可能采用了不同的方法。 Turbo Pascal 3.0就提供了一种非内带的方
太式来访问所有的文件(满足通配符文件)(如C:\QTRLY\QTR?1993.DAT)。为了访问这
样一组文件,编程者不得不多写两个过程:一个是调用DOS Int 21h的功能4Eh来找出满
足匹配文件名说明的第一个文件,另一个则用于调用功能4Fh来找出满足匹配文件名说
明的余下文件。当Turbo Pascal 4.0推出后,它不仅允许用户自己去生成这样的例程,并
将它们保存到一个运行时刻库中,而且还提供了两个过程一FindFirst和FindNext来完
成这项工作(与其它一些过程一起,放在一个名为“unit”的运行时刻库中)。而到了Turbo
Pascal 5.0的问世,连这些例程的源代码也都可以得到了(尽管需要额外付费)。
    在以下各小节中,每一节都给出了两个实例程序。对每种语言,第一个实例程序是一
个较简单的程序,用于说明访问操作系统资源的基本方法,它使用BIOS Int 17h功能2来
检查打印机的状态。第二个例子则相对地较复杂一些,它解释了从系统调用中返回的结
果。
    较复杂的例子在不同的语言中差别很大。所遵循的原则是,用户不必再做无谓的工
作,给出的编程例子,在所选择的语言中不能提供这样的服务。BASIC例子显示了如何使
用DOS Int 21h的功能4Eh和4Fh来获取满足带有匹配符的文件,并将那些文件名赋值
给BASIC变量。因为类似的功能已由Turbo Pascal及C中内带,所以在这两种语言中给
出的例子就会执行不同的任务。Turbo Pascal的例子使用DOS Int 21h功能57h来获取一
个给定文件的日期和时间。C语言的例子则使用DOS的Int 21h功能43h来获得与设置
文件属性(归档、隐藏、系统及只读属性)。这两个例子根据需要使用了不同的功能来完成
必需的设置及清除工作。这些辅助的功能调用分别在程序文本和注释中作了说明。
避免做无谓的工作
    许多高级语言都有预定义的函数、过程或变量来提供方便地访问系统资源的方式。在
大多数情形下,这些语言成份所访问的DOS和BIOS功能与用户自己在需要时直接编程
所调用的系统资源是相同的;只不过提供该编程语言环境的厂家已为使用者编好了这些
代码。如果认为在此语言中使用系统资源,对你来说是件新鲜事——或者它是你所熟悉语
                                               
60页
言的一种新的实现版本,那么必须注意的一点就是,不要做事复的工作。在本书中反复强
调的一个原则是,仅在必要时才去访问系统。因此,作为编程者,应该尽量使用语言中内带
    的功能,除非有某些特别的要求,而所使用的语言资源满足不了这种要求。
      应该总是仔仔细细地阅读用户手册。下述基本建议值得反复强调:去查手册!许多程
序员,特别是那些新接触某个语言或操作系统的人,常常将大量的时间花费在一些无谓的
工作上。如果他们更仔细地阅读过所使用的语言手册,就不会浪费时间去编制一些业已存
    在的资源上。
    4.2.1C语言
      对与操作系统打交道来说,C是最合适的语言。如果读者读完了在其它节里介绍的其
它语言,就会发现,Pascal和BASIC中的高级特性都会导致使用者按照自己的想法去实
现,如果要深入地想看个究竟的话。因为这些语言在需要方便地访问DOS和BIOS资源
时,都禁止或限制用户访问系统级的数据结构和其它信息。
      由于C语言在访问高级和低级资源时都很方便,因此曾有人将它称为“高级汇编语
言”。当需要在字符或位级进行详细地设置或操作时,C能让使用者“像一个微处理器那样
    去考虑问题”。
      C与操作系统有着一些类似的组成:许多C函数都是“穿着C语言外衣的DOS”。这
些函数通常都与DOS采用相同的参数,并返回相同的结果;确实,这些C函数作为输入的
数据结构和返回的输出值,都与在DOS中所用的参数相同。
      接下来,在本节中要给出一个更复杂的实例程序,我们给出了两个版本:chmd.C直接
调用DOS,而chmc.c则调用等价的C函数。而只有一个版本的较为简单的例子名为
_Pronok.c;该例子所提供的功能,在Borland C++库中是不能直接得到的。
      本节所给出的C程序都是由Borland C/C++编译器开发的,有关Borland c++与
Microsoft C之间的不同之处,可阅读例子代码片断中给出的注释。
      访问寄存器和产生中断
      在C和DOS接口中所使用的数据结构,是由REGS联合及SREGS与REGPACK结
构定义的。这些对象都是在头文件DOS.H中声明的。它们的声明在下面的代码片段中列
    了出来:
    struct WORDREGS{ 
        unsigned int ax, bx, cx, dx, si, di, cflag, flags;
    };
    /* Microsoft C lacks flags element*/
    struct BYTEREGS{ 
        unsigned char al, ah, bl,bh, Cl, ch, dl, dh;
    };
    union REGS{ 
        struct WORDREGS x; 
        struct BYTEREGS h; 
    };
 
61页
          struCt SREGS{
            unSigned int es;
            unSigned int cs;
            unSigned int ss;
            unsigned int ds;
        };
        struct REGPACK{         /*Not defined in Microsoft c*/
            unSigned r_aX,r_bX,r_cx,r_dx;
            unSigned r_bp,r_Si,r_di,r_dS,r_es,r_flags;
        };                         。
    此外,在BorlandC++中CPU各个寄存器的内容也呵以通过伪变量AX、_AL、AH
等得到。每个8086的通用寄存器、偏移值及段寄存器(除了IP)都有相对应地伪变量。也
可使用对应于16位或8位的寄存器的变量(但却未声明),把它门当作相应的unsigned
int和unsigned char类型:
      unsigned int _AX;
      unsigned char _AL;
    在前面介绍的伪变量是不能在Microsoft C中使用的。因为缺少这些伪变量会给程
序带来少许变化,因此必须仔细地阅读厂述程序片断,如果想要让它们通过Microsoft C
编译器的话。
    DOS.H头文件中包含有下列C函数的原型,用于产生软件中断:
      int int86(int intno,union REGS*inregs, 
                        union REGS*outregs);
      int int86x(int intno,union REGS*inregs,
                        union REGS*intregs,
                              struct SREGS*segregs);
      int intdos(union REGS*inreg,
                union REGS*outregs);
      int intdosx(union REGS*inregs,
                union REGS*outregs);
                struct SREGS*segregs);
    void intr(int int_type,struct REGPACKOpreg);
    intdos()函数能产生Int 21h——最基本的DOS中断;int86()*intr()则产生由该函
数的第一个参数(intno或int_type)所指定的中断。每一个intdos和int86都有一个x版
本,它除了使用通用寄存器及偏移值寄存器外,还将使用段寄存器,在Microsoft C里是不
能使用intr()函数的。
    列表4.1中所给出的PRNOK.C显示了如何使用int86()函数来验证LPT1是否联机
的技术。
62页
    列表4.1
          /*prnok.c 
              Listing 4.1 of DOS Programmer'S ReferenCe*/
          #include<cOnio.h>
          #include<dOS.h> 
          #define PRN_INT 0x17/*Printer·serviCes interrupt */
          #define STAT_RQ 0x02/*Status·request Service number*/
          int prnok(void)
          {
              union REGS regs;
              regS.h.ah=STAT_RQ;   /*AH=02 for printer status*/
              regs.x.dX=0;          /*DX=00 for LPT1*/
              int86(PRN_INT,&regs, &regs);
              return (((regs.h.ah&0x80)==0x80)?1:0);
          } 
          main()
          { 
              if(prnok())
                    cputs("Ready to print!\n");
              else
                  Cputs("Please cheCk the printer!\n");
          }
    有关BIOs打印机状态请求的说明
      每种语言的第一个实例程序都使用BIOS Int 17h功能2来验证LPT1是否已联机。
对于这一功能,将2放入AH,而将打印机号(0为LPT1,1为LPT2,依此类推)放入DX。
此功能在返回后,AH内存有打印机的状态,放在AH中的各位含义如下:
     位                               意义(如果置上,即为1时)
     0                                time-out(超时)
     1                                unused(未用)
     2                                 unused(未用)
     3                                 I/Oerror(I/O错) 
     4                                printer is selected(打印机已选)
     5                                out of paper(无纸)
     6                                Acknowledge(确认)
     7                                printer not busy(打印机不忙) 
    只有位0和位3至位7的意义已定义好,但是在两个已配置好的硬件上进行测试,在
相同的环境下,会返回不同的结果。在每个测试情况下,当打印机已加电并已联机的情况
下,AH的高位都已置上。但是,当一台Toshiba(东芝)P351打印机与一台IBM Personal
System/2 Model 50(PS/2 50型)计算机相连接时,当打印机已连接但没有加电时,程序会
报告出“Ready to print(已准备打印)”的信息。而将一台Epson的RX-O打印机与一台
COMPAQ便携式计算机相连时,程序则以“please check the printert”的信息作为响应(如
果打印机已连接但却没有加电时)。这只是一个演示性的例程;一个真正起作用的状态程
序则需要更复杂的逻辑。
63页
      获取和设置文件属性
    本节给出了两个C的实例程序,用于改变一个指定文件的属性。这两个程序类似于
Norton Utilities中的很有用的FA(文件属性)程序;不同的是,它们都只接收一个确定的
文件名,而不是包含有通配符的文件说明。为了保证程序尽可能地简单,程序也不包括查
询文件属性的选项或一次改变多于一个属性的选项。这两个程序在运行时,要求在命令行
上指定一个要设置或清除的属性来作为参数,如果给了一个不正确的参数,程序就会给出
一条出错信息,并终止运行。
    如果读者已精于C编程,则不难增加更进一步的选项(如“查询”),以扩展此程序的
功能。本书中已提供了足够的信息,因而也很容易加进处理带有通配符文件名的功能。
    这两个程序的不同之处在于:一个直接地调用了DOS中断,而另一个则使用了相应
的C函数来调用此中断。第一个例子在列表4.2中给出,它直接调用了DOS的Int 21h功
能43h。
    列表4.2
          /*Chmod.c
          Listing 4.2 of DOS programmer'S Reference */
        #include <coniO.h>
        #inClude<Stdio.h>
        #include<ctype.h>
        #inClude<process.h>
        #include<dOS.h>
      #include"attrmask.h"        /*omit for Turbo c 2.0 */
        #define GS_FATTR        0x43
        #define GET_FATTR     0x00
        #define SET_FATTR       0x01
        /*For Turbo C 2.0,add these #define statements:
                define ARCHIvE_BIT FA_ARCH
                define HIDDEN_BIT       FA_HIDDEN
                define RDONLY_BIT     FA_RDONLY
          define sYsTEM_BIT FA_sYSTEM
          */
      typedef enum{clr,set} clrorset;
      void showattr(int attr);
    int parsearg(char*thearg,clPorset*actiOn,char*selection);
      main(int argc,char*argv[])
        { 
          extern char*sys_errlist[];/*provided by Turbo
          eXtern int errnO;             /*Ditto*/
          union REGS regs;
          struct SREGS sregs;
        clrorSet action;
        char SelectiOn;
        unsigned attPib,setting; 
        int goahead;
    if(argc==3)goahead=parsearg(argv[2],&action,&selection);
                                                                                             
64页
if(!goahead)
{
if(argc=3)
{
 CPUtS("Can't parse");
cputs(argv[2]);
}
else
cputS("No input");
exit(1);
}
switch(selection){
caSe 'A':setting=ARCHIVE_BIT;break;
caSe 'H':Setting=HIDDEN_BIT;break;
Case 'R':Setting=RDONLY_BIT;break;
caSe 'B':Setting=SYSTEM_BIT;break;
default:
cputS("Bad input:");cputS(argv[2];cputS("\n\r");
exit(1);
}
regS.h.ah=GS_FATTR;
regS.h.al=GET_FATTR;
regS.x.dx=(unsigned)argv[1];/* offset of first argument*/
sregS.dS=_DS;
/* - - - - - - - - - - - - - - - - - - - - -
For Microsoft C,use the following in place of the preceding line:
segread(&sregs);
*/
intdosx(&regs,&regs,&sregs);/*Get the current attribute wOrd*/
if(!regs.x.cflag){/*If carry is clear,success*/
 attrib=regS.x.Cx;
cputs("- - - - - - - - - - - - Initial - - - - - - - - - - - - \n\r");
showattr(attrib);
if(action==clr){
setting=((-setting)&attrib);
}else{
setting=lsetting|attrib);
}
regs.h.ah=GS_FATTR;
cegs.h.al=SET_FATTR;
regs.x.cx=setting;
regs.x.dx=(unsigned)argv[1];
SregS.ds=_DS;
/* - - - - - - - - - - - - - - - - - - - - - -
For Microsoft C, use the following in place of the preceding line:
segread(&sregs);
*/
intdosx(&regs,&regs,&sregs);/*Set the attribute*/
attrib=regs.x.cx;
cputs(" - - - - - - - -  - - - - Final - - - - - - - - - - - - - \n\r");
showattr(attrib);
}else{/*That is,if carry is not set*/
char*msg;
cputs("function 0x43 failed:");
65页
               switch(regs.x.ax){ 
                case 1: msg="Bad function code\n";break;
                case 2: msg="Bad file name\n";break;
                case 3: msg="Bad path\n";break;
                case 5: msg="can't change attribute\n";break;
                default: msg="Unknown cauSe\n";
                }
                cputs(msg);
            } 
      }/*End main*/
    Int 21h功能43h需要下列输入值:
      寄存器                          值
        AH                       43h
      AL                          0表示获取文件属性,1表示设置文件属性
      DS:DX                      段:文件路径名偏
该程序使用了#define处理指示符来“命名"DOs功能以及所需的操作(获敢或设
置)。文件名是由命令行参数提供的。
    对于一个在Turbo C下以小内存模式编译的程序,应使用下列语句来将路径名的段
和偏移值放入DS和DX:
      regs.x.dx=(unsigned)argv(1);
      sregs.ds=_DS;
    在其它的内存模式下儒要用到其它的赋值方法。因为MicrosoftC缺少寄器伪变量,
因此应使用下列语句来取而代之:
      segread(&sregs);
    如果操作成功,就会清除进位标志,并且在cx中包含有文件的属性字。下面的表描
述了属性字中的每一位以及所对应的属性含义:
        位                    含义(如果置位,为1时)
        0                               Read Only(只读)
        1                               Hidden(隐藏)
        2                               System(系统)
        5                               Archive(归档)
                   
    如果某位已设置,则此文件就具备所对应的属性(设置归档位表示此文件自从被创建
成上次修改后还未备份)。
    为了使用方便和便于阅读,我们使用了#有define指示符来创建属性字中各个含义位
的位屏蔽字(这些定义包含在ATTRMASK.H文件中;参见以下的代码片断)。我们在程
序中使用位屏蔽字来读取和修改文件的属性字。
      /*attmask.h-from chapter 4 of DOS Programmer's Reference*/
                                                                
66页
#define ARCHIVE_BIT 0x20 /*Bit 5 of CX is the archive bit*/
#define SYSTEM_BIT 0x04 /*Bit 2 of CX is the system bit*/
#define HIDDEN_BIT 0x02 /*Bit 1 of CX is the hidden bit*/
#define RDONLY_BIT 0x01 /*Bit 0 of CX is the red-only bit*/
列表4.3及列表4.4则给出了第二个实例程序版本所使用的一些杂用功能。
列表4.3
/*parsarg.c
Listing 4.3 of DOS Programmer's Reference*/
#include<conio.h>
#include<ctype.h>
typedef enum{clr,set}clrorset;
int parsearg(char*thearg,clrorset*action,char*selection)
{
if(*(thearg)=='/'){
switch(*(thearg+2)){
case '+':
*action=set;break;
case '-':
 *action=clr;break;
default:
CPUtS("USe '+' tO Set Or '-' tO Clear\n\r");
return (0);
}
*selection=toupper(*(thearg+1));
return (1);
} else{
 cputs("Usage:chmd filename/xy\n\r"
"where x=A(acchive) or H(hidden) or\n\r"
"R(read-only) or S(system),\n\r"
"and y=+(set) or -(clear)\n\r");
return (0);
}
}
列表4.4
/*showatr1.c
Listing 4.4 of DOS Programmer's Reference*/
#include<conio.h> 
#include"attrmaSk.h"
#define CLEAR 0
#define isclear(x,y) ((x&y)==CLEAR)/*Parens around x,y?*/
#define putStat(x,y) CPUtS(iSClear(x,y)?"clear":"set")
void showattr(int attr) 
{
CputS("Archive System Hidden Read-only\n\r");
putStat(attr,ARCHIVE_BIT);
putStat(attr,SYSTEM_BIT);
putStat(attr,HIDDEN_BIT);
putStat(attr,RDONLY_BIT);
cputS("\n\r");
}
 
67页
    如果把列表4.2(此程序的第一版)与列表4.5(第二版)相比,读者就会发现,在使用
中断的程序中,进行出错检查的工作要比使用C库函数更复杂一些。因为不同的C在库
函数上有一些不同之处,所以本程序只能在Turbo C下编译如果使用Microsoft C则需
要作些修改,因为它缺少_chmod函数。
列表4.5
/*chmod2.c.
Listing 4.5 of DOS Programmer's Reference*/
#include <conio.h>
#include <stdio.h>
#include <io.h>
#include <ctype.h>
#include <process.h>
#include "attrmaSk.h"
#define GET_FATTR 0X00
#define SET_FATTR 0X01
typedef enum {clr,set}clrorset;
void showattr(int attr); /*Show file attribute*/
int parsearglchar*thearg,/*Parse command-line argument*/
clrorset*action,/*Clear or set attribute*/
char*selection);/*Selected attribute*/
  main(int argc,char*argv[])
{
extern char*sys_errlist[];/*PrOvided by Turbo C*/
extern int errno;/*Ditto*/
clrorset action;
char selection;
 unsigned attrib, setting;
int goahead;
if(argc==3)goahead=parsearg(argv[2],&action,&selection);
if(!goahead)
{
if(argc==3)
{
cputs("Can't parSe");
 cputs(argv[2]);
}
else
cputs("NO input");
exit(1);
}
switch(selection){
caSe 'A': Setting=ARCHIVE_BIT;break;
caSe 'H': Setting=HIDDEN_BIT;break;
caSe 'R': Setting=RDONLY_BIT;break;
CaSe 'S': Setting=SYSTEM_BIT;break;
default:
cputS("Bad inpUt:"); cputS(argv[2]);cputS("\n\r");
exit(1);
}
 
68页
            attrib=Chmod(argv[1],GET_FATTR);
            if(attrib!=0xFFFF){ 
                Cputs("··········Initial········\n\r");
                shOwattr(attrib);
                  if(actiOn==clr){
                      setting=((-setting)&attrib);
                  }else{ 
                      setting=(setting|attrib);
                  }
                attrib = chmod(argv[1],SET_FATTR,setting);
                cputs("··········Final············\n\r");
                  showattr(attrib);
            }else{ 
                  cputs("funCtion_chmodfailed: \n\r");
                cputs(SyS_errlist[errnOn]);cputs("\n\r");
            }
        }/*end main*/
      注意chmod()C库函数返回文件属性字作为一个函数的返回值(-1表示有一个错
误出现),而Int 21h功能43h则在寄存器CX中返回文件属性字,并通过设置进位标志来
表示出错(象大多数DOS功能那样)。因为由C库函数所返回的结果与DOS功能在CX
中返回的结果是相同的,因此可在两个程序中都使用showattr()函数来解释属性字的含
义。
4.2.2Turbo Pascal 
      Turbo Pascal 4.0版在3.0版的基础上作了改进。新的版本都提供了对调用DOS和
BIOs功能的极好的支持。随后在升级到5.0版时,又恢复了几个在由3.0版到4.0版升
级时所丢掉的几个很有用的特性,并且又第一次内带了一个内带的调试器,这对于复杂程
序是一个非常有帮助的工具。在Turbo Pascal 5.5版中。加进了面向对象编程的功能,并
且还支持多种数学协处理器。Turbo Pascal 6.0版中加进了Turbo Vision,这是一个用于
DOS程序的面向对象的应用程序框架,此外还增加了一个增强的开发环境。 Turbo Pascal
7.0版则继续地增加了一些功能,以添加支持Windows的功能。
      该编程语言中已含有大量的内带设施,来访问系统资源,并最大限度地减少了程序员
自己去编写他们自己的系统级代码的可能性。
      文件和控制台输入/输出功能满足了几乎所有程序员的要求,大多数文件和目录操作
(获取文件大小、获取当前目录、改变目录、创建目录等等)都得到了完全的支持。我们在本
章前面曾提到过,4.0版和5.0版简化了编写需要选择一组含有通配符文件说明的程序
的过程。
      这类资源支持是如此地齐备,因此,很难找出需要直接从程序中调用DOS或BIOS
服务的地方。当Turbo Pascal已具备了对控制台输入和输出操作的完备支持的函数和过
程时,还有什么必要非得自己去调用相应的DOS或BIOS呢?
      但是,毕竟还是有需要直接访问DOS和BIOS的时候,那就是当用户程序需要某些
功能,而Turbo Pascal语言不能提供此类功能时,本节给出的实例程序,只是出于指导性
    
69页
目的;此程序重复的是在高级语言Turbo Pascal里可以得到的操作。
    在这些程序中,第一个使用了BIOS的Int 17功能2来检验打印机是否已联机(类似
的结果—以及相同的复杂错误检查功能,都可通过Turbo Pascal语言的内带过程和函
数来实现)。第二个实例程序,同样也是重复了Turbo Pascal 4.0所内带的函数,来得到一
个文件创建或最近修改时的日期和时间。
    访问寄存器和产生中断
    在Turbo Pascal中,用于访问各个寄存器的数据结构位于Registers记录中。此记录中
的这一结构,是在Turbo Pascal 4.0的DOS联合中定义的,下面给出了它的代码;在3.0
版中,此记录则必须由用户来定义:
      Type
      { Registers record used by Intr and MsDos } 
        Registers = Record
              Case Integer of
              0: (AX,BX,CX,DX,BP,SI,DI,DS,ES,Flags : Word) ;
              1 : (AL,AH,BL,BH,CL,CH,DL,DH : Byte)
              End:<Rs>
    Turbo Pascal带有下列两个过程,用于产生中断:
    MsDos(vars Regs: Registers)
    Intr(IntNo :Byte; vars Regs :Registers)
    MsDos产生的是Int 21h中断,即“通用的”DOS中断。Intr则可以产生任何软件中
断,包括21h(在 3.0版中,Intr的第一个参数是一个整数)。
    Turbo Pascal程序的第一个例子—列表4.6 演示了一个简单的对BIOS的调用。
Printer Online函数调用BIOS的Int 17h功能2(打印机状态请求)以检查打印机是否已联
机。功能2在AH中返回一个字节,以指示打印机是否是忙、已选择、缺纸等等(必须保证已
读过本章前面的“高级语言资源”一节中对这个BIOS中断的说明)。但是,这个程序仅检
查了AH中的位7;如果此位已置上,表示打印机已选择上。
列表4.6
{ PRTRDEMO.PAS                } 
{ Listing 4.6 in DOS Programmer's Reference  } 
{ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = } 
Program PrinterDemo;
Uses Dos ;
      Function PrinterOnline : Boolean ;
                Const
                PrnStatuSInt :Byte: $17;
                StatusRequest : Byte: $02 ;
                PrinterNum : Word:0;{ 0for LPT1,1for LPT2, etc.} 
              Var
            Regs : Registers;       {  Type is defihed in Dos unit.} 
        Begin
        Regs.AH := StatusRequest;
        Regs. DX := printerNum;
70页
        Intr( PrnStatusInt,Regs); 
            Printeronline := ( Regs.AH and $80) : $80
            End; 
      Begin      {  Program} 
      If Printeronline  Then
          WriteLn( '  Ready to print !' ) 
      ElSe
          Writeln(' please check the printer! ' ) 
      End
    读取一个文件的日期及时间标记
    通过DOS的Int 21h,可以获得一个文件在创建成最近修改时的日期或时间。名为
GeteDateAndTime Pascal过程演示了DOS功能的使用方法(参见列表4.7)。
列表 4.7
    { GETDTTM.PAS } 
    { Listing 4.7 in DOS Programmer's Reference       } 
    {Designed to be called from FILEDTTM.PAS              } 
    { = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = } 
    Procedure GetDateAndTime( Pathname:PathStr ;
                                  Var DateWord , TimeWord : Word) ;
        const
              GetDate AndTime:Byte=$57;
              CloseFile:Byte=$3E;
            var
              Regs;Registers ;
              Handle : Word ;
        Function CarryClear (Regs : Registers) : Boolean ;
        Begin CarnyClear :=( ( Regs. Flags and  1=0)End ;
        { = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = } 
        Function GetFileHandle( Pathname : PathStr): Word; 
              Const GetHandle : Byte = $3D; ReadAccess : Byte = 0;
              Var PathSeg , PathOfs : Word ;
        Begin
        Pathname: = pathname+Chr( 0) ;
        PathSeg : = Seg(Pathname[1]) ; Pathofs := ofs(Pathname[1] );
        Regs. AH: =  GetHandle ;  Regs. AL : = ReadAccess ;
        Regs,DS : = pathSeg ; Regs. DX : = pathofs;
        MSDos (Regs);
        If CarcyClear(Regs) Then
              GetFileHandle : =  Regs.AX{ No semicolon before ElSe !} 
        Else
              Begin
              WriteLn('Handle function failed!' ) ;
                  EXit
              End;{If carry is clear} 
        End ;                        {  Function GetFileHandle} 
    { = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = }
    Begin          {  Procedure GetDateAndTime} 
    Handle := GetFileHandle(Pathname) ;
    Regs.AH : = GetDateAndTime ;  Regs. AL :=0;Regs. BX : = Handle;
    MSDos( Regs) ;
    If CarryClear(Regs ) Then
        Begin DateWord: = Regs.DX;TimeWord : = Regs.CX End
    Else
 
71页
      Begin
            DateWord: = 0, TimeWord: = 0, { Error flag for Cailer}  
            End; 
      Regs.AH := CloseFile;       Regs. BX := Handle; 
      MSDOS (Regs);
        If NOT CarryClear(Regs ) Then
            WriteLn( 
                ' Warning: Procedure GetDateAndTime did not close' ,                    ·
                pathname)
      End ;              { Procedure GetDateAndTime}  
    功能57h要求的正确输入项是一个文件句柄。Turbo Pascal具备完整的面向文件的过
程和函数,但是,如果要进行更细致的系统级文件管理,还得靠程序员自己去努力。为了使
用功能57h,GetDateAndTime过程,必须“翻遍”Turbo的正常文件管理例程。因此,我们不
使用Pascal的文件打开过程(即名为Assign和Reset或Rewrite),而调用DOS的Int 21h
功能3Dh。此系统调用是由GetFileHandle函数执行的,它被嵌套在GetDateAndTime过程
内。
    功能3Dh要求得到一个ASCIIZ串的地址,此串中包含有文件的路径名。因为Turbo
Pascal串不是由一个零字节来结尾的,所以该函数必须自己附加Chr(0)到串Pathname
上。因为在Turbo Pascal串中,第一个字节位置存放的是串的长度,但却不能包含在DOS
功能3Dh的串参数中,因此,我们将Pathname[ 1] 的地址传递给了此函数。有了Seg和Ofs
函数,就可以直截了当地访问串地址的两个组成部分。
    在获得了文件句柄后,GetDateAndTime将它传递给了DOS功能57h。AL寄存器设
置为0,指定DOS功能应该获取(而不是设置)文件的日期和时间。如果功能57h成功(由
进位标志清除来指明),它在CX和DX寄存器中相应地返回文件的时间和日期。
    列表4.8显示了程序FILEDTTM.PAS,则显示了如何使用Turbo Pascal的GetDate- ·
AndTime函数的。
列表4.8
{ FILEDTTM.PAS  
{Listing 4.8 in DOS Programmer's Reference         }
{= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = }
Program FileDateAndTime ;
      Uses Dos;
      Type
            PathName Type = String[64];
            String5 = String[5] ;
            { Max DOS path is 63 ; add 1 for the null.}
      Var
            Pathname : PathNameType;
          DateWord , TimeWord : Word ;     { Use Integer in 3.0} 
          Hours, Mins, Secs, Year, Month, Day : Integer,
          Ch : Char;
{ $i GETDTTM.PAS} 
{ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = } 
Begin         {  Program}
Write( ' pathname? >' );ReadLn(Pathname);
GetDateAndTime ( Pathname , DateWord , TimeWord);
If( DateWord<> 0) Then
72页
              Begin
              { Decode date} 
              Year     :=( ( DateWord AND $FE00)  SHR 9) +1980;
            Month:=(DateWord AND $01E0)SHR 5 ;
              Day      :=(DateWord AND $001 F);
              { Decode time} 
              Hours:=( TimeWord AND $F800)SHR 11 ;
              Mins    :=( TimeWord AND $07E0)  SHR 5 ;
              Secs     :=(TimeWord AND $001 F) SHL 1 ; {  Shift left to double} 
              WriteLn(' File Time:'  , Hours : 2,' :'  , Mins :02,':',Secs:2);
            Writeln(' File Date:' , Year:4,' /' , Month:02,'/'  ,  Day:2);
  End                      {  No semicolon before EIse} 
        Else
              Writeln(' GetDate And Time has failed!');
        End
4.2.3QuickBASIC
      QuickBASIC已包含了在BASIC及GW_BASIC中就已带有的,我们大家都很熟悉
的处理文件语句。如KILL FileName$语句删除文件,CHDIR用于改变当前目录,等等。
输入输出的例程也很灵活。
      但是,BASIC的文件处理功能在某些方面有些欠缺。例如,它处理包含有文件名通配
符(使用* 或?)的过程就非常地麻烦。我们不得不弯弯绕绕地,先将一个DOS命令传递给
SHELL,引导命令的输出到一个文件,然后从输出文件中将内容读进程序中,每次读一
行,分析出文件名(并要跳过一些干扰行)。磁盘驱动器为空、盘上贴有写保护标签,以及磁
盘已满,都有可能导致程序中止。并且,我们又怎么能知道所使用的SHELL的输出文件,
不会与某个已有的文件重名呢?
      而通过在DOS层上的编程,便可以避免所有这些麻烦。本节中给出的第二个实例程
序,便显示了如何去调用DOS的int 21h功能4Eh及4Fh,来找出匹配文件说明的所有文
件的名字,其说明中可以包含一个或多个通配字符(在DOS级上所进行的其它编程工作
的最佳应用,不仅可以使得编程更方便,而且也极大地方便了运行应用程序的各个用户)。
      首先,让我们来看一个简单的例子,它介绍了从BASIC中调用DOS的一些基础知
识。第一个QuickBASIC程序(就象其它语言中的第一个例子一样),使用BIOS的Int 17h
功能2来检查打印机是否已联机。
      访问寄存器和产生中断
      在QuickBASIC中,用于DOS及BIOS接口的基本数据结构是Register记录:
      TYPE Registers
            AX AS INTEGER
                BX AS INTEGER
                CX AS INTEGER
                DX AS INTEGER
                BP AS INTEGER
73页         SI AS INTEGER
              DI AS INTEGER
              SI AS INTEGER
              FLAGS AS INTEGER
              DS AS INTEGER
              ES AS INTEGER
      END TYPE
    我们通过DIM语句来声明寄器类型变量:
      DIM InRegs AS Registers,OutRegs AS Registers
因为这些变量遵守正常的作用范围规则,所以它们声明为过程内的局部变量。
    Quick BASIC内部带有下述两个过程,用于产生中断:
      CALL INTERRUPT(Int Number,InRegs,OutRegs)
      CALL INTERRUPTX(Int Number,InRegs,OutRegs)
    CALL INTERRUPT忽略DS和ES寄存器(或Registers记录参数中的DS和ES
域),而CALL INTERRUPTX则使用这些寄存器或域。如果想要在调用INTERRUPTX
时保持DS和ES不改变,则应给记录中的DS和ES域赋值-1。
    列表4.9显示了使用BIOS打印机状态请求的过程。因为此结果在各种硬件配置情况
下并不能保持完全的一致性,因此必须阅读"对BIOS打印机请求"一节中的注释(在本章
中有关C语言的小节内),这样才能在你的程序中使用此功能,并知道所起的作用。
列表4.9
"PRNOK.BAS-Listing 4.9 in DOS Programmer' s Reference
CONST PRN.Status.rq%=&H200            '2 in AH
CONST BIOS.PRN.INT%=&H17
TYPE Registers
        AX AS INTEGER
        BX AS INTEGER
        CX AS INTEGER
        DX AS INTEGER
        BP AS INTEGER
        SI AS INTEGER
        DI AS INTEGER
        FLAGS AS INTEGER
        DS AS INTEGER
        ES AS INTEGER
ENDTYPE
DIM InRegs AS Registers,outRegs AS Registers
InRegs.AX =  PRN. Status.rq% 
CALL INTERRUPT(BIOS.PRN.INT%, InRegs , OutRegs) 
IF((OutRegs. AX AND &H8000) = &H8000) THEN
        PRINT" Printer OK" 
ELSE
        PRINT" Please check the printer"
74页
        END IF
            END           ' PROGRAM
      找到匹配文件说明的文件
      通常,复杂而又细致的程序必须处理由带有通配符说明的一组文件。QuickBASIC并
未提供一个内带的设施,来找出匹配用户提供的文件名说明的一组文件。只有构造出这样
一个程序,才能使QuickBASIC语言发挥出更大的作用。
      DOS资源中,用于文件说明的是Int 21h的功能4Eh(FindFirst)和4Fh(FindNext)。这
些函数相应地找出第一个匹配的文件名以及任何剩下匹配的文件名。它们输出中的两个
地方——文件名和其它的信息,都位于DTA(磁盘传送区域)内。缺省时,DTA是一个128
字节的缓冲区,位于程序段前缀的偏移值80h处。但是,在本节中给出的样本程序,使用了
DOS功能1Ah来设置不同的DTA地址,来避免任何可能的对其它DOS功能的干扰。
      大多数BASIC的实现,也包括QuickBASIC在内,都没有将串保存在一个固定的位
置处。有一个4字节的串描述,保持着对每个串位置的跟踪。此描述符包含了一个16位的指
向该串的指针;这个指针中包含有进入该缺省数据区域的串的偏移值。我们不用担心去定
位此描述符并获取此地址,因为下面的QuickBASIC函数:
      SADD(TheString$)
    将返回作为参数的串的偏移值(如果需宴访问该串描述符,就可以通过VARSEG和
VARPTR函数来获取它的段值和偏移值)。SADD应该在用户程序访问此串之前立即被
调用,因为一个串可以在程序执行期间,在内存中“任意挪动”,特别是如果程序中改变了
串的长度。
      在列表4.10里列出的程序演示了使用功能4Eh和4Fh来获取匹配文件说明的文件名
的过程。
    列表4.10
    ' FILEDEMO.BAS- Listing  4.10 from DOS Programmer's Reference
    DECLARE SUB SetDTA(TheDTA$) 
    DECLARE SUB FindFirst(FileSpec$,FileName$) 
    DECLARE SUB FindNext ( FileName$) 
    DECLARE SUB BuildName (TheName$)
    TYPE Registers
        AX AS INTEGER
        BX AS INTEGER
        CX AS INTEGER
        DX AS INTEGER
        bP AS INTEGER
        SI AS INTEGER
        DI AS INTEGER
        FLAGS AS INTEGER
        DS AS INTEGER
        ES AS INTEGER
    END TYPE
    DTA$=SPACE$(43) 
75页
LINE INPUT "FileSpec? >" , FileSpec$
  CALL SetDTA( DTA$ )
 Get the first matching file name
CALL FindFirst ( ( FileSpec$) , FileName$)
IF FileName$ <> " " THEN
PRINT "First match: " ; FileName$
DO
CALL FindNext ( FileName$ )
IF FileName$ <> " "THEN
PRINT " Next match: " ; FileName$
END IF
LOOP UNTIL FileName$ = " "
ELSE
 PRINT "NO files match " ; FileSpec$
END IF
END  PROGRAM
 =====Subroutines =====
SUB BuildName (TheName$)
SHARED DTA$
EndofStr% = INSTR(31 , DTA$ , CHR$(0))
TheName$ = MID$(DTA$, 31 , Endofstr% - 31 )
END SUB
SUB FindFirst (FileSpec$, FileName$)
FileSpec$ = FileSpec$ + CHR$(0)  make ASCIIZ
DIM InRegs AS Registers, outRegs AS Registers
InRegs.AX = &H4E00  'find first matching file
InRegs . DX = SADD(FileSpec$) ' offset of FileSpec$
InRegs .DS = VARSEG(FileSpec$) ' seg of FileSpec$
InRegs.CX = 0 'normal files only- -no dirs, etc.
CALL INTERRUPT(&H21 , InRegs, OutRegs)
GOt a match? Yes, if CARRY FLAG(bit 0 of FLAGS) is Clear.
 
IF (OutRegs.FLAGS AND 1 ) = 0 THEN
CALL BuildName ( FileName$ ) :
ELSE 
FileName$ = "  "
END IF
END SUB
SUB FindNext (FileName$)
DIM InRegs AS Registers, outRegs AS Registers
InRegs.AX = &H4F00 'find next matching file
CALL INTERRUPT(&H21 , InRegs, outRegs)
IF (OutRegs.FLAGS AND 1 ) = 0 THEN
CALL BuildName ( FileName$ ) : 
ELSE
FileName$ =" "
END IF
END SUB
SUB SetDTA (DTA$)
DIM InRegs AS Registers, outRegs AS Registers
 Set the Disk Transfer Area address
InRegs.DX = SADD(DTA$) ' OffSet Of DTA
InRegs .DS = VARSEG(DTA$) ' segment Of DTA
InRegs.AX = &H1A00 'DOS function fon setting OTA addr
CALL INTERRUPT(&H21 , InRegs , outRegs)
76页
             no return value for function &H1A
          END SUB
      下面的过程:
        SetDTA(TheDTA$)
      将设置磁盘传送区的地址为它的串参数的地址:
      下面的过程:
        FindFirst(FileSpec$,FileName$)及FindNext (FileName$)
      将定位到所匹配的文件名。如果找到了匹配的文件名,这两个过程都把匹配的文件名
赋值给FileName$;如果未找到匹配的文件名,它们就会赋上空串(" " )。尽管此实例程序
只是简单地显示出文件名,但我们可以在自己的程序中利用所返回的结果来完成任何工
作。
                        4.3小    结
      我们在本章中介绍了在汇编语言、C、Pascal及BASIC内进行系统级编程的基础。用
于访问DOS和BIOS资源的过程有三个主要的组成部分:
      ·以相应的值装入寄存器
      ·产生一个软件中断
      ·在寄存器内返回中断的结果
      有时还需要一些额外的工作,如当应用程序需要将串或其它数据项的地址传送给系
统。每种编程语言都提供了各种资源,用于获取各个项的地址;使用各个地址的简单程度
依使用的各种的语言而有所变化。
      如果在已读完了本章各节中对每种语言(不仅仅是所选择的语言)的技术介绍后,读
者可能已注意到,在提供对DOS和BIOS资源的支持上,各种语言有很大的不同。在以后
进行的编程项目中选择语言时,应考虑到这些不同之处的。
 

    本章讨论视频显示器和打印机这两种基本的输出设备。它们可能是计算机编程中最

重要的设备,因为它们在程序和程序员之间担当着相互作用点的角色。

    大多数计算机书籍都将辅助设备(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<stdio.h>

    #include<dos.h>

    #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, &regs, &regs);

}

/* 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,&regs,&regs);

            }

      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<dos.h>

          #define VIDEO 0x10

            union REGs regs;

            /*FunctiOn:wch()*/

            void wch(Ch,attr)

                char ch,attr;

            {

                regs.h.ah=0x09;

                regs.h.bh=0;

                regs.h.bl=attr;

                regs.h.al=ch;

              regs.x.cx=1;

                int86(VIDEO,&regs,&regs);

            }

            /*FunctiOn:rch()*/

            void rch(ch,attr)

                char*ch,*attr;

            {

                regs.h.ah=0x08;

                regs.h.bh=0;

                int86(vIDEO,&regs,&regs);

                *ch=regs.h.al;

                *attr=regs.h.ah;

    }

     如果用户正在使用MicrosoftC/c++,那么他就没有版本4.0所需的sleep()函数。

该函数(见列表5.6)采用与Microsoft c/c++同样的路径。在向前推进之前,它要等待,

    直到指定数目的秒数过去。

      列表5.6

              /* Sleep.C

            Listing 5.6 of DOS programmer'S Reference*/

              #include<stdio.h>

          ;For use with Microsoft C/C++

              void sleep(n)

                  int n;

              {

                  long timeval, time();

                  timeval=time(NULL);

              while(time(NUL)<timeval+n);

              }

    5.3.2使用多个显示页

      因为用于显示这个简单窗口系统的函数只涉及一个显示页(0页),所以它们独立于

    所使用的监视器系统的类型之一。可以加上屏幕页控制,但并非所有的函数都允许它的存

    在。例如,翻卷函数的变量中没有页赋值变量。于是就不能使用这些函数来给显示内容编

    页码(如果用户编写了自己的窗口函数来直接存取显示器内存,那么就不会受此限制)。而

 

105页

且,功能0Eh不能适用于所有BIOS ROM的所有页。所有测试的非IBM的ROM中,该功

能只在当前显示的页上工作,而不在非显示页上工作。

     添加屏幕——页控制的一条途径是改变路径,使之包含一个当前屏幕页号以及设置

该页号的途径。例如,可以改变screen.c,使之包含一条setpage命令(见列表5.7)。

列表5.7

/*Screen2.c

LiSting 5.7 of DOS Programmer's Reference*/

#include<stdio.h>

#include<dos.h>

#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,&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);

}

/* 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,&regs,&regs);

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()用于尚未显示的页。然后可以构造一

个完整的显示,而且当它成为当前显示时,用户看到的是瞬间的显示。

推荐阅读