📄 第20章 杂项.txt
字号:
ax=AX; /* use return value from switch() */
}
/ * avoids calling DOS to print characters * /
void
OutputString(char * str)
{
int i ;
regs. h. ah = 0x0E ;
regs. x. bx = 0 ;
for(i=strlen(str) ; i>0; i--,str++){
regs. h. al= * str;
int86 (0xl0, ®s, ®s) ;
}
}
上述程序是这两个程序中的TSR程序。这个程序中有一个NewCommVector()函数,它被安装在中断63H(63H通常是一个可用的向量)处作为中断服务程序。当它被安装好后,它就可以接收命令了。switch语句用来处理输入的命令,并作出相应的反应。笔者随意选择了0x1O和0x30来代表这样两条命令:“从ES:BX处复制数据,并打印到屏幕上,CX中的数值为打印次数”;“脱离中断63H,并停止接收命令”。下面是第二个程序——向中断63H发送命令的程序(注意它必须在Large模式下编译)。
# include <stdlib. h>
# include <dos. h>
# define COMM VECTOR 0x63
union REGS regs;
struct SREGS segregs ;
char buffer[80];
char _far * buf=(char_far *)buffer;
main (int argc,char * * argv)
{
intcnt;
cnt = (argo= =1 ? 1:atoi(argv[1])) ;
strcpy (bur, "Hello There\r\n" ) ;
regs. x. ax= 0x10;
regs. x. cx=cnt ;
regs. x. bx=FP OFF(buf);
segregs, es=FP SEG(buf) ;
int86x(COMM_VECTOR ,®s, &segregs) ;
printf ("TSR returned %d\n" ,regs. x. ax) ;
}
你可能会认为这个短小的程序看上去和那些通过调用int 21或int 10来在DOS中设置或检索信息的程序差不多。如果你真的这么想,那就对了。唯一的区别就是现在你所用的中断号是63H,而不是21H或10H。上述程序只是简单地调用前文中的TSR程序,并要求后者把es:bX所指向的字符串打印到屏幕上,然后,它把中断处理程序(即那个TSR程序)的返回值打印到屏幕上。
当字符串"Hello There”被打印到屏幕上后,在两个程序之间传递数据的全部必要步骤就都完成了。这个例子的真正价值在于它能够举一反三。现在你能很轻松地编写一个这样的程序,它将发送一条类似于“把要求你打印的最后一个字符串传递给我”的命令。你所要做的就是在前述TSR程序的switch语句中加入这条命令,然后再写一个程序来发送这条命令。此外,你也可以在第二个程序中利用20.11中所介绍的system()或spawn()函数来启动前述TSR程序。由于TSR程序会检查自己是否已被装入,因此你只需装入一次TSR程序,就可以多次运行第二个程序了。在所有要和前述TSR程序通信的程序中,你都可以使用这里所说的方法。
在建立前述TSR程序时,需要有几个前提条件。其一就是没有其它重要的中断服务程序也在处理中断63H。例如,笔者原来在程序中使用的是中断67H,结果该程序能正常装入并运行,但此后笔者就无法编译程序了,因为Microsoft用来运行C编译程序的DOS扩展程序也要使用中断67H。在笔者发送了命令0x30(让程序卸载自身)后,编译程序又能正常运行了,因为DOS扩展程序的中断处理程序已被该程序恢复了。
第二个前提条件与驻留检查在关。笔者假设永远不会有另一个中断处理程序使用和NewCommVector()相同的近程型地址,尽管这种巧合的可能性极小,但读者应该知道该程序并不是万无一失的。在该程序中,笔者特意让NewCommVector()使用自己的栈,以避免它运行在调用它的程序的栈上,但是,笔者还是假设调用所需的任何函数都是安全的。注意,该程序没有调用printf(),因为它占用较多的内存,并且要调用DOS(int 21)来打印字符。在该程序中,当中断63H发生时,笔者不知道DOS是否可以被调用,因此不能假设可以使用DOS调用。
注意,在该程序中,可以调用那些没有用到DOS int21服务程序的函数来完成所需的任务,如果必须使用一个DOS服务程序,你可以在中断63H发生时检查DOS忙标志,以确定当时DOS是否可以被调用。最后,对dos_keep()作一点说明:该函数要求知道在程序退出时要在内存中保留多少段(每段16字节)数据。在本例这个TSR程序中,提供给该函数的段数(276)稍大于整个可执行程序的大小。当你的程序变大时,提供给该函数的段数也必须增大,否则就会出现一些异常现象。
请参见:
20.10 怎样在一个程序后面运行另一个程序?
20.1l 怎样在一个程序执行期间运行另一个程序?
20.15 本书的有些例子程序有许多缺陷,为什么不把它们写得更好?
20.13 怎样判断正在运行的程序所在的目录?
我们这些DOS程序员是很幸运的,因为DOS程序的装入程序会提供正在运行的可执行文件的路径全名。这个路径全名是通过指针argv[0]提供的,mai‘n()函数的argv变量指向该指针。只需去掉路径全名中的文件名,你就得到了正在运行的程序所在的目录。下面的例子演示了这种技巧:
# include <stdio. h>
# include <stdlib. h>
# include <string. h>
void main(int argc, char ** argv)
{
char execDir [80];
int i,t;
/ * set index into argv[0] to slash character prior to appname * /
for(i= (strlen(argv[0])-1) ;
((argv[O][i] ! ='/' ) && (argv[O][i]! =' \\' ));--i) ;
/ * temporarily truncate argv[] * /
t =argv[O][i] ;
argv[O][i]= O ;
/ * copy directory path into local buffer * /
strcpy(execDir ,argv[O]) ;
/* put back original character for sanity's sake * /
argvEO]Ei]=t;
}
请参见:
20.1 怎样获得命令行参数?
20.14 怎样找到程序中的重要文件(数据库,配置文件,等等)?
DOS提供了一对函数,用来在一个目录下查找一个任何类型的文件。你可以查找普通文件、档案文件、隐含文件、系统文件、只读文件、目录文件,甚至卷标文件。下面这个小例子说明了如何在当前目录下查找一个特定文件:
# include <stdio. h>
# include <dos. h>
void main(void)
{
struct find_t myFile ;
_dos_findfirst ("MYFILE. INI" ,_A_NORMAL ,&-nyFile) ;
while (_dos_findnext (&myFile) == 0)
printf("Found file %s of size %s\n", myFile, name,myFile, size) ;
}
这个例子说明了函数_dos_findfirst()和_dos_findnext()是如何工作的。你可以进入一个目录,然后象上例这样用这两个函数查找一个指定名字的文件。这两个函数还允许使用通配符“*”和“?”,如果你用“*”作为文件名,它们就会返回一个目录中的所有文件。如果你要查找硬盘上的每一个文件,则要把上例中的代码放到一个递归的函数中,由它来进入每个子目录并查找指定的文件。
20.15 本书的有些例子程序有许多缺陷,为什么不把它们写得更好?
本书的有些例子尽管是完整的程序,但是比较短小,因此往往不是实用的、注释完整的程序。这样做的原因是为了尽量给读者提供明确的、知识性强的,但又非常简洁的答案。如果例子很长,它们就会破坏全书的连贯性,并且不利于读者把握基本的学习目标。
请参见: .
20.12 怎样把数据从一个程序传给另一个程序?
20.14 怎样找到程序中的重要文件(数据库,配置文件,等等)?
20.16 怎样使Ctrl+Break失效?
有好几种方法可以使Ctrl+Break功能失效,这里只讨论两种最常用的方法。
第一种方法是用DOS来解除Ctrl+Break功能。你可能用DOS中断21H的33H函数来得到或设置Ctrl+Break检查标志,该标志告诉DOS是否在Ctrl+Break按下时对其进行处理。下面的例子说明了这种方法:
# include <stdio. h>
# include <dos. h>
void main(int argc,char **argv)
{
union REGS regs;
int ctrlBreakFlag ;
/ * find out the curre.nt state of the flag * /
regs. x. ax= 0x3300 ; / * subfunction 0 gets flag state */
int86 (0x21, ®s, ®s) ;
ctrlBreakFlag == rags. h. dl ; / * save flag value from DL * /
/ * set the state of the flag to disable Ctrl+Break * /
regs. x. ax=0x3301;
regs. h. dl = 0 ; / * disable checking * /
int86(0x21,®s, ®s) ; /* subfunction 1 sets flag state * /
}
上例首先调用DOS来查询Ctrl+Break检查标志的当前状态,并将其存入ctrlBreakFlag中。DOS调用的返回值存在DL中,如果解除了Ctrl+Break检查,则该值为O;如果允许Ctrl+Break检查,则该值为1。接着,上例清除DL并调用DOS的设置Ctrl+Break标志函数来解除Ctrl+Break检查。在调用上述函数重新设置Ctrl+Break标志之前,此次所设置的状态将
一直保留。上例没有用到子函数02(AX一0x3302),该函数能同时得到并设置Ctrl+Break标志的状态,为了完成这项任务,你应该把0x3302放到AX中,把要求的Ctrl+Break标志状态放到DL中,然后进行中断,并把原来的状态从DL中存入ctrlBreakFlag中。
第二种方法是使用信号(signal)。信号是从过去的UNIX时代继承下来的。信号函数的作用是在某些事件发生时通知程序员,这些事件之一就是用户中断——在DOS下就是Ctrl+Break事件。下面的例子说明了如何用Microsoft的signal()函数来捕获Ctrl+Break事件并作出反应(假设允许Ctrl+Break检查):
# include <[stdio. h>
# include <signal. h>
int exitHandler (void) ;
int main (int argc,char ** argv)
{
int quitFlag = 0 ;
/ * Trap all Ctrl+Breaks * /
signal (SIGINT, (void (__cdecl * ) (int))exitHandler);
/ * Sit in infinite loop until user presses Ctrl+Break * /
while (quitFlag = = 0)
printf (" % s\", (argv > 1 ) ? argv [ 1 ] : "Waiting for Ctrl + Break" ) ;
}
/ * Ctrl+Break event handler function * /
int exitHandler ()
{
char ch ;
/ * Disable Ctrl+Break handling while inside the handler * /
signal (SIGINT, SIG_ IGN ) ;
/ * Since it was an "interrupt",clear keyboard input buffer * /
fflush(stdin) ;
/ * Ask if user really wants to quit program * /
printf("\nCtrl+Break occurred. Do you wish to exit this program?
→(Y or N)");
/ * Flush output buffer as well * /
fflush (stdout) ;
/ * Get input from user, print character and newline * /
ch =getche ( ) ;
printf("\n" ) ;
/ * If user said yes, leave program * /
if(toupper (eh) = 'Y' )
exit (0) ;
/ * Reenable Ctrl+Break handling * /
signal (SIGINT, (void (__cdecl * ) (int))exitHandler) ;
return(0) ;
}
上例的好处在于每当按下Ctrl+Break时都会调用一个函数,也就是说,你可以选择将要作出的反应一一你可以忽略这个事件,相当于解除Ctrl+Break功能;你也可以作出所需的其它任何反应;上例的另一个好处是当程序退出时,Ctrl+Break的正常操作就会恢复,不需要人为的干预。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -