【简介】 PC中最主要的难题之一,也是最容易引起误解的,就是系统调用。系统调用所代表的那些函数实际上是计算机的所有底层操作――屏幕和磁盘的控制,键盘和鼠标的控制,文件系统的管理,时间,打印,这些只不过是系统调
PC中最主要的难题之一,也是最容易引起误解的,就是系统调用。系统调用所代表的那些函数实际上是计算机的所有底层操作――屏幕和磁盘的控制,键盘和鼠标的控制,文件系统的管理,时间,打印,这些只不过是系统调用所实现的一部分功能。
总的来说,系统调用往往涉及到BIOS(基本输入输出系统)。实际中有好几种不同的BIOS,例如主板的BIOS负责初始硬件检测和系统引导,VGA BIOS(如果有VGA卡的话)处理所有的屏幕处理函数,固定磁盘BIOS管理硬盘驱动器,等等。DOS是位于这些低级BIOS之上的一个软件层,并且提供了进入这些低级BIOS的基本接口。一般说来,这意味着有一个DOS系统调用可以调用几乎所有你想使用的系统功能。实际上,DOS将调用相应的一种低级BIOS来完成所要求的任务。在本章中,你将会发现你既可以调用DOS来完成一项任务,也可以直接调用低级BIOS来完成相同的任务。
14.1 怎样检索环境变量(environment variables)的值?
ANSI C标准提供了一个名为getenv()的函数来完成这项任务。getenv()函数很简单一把指向要查找的环境串的指针传递给它,它就返回一个指向该变量值的指针。下面的程序说明了如何从C中获得环境变量PATH的值:
# include <stdlib. h>
main(int argc, char * * argv)
{
char envValue[l29]; / * buffer to store PATH * /
char * envPtr = envValue ; / * pointer to this buffer * /
envPtr = getenv("PATH"); /* get the PATH */
printf ("PATH= %s/n" , envPtr) ; / * print the PATH * /
}
如果你编译并运行了这个程序,你就会看到与在DOS提示符下输入PATH命令完全相同的结果。事实上,你可以用getenv()检索AUTOEXEC.BAT文件中的或者系统引导后在DOS揭示符下输入的所有环境变量的值。
这里有一个小技巧。当运行Windows时,Windows设置了一个名为WINDIR的新的环境变量,它包含了Windows目录的路径全名。下面这段简单的程序用来检索这个串:
# include <stdlib. h>
main(int argc, char * * argv)
{
char envValue[l29];
char * envPtr = envValue ;
envPtr = getenv("windir");
/ * print the Windows directory * /
printf("The Windows Directory is %s/n" , envPtr);
}
这个程序还可以用来判断当前是否正在运行Windows,以及DOS程序是否运行在一个DOS shell下,而不是运行在“真正的"DOS下。注意,程序中的windir字符串是小写――这一点很重要,因为它对大小写是敏感的。如果你使用WINDIR,getenv()就会返回一个NULL串(表示变量未找到错误)。
用一putenv()函数也可以设置环境变量。但要注意,该函数不是一个ANSI标准函数,在某些编译程序中它可能不以这个名字出现,或者根本就不存在。你可以用一putenv()函数做许多事情。实际上,在上面那个例子中,Windows正是用这个函数创建了windir环境变量。
请参:
14.2 怎样在程序中调用DOS函数?
14.3 怎样在程序中调用BIOS函数?
14.2 怎样在程序中调用DOS函数?
其实,当调用printf(),fopen(),fclose(),名字以一dos开始的函数以及很多其它函数时,都将调用DOS函数。Microsoft和Borland还提供了一对名为int86()和int86x()的函数,使你不仅可以调用DOS函数,还可以调用其它低级函数。用这些函数可以跳过标准的C函数而直接调用DOS函数,这常常可以节省你的时间。下面的例子说明了如何通过调用DOS函数,而不是getch()和printf()函数,从键盘上得到一个字符并将其打印出来(该程序需要在大存储模式下编译)。
# include <stdlib. h>
# include <dos. h>
char GetAKey(void);
void OutputString(char * );
main(int argc, char * * argv)
{
char str[l28];
union REGS regs;
int ch;
/ * copy argument string; if none, use "Hello World" * /
strcpy(str, (argv[1]== NULL ? "Hello World": argv[1])),
while ((ch = GetAKey()) ! =27){
OutputString(str);
}
}
char
GetAKeyO
{
union REGS regs;
regs.h. ah = 1; /* function 1 is "get keyboard character" * /
int86(0x21, ®s, ®s);
return( (char)regs. h. al) ;
}
void
OutputString(char * string)
{
union REGS regs;
struct SREGS segregs;
/ * terminate string for DOS function * /
* (string + strlen(string)) = '$';
regs.h. ah = 9; / * function 9 is "print a string" * /
regs.x. dx = FP_OFF(string) ;
segregs. ds= FP_SEG(string) ;
int86x(0x21, ®s, ®s, &segregs);
}
上例创建了两个函数来代替getch()和printf(),它们是GetAKey()和OutputString()。实际上,函数GetAKey()与标准c函数getche()更为相似,因为它与getche()一样,都把键入的字符打印在屏幕上。这两个函数中分别通过int86()(在GetAKey()中)和int86x()(在OutputString()中)调用DOS函数来完成所要求的任务。
可供函数int86()和int86x()调用的DOS函数实在太多了。尽管你会发现其中许多函数的功能已经被标准的C函数覆盖了,但你也会发现还有许多函数没有被覆盖。DOS也包含一些未公开的函数,它们既有趣又有用。DOS忙标志(DOS Busy Flag)就是一个很好的例子,它也被称作InDos标志。DOS函数34H返回指向一个系统内存位置的指针,该位置包含了DOS忙标志。当DOS正忙于做某些重要的事情并且不希望被调用(甚至不希望被它自己调用)时,该标志就被置为1;当DOS不忙时,该标志将被清除(被置为O)。该标志的作用是当DOS正在执行重要的代码时,把这一情况通知DOS。然而,该标志对程序员也是很有用的,因为他们能由此知道什么时候DOS处于忙状态。尽管从DOS 2.0版开始就有这个函数了,但因为Microsoft最近已经公开了这个函数,所以从技术角度上讲它已不再是一个未公开的函数。有几本很不错的书介绍了已公开和未公开的DOS函数,对这个问题有兴趣的读者可以去阅读这些书。
请参见:
14.3 怎样在程序中调用BIOS函数?
14.3 怎样在程序中调用BIOS函数?
与前文中的例子一样,在使用象一setvideomode()这样的函数时,将频繁地调用BIOS函数。此外,就连前文例子中使用过的DOS函数(INT 21H,AH=01H和INT 21H,AH=09H)最终也要通过调用BIOS来完成它们的任务。在这种情况下,DOS只是简单地把你的DOS请求传给相应的低级BIOS函数。下面的例子很好地说明了这一事实,该例与前文中的例子完成相同的任务,只不过它完全跳过了DOS,直接调用了BIOS。
# include <stdlib. h>
# include <dos. h>
char GetAKey(void) ;
void OutputString( char * );
main(int argc, char * * argv)
{
char str[128];
union REGS regs;
int ch;
/ * copy argument string; if none, use "Hello World" * /
strcpy(str, (argv[1] == NULL ? "Hello World" : argv[1]));
while ((ch = GetAKeyO) !=27){
OutputString(str);
}
}
char
GetAKey()
{
union REGS regs;
regs. h. ah = 0; /* get character */
int86(0xl6, &xegs, ®s);
return( (char)regs. h. al) ;
}
void
OutputString(char * string)
{
union REGS regs;
regs. h. ah = 0x0E; /* print character * /
regs. h. bh = 0;
/ * loop, printing all characters * /
for(; * string !='/0'; string+ + ){
regs. h. al= * string;
int86(0xl0, ®s, ®s);
}
}
你可以发现,唯一的变化发生在GetAKey()和OutputString()自身之中。函数GetAKey()跳过了DOS,直接调用键盘BIOS来获得字符(注意,在本例这个调用中,键入的字符并不在屏幕上显示,这一点与前文中的例子不同);函数OutputString()跳过了DOS,直接调用了Video BIOS来打印字符串。注意本例效率不高的一面――打印字符串的C代码必须位于一个循环中,每次只能打印一个字符。尽管Vidoeo BIOS支持打印字符串的函数,但C无法存取创建对该函数的调用所需的所有寄存器,因此不得不一次打印一个字符。不管怎样。运行该程序可以得到与前文例子相同的输出结果。
请参见:
14.2 怎样在程序中调用DOS函数?
14.4 怎样在程序中存取重要的DOS内存位置?
与DOS和BIOS函数一样,有很多内存位置也包含了计算机的一些有用和有趣的信息。你想不使用中断就知道当前显示模式吗?该信息存储在40:49H(段地址为40H,偏移量为49H)中。你想知道用户当前是否按下了Shift,Ctrl或Alt键吗?该信息存储在40:17H中。你想直接写屏吗?单色显示(Monochrome)模式的视频缓冲区起始地址为B800:O,彩色文本模式和16色图形模式(低于640×480 16色)的视频缓冲区起始地址为B8000:0,其余标准图形模式(等于或高于640×480 16色)的视频缓冲区起始地址为A000:O,详见14.8。下面的例子说明了如何把彩色文本模式的字符打印到屏幕上,注意它只是对前文中的例子做了一点小小的修改。
# include <stdlib. h>
# include <dos. h>
char GetAKey(void) ;
void OutputString(int, int, unsigned int, char * );
main (int argc, char * * argv)
{
char str[l28];
union REGS regs;
int ch, tmp;
/ * copy argument string; if none, use "Hello World" * /
strcpy(str, (argv[1] == NULL ? "Hello World" : argv[1]));
/ * print the string in red at top of screen * /
for(tmp = 0;((ch = GetAKeyO) ! = 27); tmp+=strlen(str)) {
outputString(0, tmp, 0x400,str);
}
}
char
GetAKey()
{
union REGS regs;
regs. h. ah = 0; / * get character * /
int86(0xl6, ®s, ®s);
return((char)regs. h. al);
}
void
OutputString(int row, int col, unsigned int video Attribute, char * outStr)
{
unsigned short far * videoPtr;
videoPtr= (unsigned short far * ) (0xB800L <<16);
videoPtr + = (row * 80) + col; /* Move videoPtr to cursor position * /
videlAttribute & = 0xFF00; / * Ensure integrity of attribute * /
/ * print string to RAM * /
while ( * outStr ! = '/0'){
/ * If newline was sent, move pointer to next line, column 0 * /
if( (* outStr == '/n') || (*outStr == 'V') ){
videoPtr + = (80- (((int)FP-OFF(videoPtr)/2) % 80));
outStr+ + ;
continue;
}
/ * If backspace was requested, go back one * /
if( *outStr = = 8){
videoPtr -- ;
outStr++ ;
continue;
}
/* If BELL was requested, don't beep, just print a blank
and go on * /
if ( * outStr = = 7) {
videoPtr+ + ;
outStr++ ;
continue ;
}
/ * If TAB was requested, give it eight spaces * /
if ( * outStr == 9){
* videoPtr++ = video Attribute | ' ' ;
* videoPtr++ = video Attribute | ' ' ;
* videoPtr++ = video Attribute | ' ' ;
* videoPtr++ = video Attribute | ' ' ;
* videoPtr++ = video Attribute | ' ' ;
* videoPtr++ = video Attribute | ' ' ;
* videoPtr++ = video Attribute | ' ' ;
* videoPtr++ = video Attribute | ' ' ;
outStr+ + ;
continue;
}
/ * If it was a regular character, print it * /
* videoPtr = videoAttribute | (unsigned char) * outStr;
videoPtr+ + ;
outStr + + ;
}
return;
}
显然,当你自己来完成把文本字符打印到屏幕上这项工作时,它是有些复杂的。笔者甚至已经对上例做了一些简化,即忽略了BELL字符和一些其它特殊字符的含义(但笔者还是实现了回车符和换行符)。不管怎样,这个程序所完成的任务与前文中的例子基本上是相同的,只不过现在打印时你要控制字符的颜色和位置。这个程序是从屏幕的顶端开始打印的。如果你想看更多的使用内存位置的例子,可以阅读20.12和20.17――其中的例子都使用了指向DOS内存的指针来查找关于计算机的一些有用信息。
请参见:
14.5 什么是BIOS?
20.1 怎样获得命令行参数?
20.12 怎样把数据从一个程序传给另一个程序?
20.17 可以使热启动(Ctrl+Alt+Delete)失效吗?
14.5 什么是BIOS?
BIOS即基本输入输出系统,它是PC机的操作的基础。当计算机上电时,BIOS是第一个被执行的程序,DOS和其它程序都通过BIOS来存取计算机内部的各种硬件设备。
然而,引导程序并不是计算机内唯一被称为BIOS的代码。实际上,PC机上电时要执行的BIOS通常被称为主板BIOS,因为它被存放在主板上。直到不久之前,这个BIOS还被固化在一块ROM芯片上,因而无法为了修改错误和扩充功能而重新编写它。现在,主板BIOS被存放在一块叫做Flash EPROM的可重新编程的存储器芯片中,但它还是原来的BIOS。不管怎样。主板BIOS会读遍系统内存,从而找到系统中其它一些硬件设备,这些设备都带有自身要使用的一些基础代码(即其它的BIOS代码)。例如,VGA卡就有其自身的BIOS,通常被称为Video BIOS或VGA BIOS;硬盘和软盘控制器也有一个BIOS,并且也在系统引导时被执行。当人们提及BIOS时,或者是指这些程序的集合,或者是指其中单独的一个BIOS,这两种说法部对。
根据上述介绍,你应该知道BIOS并不是DOS――BIOS是PC机中最底层的功能软件。DOS刚好位于BIOS上面的一层,并且经常调用BIOS来完成一些基本操作,而这些操作可能会被你误认为是"DOS"函数。例如,你可能会用DOS函数40H来把数据写到硬盘上的一个文件中,而DOS最终还是要通过调用硬盘BIOS的函数03来把数据写到硬盘上。
请参见:
14.6 什么是中断?
14.6 什么是中断?
首先,中断分硬件中断和软件中断两种。中断为计算机的硬件设备和软件"部件"提供了一种相互交流的途径,这就是它的作用。那么,都有哪些中断呢?它们又是怎样实现这种交流的呢?
PC机中的CPU通常都是Intel 80x86处理器,它有几条引脚用来中断CPU的当前工作,并使它转去进行其它工作。每条中断引脚上都连接着一些硬件设备(例如定时器),其作用是为这条引脚提供一个特定的电压。当中断事件发生时,处理器会停止执行当前正在执行的软件,保存当前的操作状态•然后去“处理”中断。处理器中事先已经装有一张中断向量表,其中列出了每个中断号以及当某个特定中断发生时所应执行的程序。
以系统定时器为例――作为要完成的许多任务中的一部分,PC机需要维持一天的计时工作,其具体工作过程为:(1)一个硬件计时器每秒钟向CPU发出18次中断;(2)CPU停止当前的工作并在中断向量表中查找负责维持系统计时器数据的程序(这种程序叫做中断处理程序(interrupt handler),因为它的工作就是在中断发生时处理中断);(3)CPU执行该程序(将新的定时器数据存入系统内存),然后返回到刚才被中断的地方继续往下执行。当你的程序要求使用当前时间时,定时器数据就会按照你要求的格式被组织好并传给程序。以上的解释大大简化了定时器中断的工作情况,但它是一个很好的硬件中断的例子。
系统定时器只是通过中断机制发生的数百个事件(有时被称为中断)中的一个。在很多时候,硬仵并不参与到中断处理过程中去。换句话说,软件经常会通过中断来调用其它软件,并且可以不需要硬件的参与。DOS和BIOS就是这方面的两个主要例子。当一个程序打开一个文件,读/写一个文件,把字符写到屏幕上,从键盘那里得到一个字符,甚至询问当前时间时,都需要有一个软件中断来完成这项任务。你可能不知道发生了这些事情,因为这些中断都深藏在你所调用的那些无足轻重的小函数(例如getch(),fopen()和ctime())的后面。
在C中,你可以通过int86()和int86x()函数产生中断。int86()和int86x()函数要求用你想产生的中断号作为它们的一个参数。当你调用其中的一个函数时,CPU将象前面所讲的那样被中断,并俭查中断向量表,以找到需要执行的那个程序。在调用这两个函数时,通常将执行的是一个DOS或BIOS程序。表14.6列出了一些常见的中断,你可以通过它们设置或检索计算机的有关信息。注意这并不是一张完整的表,并且其中的每个中断都可以服务于数百种不同的函数。
表14.6 常见的PC中断
―――――――――――――――――――――――――――――――――――――
中断(hex) 描述
――――一――――――――――――――――――――――――――――――――
5 屏幕打印服务
10 视频显示服务(MDA,CGA,EGA,VGA)
11 获得设备清单
12 获得内存大小
13 磁盘服务
14 串行口服务
15 杂项功能服务
16 键盘服务
17 打印机服务
1A 时钟服务
21 DOS函数
2F DOS多路共享服务
33 鼠标器服务
67 EMS服务
--------------------------------------------------------------------------
当你知道了什么是中断后,你就会认识到:当计算机处于空闲状态时,它每秒可能要处理几十个中断;而当计算机紧张工作时,它每秒经常要处理数百个中断。在20.12中有一个例子程序,你可以参照该程序写出自己的中断处理程序,从而使两个程序通过中断进行交流。如果你觉得有意思,不妨试一下。
请参见:
20.12 怎样把数据从一程序传给另一个程序?
14.7 使用ANSI函数和使用BIOS函数,哪种方式更好?
两种方式各有利弊。你必须先回答几个问题,然后才能确定哪种方式适合你需要创建的那种应用。例如:你需要很快地实现你的应用吗?你的应用仅仅是用来“证实有关概念”,还是一个“真正的应用”呢?速度对你的应用重要吗?下面比较了使用ANSI函数和使用BIOS函数的基本优点:
使用ANSI函数的优点:
只需要printf()语句就可完成任务
改变文本的颜色和属性很方便
不管系统如何配置,都可以在所有PC机上工作
无需记忆BIOS iN数
使用BIOS函数的优点:
运行速度快
用BIOS可以做更多的事
不需要设备驱动程序(使用ANSI iN数需要ANSI.SYS)
无需记忆ANSI命令
刚开始时,你会发现用ANSI函数编程是很不错的,并且能使你写出一些漂亮的程序。然而,不久你就可能会发现ANSI函数“有些碍事”,此时你就会想用BIOS函数。当然,以后你又发现BIOS函数有时也会“碍事”,此时你就想使用一种更快的方式。例如,14.4中的一个例子甚至不通过BIOS来把文本打印到屏幕上,你也许会发现这种方法比使用ANSI或BIOS函数更有趣。
请参见:
14.4 怎样在程序中存取重要的DoS内存位置?
----------------解决方案--------------------------------------------------------
14.8 可以通过BIOS把显示模式改为VGA图形模式吗?
当然可以。中断10H,即Video BIOS,负责处理文本模式和图形模式之间的转换。当你所运行的程序要进行文本模式和图形模式之间的相互转换时(即使该程序是Microsoft Windows),就需要通过Video BIOS来实现这种转换。每一种不同的设置都被称作一种显示模式。
要改变显示模式,你必须通过int 10H服务来调用Video BIOS。这就是说,你必须向中断10H的中断处理程序发出中断请求。除中断号不同之外,这与实现DOS调用(int 21H)没有什么区别。下面的一段程序通过调用Video BIOS函数0,先从标准文本模式(模式3)切换到一个由命令行输入的模式号,然后再切换回来:
# include <stdlib. h>
# include <dos. h>
main(int argc, char * * argv)
{
union REGS regs;
int mode;
/ * accept Mode number in hex * /
sscanf (argv[1] , " %x" , &mode) ;
regs. h. ah = 0; /* AH = 0 means "change display mode" */
regs.h.al = (char)mode; /* AL = ??, where ?? is the Mode number *
regs. x. bx = 0; /* Page number, usually zero */
int86(0xl0, ®s, ®s); /* Call the BIOS (intlO) * /
printf("Mode 0x%X now active/n" , mode);
printf ("Press any key to return. . . ") ;
getch();
regs. h. al = 3; / * return to Mode 3 * /
int86(0xl0, ®s, ®s);
}
有一个有趣的特点并没有在这个程序中表现出来,即该程序可以在不清屏的情况下改变显示模式。在某些场合,这一特点极为有用。要想改变显示模式,而又不影响屏幕内容,只需把存放在AI.寄存器中的显示模式值和80H或一下。例如,如果你要切换到模式13H,你只需把93H存入AL中,而程序中其余的代码可以保持不变。
今天,在VESA Video BIOS标准中已经加入了VGA卡对扩充显示模式(见下文中的补充说明)的支持。然而,需要有一个新的“改变显示模式”函数来支持这些扩充模式。按照VESA标准,在切换VESA模式时,应该使用函数4FH,而不是前文例子中的函数O。下面的程序改进了前文中的例子,以切换VESA模式:
# include <stdlib. h>
#include <dos. h>
main(int argc, char * * argv)
{
union REGS regs;
int mode;
/ * accept Mode number in hex * /
sscanf (argv[1], " %x" , &mode);
regs. x. ax = 0x4F02; /* change display mode * /
regs. x. bx = (short )mode; / * three-digit mode number * /
int86(0x10, ®s, ®s); /* Call the BIOS (intlO) * /
if(regs.h.al !=0x4F){
printf("VESA modes NOT supported! /n" );
}
else {
printf("Mode Ox%X now active/n" , mode);
printf ("Press any key to return. . . " ) ;
getch() ;
}
regs. h. al = 3; / * return to Mode 3 * /
int86(0x10,®s, ®s) ;
}
注意,在切换VESA模式时,不能通过把模式号和80H或一下来达到不清屏的目的。但是。只要把原来两位的(十六进制)模式号的最高位往前移一位,就得到了VESA模式号(所有VESA模式号的长度都是三位(十六进制),见下文中的补充说明)。因此,为了切换到VESA模式101H并且保留屏幕上的内容,你只需把VESA模式号换为901H。
关于显示模式的补充说明:
IBM推出了一种显示模式标准,该标准试图定义所有可能会用到的显示模式,其中包括所有可能的像素层次(颜色的数目)。因此,IBM创建了19种显示模式(从OH到13H)。表14.8a给出了这种显示模式标准。
14.8a 标准显示模式
-------------------------------------------------------------------------------
模式(H) 分辨率 图形/文本 颜色
-------------------------------------------------------------------------------
0 40X 25 文本 单色
1 40 X 25 文本 16
2 80X 25 文本 单色
3 80X 25 文本 16
4 320X 200 图形 4
5 320X 200 图形 4级灰度
6 640X 200 图形 单色
7 80 X 25 文本 单色
8 160X 200 图形 16
9 320X 200 图形 16
A 640 x 200 图形 4
B 保留给EC-A BIOS使用
C 保留给EGA BIOS使用
D 320×200 图形 16
E 640×200 图形 16
F 640×350 图形 单色
10 640×350 图形 4
11 640×480 图形 单色
12 640×480 图形 16
13 320×200 图形 256
-------------------------------------------------------------------------------
那么,你见过其中的某些模式吗?模式3是80×25彩色文本模式,也就是PC机上电时你所看到的模式。当你把"VGA"(随Windows提供的一个驱动程序)选为Microsoft Windows3.x的驱动程序时,你所看到的就是模式12(H)。注意,上表中并没有一种颜色多于256色或分辨率高于640×480的模式。多年以来,模式4,9和D一直是DOS游戏开发者喜欢用的模式,它们拥有“高”达320×200的分辨率和足够的颜色(4或16种),足以显示一些“象样”的图形。所有流行
的动画游戏几乎都使用模式13,例如DOOM(一代和二代),id软件公司的new Heretic,Apogee公司的Rise of the Triad,Interplay公司的Descent,等等。实际上,许多动画游戏在VGA卡上耍了个小花招,即把模式13的分辨率改为320×240 这种模式被称为模式x,它有更多的内存页。可以提高图形质量和显示速度。
那么,其它一些常见的显示模式又是从哪里来的呢?它们是由VGA卡的制造商提供的。这些你可能已经熟悉的显示模式来自各种各样的渠道,但不管它们来自何处,VGA卡的制造商们都把它们加到了自己的VGA卡中,以增加这些VGA卡的价值。这些模式通常被称为扩充显示模式(extended display mode)。由于竞争和资本积累的原因,VGA卡的制造商们逐步转向了这些更高级的显示模式。有人还试过其它一些显示模式(听说过1152×900吗?),但并不象上述模式那样受欢迎。
那么。什么是VESA呢?它与VGA卡有什么关系呢?尽管VGA卡的制造商们都选择了支持同样的一组显示模式(包括扩充模式),但他们都按自己的专用方式去实现其中的扩充模式,而游戏厂商和其它软件厂商不得不去支持市场上每一种VGA卡的每一种专用方式。因此,一些制造商和其它方面的一些代表一起组成了一个委员会,以尽可能地使这些卡的设置和
编程标准化,这个委员会就是VESA(Video Electronic Standards Association)。VESA委员会采用了一种扩充显示模式的标准,从而使软件可以通过普通的BIOS调用来设置和初始化所有符合该标准的VGA卡。基本上可以这样说,在美国出售的所有的VGA卡都支持某种VESA标准。
所有的VESA模式(即VESA标准所包含的那些显示模式)都采用宽度为9位(bit)的模式号,而不是标准模式的8位(hit)模式号。使用了9位(bit)的模式号后,就可以用三位十六进制数来表示VESA模式了,而IBM标准模式只能用两位十六进制数(在表14.8a中,从0到13H)来表示,这样就避免了模式号的冲突。因此,所有的VESA模式号都大于100H。VESA模式是这样起作用的:假设你想让你的VGA卡以1024×768和256色这样的模式显示,而这种模式就是VESA模式105,因此你要用模式号105作一次BIOS调用。Video BIOS(有时叫做VESA BIOS)会把VESA模式号翻译成内部专用号,以完成实际的模式切换工作。VGA卡的制造商们在每一块VGA卡上都提供了一种可以完成上述翻译工作的Video BIOS,因此你只需要搞清楚VESA模式号就行了。表14.8b列出了最新的VESA显示模式(VESA是一个不断发展的标准。)
表14.8b VESA显示模式
----------------------------------------------------------------------------
分辨率 颜色 VESA模式
----------------------------------------------------------------------------
640X400 256 100
640X480 256 101
640X480 32768 110
640X480 65536 111
640X480 16. 7M 112
800X600 16 102
800X600 256 103
800X600 32768 113
800X600 65536 114
800X600 16. 7M 115
1024X768 16 104
1024X768 256 105
1024X768 32768 116
1024X768 65536 117
1024X768 16. 7M 118
1280X1024 16 106
1280X1024 256 107
1280X1024 32768 119
1280X1024 65536 11A
1280X1024 16. 7M 11B
-----------------------------------------------------------------------------
注意,这些都是人们熟悉的显示模式,特别是在使用Microsoft Windows时,这些模式更为常见。
请参见:
14.6什么是中断?
14.9运算符的优先级总能起作用吗(从左至右,从右至左)?
如果你是指“一个运算符的结合性会从自右至左变为自左至右吗?反过来会吗?”,那么答案是否定的。如果你是指“一个优先级较低的运算符会先于一个优先级较高的运算符被执行吗?”,那么答案是肯定的。表14.9按优先级从高到低的顺序列出了所有的运算符及其结合性:
表14.9运算符优先级
----------------------------------------------------------------
运算符 结合性
----------------------------------------------------------------
() [] -> 自左至右
! ~ ++ -- -(类型转换) * & 自右至左
sizeof * / % 自左至右
+ - 自左至右
<< >> 自左至右
<< = >>= 自左至右
== != 自左至右
& 自左至右
^ 自左至右
| 自左至右
&& 自左至右
|| 自左至右
?: 自右至左
= += -= 自右至左
, 自左至右
------------------------------------------------------------------
注意,运算符“!=”的优先级高于“=”(实际上,几乎所有的运算符的优先级都高于“=”)。下面两行语句说明了运算符优先级的差异是怎样给程序员带来麻烦的:
while(ch=getch()!=27)printf(”Got a character/n”);
while((ch=geteh())!=27)printf("Got a character/n"); ’
显然,上述语句的目的是从键盘上接收一个字符,并与十进制值27(Escape键)进行比较。不幸的是,在第一条语句中,getch()与Escape键进行了比较,其比较结果(TRUE或FALSE)而不是从键盘上输入的字符被赋给了ch。这是因为运算符“!=”的优先级高于“=”。
在第二条语句中,表达式"ch=geteh()”的外边加上了括号。因为括号的优先级最高,所以来自键盘的字符先被赋给ch,然后再与Escape键进行比较,并把比较结果(TRUE或FALSE)返回给while语句,这才是程序真正的目的(当while的条件为TRUE时,打印相应的句子)。需要进一步提出的是,与27比较的并不是ch,而是表达式"ch―getch()”的结果。在这个例子中,这一点可能不会造成什么影响,但括号确实可以改变代码的组织方式和运行方式。当一个语句中有多个用括号括起来的表达式时,代码的执行顺序是从最里层的括号到最外层,同层的括号则从左到右执行。
注意,每个运算符在单独情况下的结合性(自左至右,或自右至左)都是不会改变的,但优先级的顺序可以改变。
14.10 函数参数的类型必须在函数头部或紧跟在其后说明口马?为什么?
ANSI标准要求函数参数的类型要在函数头部说明。在第20章中你将会发现,C语言最初设计于70年代,并且运行于UNX操作系统上,显然当时还没有什么ANSI C标准,因此早期的C编译程序要求在紧接着函数头部的部分说明参数的类型。
现在,ANSI标准要求参数的类型应该在函数头部说明。以前的方法中存在的问题是不允许进行参数检查――编译程序只能进行函数返回值检查。如果不检查参数,就无法判断程序员传递给函数的参数类型是否正确。通过要求在函数头部说明参数,以及要求说明函数原型(包括参数类型),编译程序就能检查传递给函数的参数是否正确。
请参见:
14.11 程序应该总是包含main()的一个原型吗?
14.11 程序应该总是包含main()的一个原型吗?
当然,为什么不呢?虽然这不是必需的,但这是一种好的编程风格。人们都知道main()的参数类型,而你的程序可以定义其返回值的类型。你可能注意到了本章(其它章中可能也有)的例子中并没有说明main()的原型,并且在main()的函数体中也没有明确地表示返回值的类型,甚至连renturn语句也没有。笔者在按这种方式写这些例子时,隐含使用了一个返回整型值的void函数,但是,由于没有return语句,程序可能会返回一个无用值。这种方式不是一种好的编程风格,好的编程风格应该描述包括main()在内的所有函数的原型以及相应的返回值。
请参见:
14.12 main()应该总是返回一个值吗?
14.12 main()应该总是返回一个值吗?
main()不必总是带有返回值,因为它的调用者,通常是COMMAND.CoM,并不怎么关心返回值。偶而,你的程序可能会用在一个批处理文件中,而这个文件会到DOS的errorLevel符号中检查一个返回码。因此,main()是否有返回值完全取决于你自己,但是,为了以防万一,给main()的调用者返回一个值总是好的。
如 main()返回void类型(或者没有return语句),也不会引起任何问题。
请参见:
14.11程序应该总是包含main()的一个原型吗?
14.13 可以通过BIOS控制鼠标吗?
可以。你可以通过中断33H调用鼠标服务程序。表14.13列出了中断33H中最常用的鼠标服务程序。
表14.13鼠标中断服务
--------------------------------------------------------------------------
功能号 描 述
--------------------------------------------------------------------------
0 初始化鼠标;当前可见则隐藏它
1 显示鼠标
2 隐藏鼠标
3 获得鼠标位置
4 设置鼠标位置
6 检查鼠标按钮是否被按下
7 设置鼠标的水平限制值
8 设置鼠标的垂直限制值
9 设置图形模式鼠标形状
10 设置文本模式鼠标风格
11 获得鼠标的移动步值
---------------------------------------------------------------------------
下面的例子通过上表中的一些鼠标服务程序来控制一个文本模式的鼠标:
# include <stdlib. h>
# include <dos. h>
main()
{
union REGS regs;
printf("Initializing Mouse. . . ") ;
regs. x. ax = 0;
int86(0x33, ®s, ®s);
printf("/nShowing Mouse. . . ") ;
regs. x.ax = 1;
int86(0x33, ®s, ®s);
printf ("/nMove mouse around. Press any key to quit. . . ") ;
getch() ;
printf ("/nHiding Mouse. . . " ) ;
regs. x. ax = 2;
int86(0x33, ®s, ®s);
printf("/nDone/n");
}
当运行这个程序时,屏幕上会出现一个闪烁的可以移动的块状光标。无论什么时候,你都可以通过函数3向鼠标处理程序询问鼠标的位置。实际上,笔者用表14.13中的函数编写了一整套鼠标库函数,并且在笔者的许多使用文本模式鼠标的程序中使用了这套函数。
为了使用上表中的函数,你必须安装一种鼠标驱动程序。通常可以通过AUTOEXEC.BAT文件来安装鼠标驱动程序。然而,现在运行Windows时通常只安装一种Windows鼠标驱动程序,在这种情况下,你必须先运行在DOS shell下,然后才能调用这些鼠标函数。
----------------解决方案--------------------------------------------------------
花了二十分钟左右的时间认真看了一下.
感觉收获不少.
谢谢楼主的分享.
----------------解决方案--------------------------------------------------------
楼主真是太热心了,以前不太经常用到计算机的底层操作,这回得认真看看,谢谢楼主了
[此贴子已经被作者于2007-10-5 0:51:33编辑过]
----------------解决方案--------------------------------------------------------
好东西
----------------解决方案--------------------------------------------------------
字好多.整理下传上来就好了大家都是懒人.呵...不错...
----------------解决方案--------------------------------------------------------
----------------解决方案--------------------------------------------------------
我把它复制了,谢谢LZ
----------------解决方案--------------------------------------------------------