怎样把数据从一个程序传给另一个程序?
有好几种基本的方法可以完成这项任务----你可以通过文件或内存来传递这些数据。这些方法的步骤都相当简洁:首先,定义在何处存放数据,如何获取数据,以及如何通知另一个程序来获取或设置数据;然后,你就可以获取或设置数据了,尽管使用文件的技术定义和实现起来都比较简单,但它的速度往往比较慢(并且容易引起混乱)。因此,这里重点讨论内存数据转移技术。下面将依次详细地分析这一过程的每一个环节:
定义在何处存放数据。当你编写要共享数据的两个程序时,你应该让程序知道要访问的数据存放在何处。这个环节同样有几种实现方法:你可以在一个(或每个)程序中建立一个固定的内部缓冲区,并在两个程序之间传递指向这个缓冲区的指针;你也可以为数据分配动态内存,并在两个程序之间传递指向该数据的指针;如果要传递的数据很小,你还可以通过CPU的通用寄存器来传递数据(这种可能性很小,因为x86结构的寄存器很少)。分配动态内存是最灵活和模块性最强的方法。
定义获取数据的方法。这个环节非常简洁——你可以使用fmemcpy()或等价的内存拷贝函数。显然,在获取和设置数据时都可以使用这个函数。
定义通知另一个程序的方法。因为DOS并不是一个多任务操作系统,所以其中一个(或两个)程序的一部分必须已经驻留在内存中,并且可以接受来自另一个程序的调用。同样,这个环节也有几种方法可供选择:第一个程序可以是一个列入CONFIG.SYS中的驱动程序,它在系统启动时就被装入内存;第一个程序也可以是一个TSR(终止并驻留)程序,在它退出时会把与第二个程序相互作用的那部分程序驻留在内存中;此外,你也可以在第一个程序中利用system()或spawn()函数(见20.11)来启动第二个程序。你可以根据需要选择合适的方法。因为有关DOS驱动程序的数据传递在DOS文档中已经有详尽的描述,而有关system()和spawn()函数的内容也已经在前文中介绍过,因此下面介绍TSR方法。
下面的例子给出了两个程序:第一个程序是一个完整的TSR程序,但为了突出整个过程中的关键环节,它写得比较单薄(见20.15中的解释)。这个TSR程序先是安装了一个中断63H的中断服务程序,然后调用终止并驻留退出函数,在执行这个TSR程序后,执行下文给出的另一个程序。这个程序只是简单地初始化一个对中断63H的调用(类似于使用中断21H调用),并且把“Hello There”传送给上述TSR程序
# include <stdlib. h>
# include <dos. h>
# include <string. h>
void SetupPointers (void) ;
void OutputString(char * );
# define STACKSIZE 4096
unsigned int near OldStackPtr;
unsigned int near OldStackSeg;
unsigned int _near MyStackOff ;
unsigned int _near MyStackSeg;
unsigned char_near MyStack[STACKSIZE];
unsigned char far * MyStackPtr= (unsigned char_far * )MyStack;
unsigned short AX, BX,CX, DX,ES;
/ * My interrupt handler * /
void_interrupt_far_cdecl NewCommVector (
unsigned short es, unsigned short ds, unsigned short di,
unsigned short si, unsigned short bp, unsigned short sp,
unsigned short bx, unsigned short dx, unsigned short cx,
unsigned short ax, unsigned short ip, unsigned short cs,
unsigned short flags) ;
/ * Pointers to the previous interrupt handier * /
void(_interrupt_far_cdecl * CommVector)();
union REGS regs;
struet SREGS segregs ;
# define COMM_VECTOR 0x63 / * Software interrupt vector * /
/ * This is where the data gets passed into the TSR * /
char_far * eallerBufPtr;
char localBuffer[255]; / * Limit of 255 bytes to transfer * /
char_far * localBufPtr=(ehar_far * )loealBuffer;
unsigned int ProgSize= 276; / * Size of the program in paragraphs * /
void
main(int argc,char * * argv)
{
int i, idx;
/ * Set up all far pointers * /
SetupPointers () ;
/ * Use a cheap hack to see if the TSR is already loaded
tf it is, exit,doing nothing * /
comm_veetor =_dos_getvect (COMM_VECTOR) ;
if(((long)eomm_vector & 0xFFFFL) ==
((long) NewCommVector & OxFFFFL ) ) {
OutputString("Error :TSR appears to already be loaded. \n");
return ;
/ * If everything's set,then chain in the TSR * /
_dos_setvect (COMM_VECTOR ,NewCommVector) ;
/ * Say we are loaded * /
OutputString("TSR is now loaded at 0x63\n");
/ * Terminate, stay resident * /
dos_keep (0, ProgSize ) ;
}
/ * Initializes all the pointers the program will use * /
void
Set upPointers ( )
{
int idx ;
/ * Save segment and offset of MyStackPtr for stack switching * /
MyStackSeg = FP_SEG (MyStackPtr) ;
MyStackOff = FP_OFF (MyStackPtr) ;
/ * Initialize my stack to hex 55 so I can see its footprint
if I need to do debugging * /
for (idx = 0 ;idx<STACKSIZE ; idx ++ ) {
MyStack [idx] = 0x55 ;
}
}
void _interrupt_ far_cdecl NewCommVector (
unsigned short es, unsigned short ds, unsigned short di,
unsigned short si, unsigned short bp, unsigned short sp,
unsigned short bx, unsigned short dx, unsigned short cx,
unsigned short ax, unsigned short ip, unsigned short cs,
unsigned short flags)
{
AX = ax;
BX = bx ;
CX = cx;
DX = dx ;
ES = es ;
/ * Switch to our stack so we won't run on somebody else's * /
_asm {
;set up a local stack
eli ; stop interrupts
mov OldStackSeg,ss ; save stack segment
mov OldStackPtr,sp ; save stack pointer (offset)
mov ax,ds ; replace with my stack s
mov ss,ax ; ditto
mov ax,MyStackOff ; replace with my stack s
add ax,STACKSIZE-2 ;add in my stack size
mov sp ,ax ; ditto
sti ; OK for interrupts again
}
switch (AX) {
case 0x10; / * print string found in ES:BX */
/ * Copy data from other application locally * /
FP_ SEG (callerBufPtr) = ES ;
FP_OFF (callerBufPtr) = BX ;
_fstrcpy (localBufPtr, callerBufPtr ) ;
/ * print buffer 'CX' number of times * /
for(; CX>0; CX--)
OutputString (localBufPtr) ;
AX=1; /* show success */
break ;
case 0x30: /* Unload~ stop processing interrupts * /
_dos_setvect (COMM_VECTOR ,comm_vector) ;
AX=2; /* show success */
break ;
default :
OutputString (" Unknown command\r\n" ) ;
AX= 0xFFFF; / * unknown command-1 * /
break ;
}
/ * Switch back to the caller's stack * /
asm {
cli ;turn off interrupts
mov ss,OldStackSeg ;reset old stack segment
mov sp,OldStackPtr ;reset old stack pointer
sti ;back on again
}
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)稍大于整个可执行程序的大小。当你的程序变大时,提供给该函数的段数也必须增大,否则就会出现一些异常现象。
定义在何处存放数据。当你编写要共享数据的两个程序时,你应该让程序知道要访问的数据存放在何处。这个环节同样有几种实现方法:你可以在一个(或每个)程序中建立一个固定的内部缓冲区,并在两个程序之间传递指向这个缓冲区的指针;你也可以为数据分配动态内存,并在两个程序之间传递指向该数据的指针;如果要传递的数据很小,你还可以通过CPU的通用寄存器来传递数据(这种可能性很小,因为x86结构的寄存器很少)。分配动态内存是最灵活和模块性最强的方法。
定义获取数据的方法。这个环节非常简洁——你可以使用fmemcpy()或等价的内存拷贝函数。显然,在获取和设置数据时都可以使用这个函数。
定义通知另一个程序的方法。因为DOS并不是一个多任务操作系统,所以其中一个(或两个)程序的一部分必须已经驻留在内存中,并且可以接受来自另一个程序的调用。同样,这个环节也有几种方法可供选择:第一个程序可以是一个列入CONFIG.SYS中的驱动程序,它在系统启动时就被装入内存;第一个程序也可以是一个TSR(终止并驻留)程序,在它退出时会把与第二个程序相互作用的那部分程序驻留在内存中;此外,你也可以在第一个程序中利用system()或spawn()函数(见20.11)来启动第二个程序。你可以根据需要选择合适的方法。因为有关DOS驱动程序的数据传递在DOS文档中已经有详尽的描述,而有关system()和spawn()函数的内容也已经在前文中介绍过,因此下面介绍TSR方法。
下面的例子给出了两个程序:第一个程序是一个完整的TSR程序,但为了突出整个过程中的关键环节,它写得比较单薄(见20.15中的解释)。这个TSR程序先是安装了一个中断63H的中断服务程序,然后调用终止并驻留退出函数,在执行这个TSR程序后,执行下文给出的另一个程序。这个程序只是简单地初始化一个对中断63H的调用(类似于使用中断21H调用),并且把“Hello There”传送给上述TSR程序
# include <stdlib. h>
# include <dos. h>
# include <string. h>
void SetupPointers (void) ;
void OutputString(char * );
# define STACKSIZE 4096
unsigned int near OldStackPtr;
unsigned int near OldStackSeg;
unsigned int _near MyStackOff ;
unsigned int _near MyStackSeg;
unsigned char_near MyStack[STACKSIZE];
unsigned char far * MyStackPtr= (unsigned char_far * )MyStack;
unsigned short AX, BX,CX, DX,ES;
/ * My interrupt handler * /
void_interrupt_far_cdecl NewCommVector (
unsigned short es, unsigned short ds, unsigned short di,
unsigned short si, unsigned short bp, unsigned short sp,
unsigned short bx, unsigned short dx, unsigned short cx,
unsigned short ax, unsigned short ip, unsigned short cs,
unsigned short flags) ;
/ * Pointers to the previous interrupt handier * /
void(_interrupt_far_cdecl * CommVector)();
union REGS regs;
struet SREGS segregs ;
# define COMM_VECTOR 0x63 / * Software interrupt vector * /
/ * This is where the data gets passed into the TSR * /
char_far * eallerBufPtr;
char localBuffer[255]; / * Limit of 255 bytes to transfer * /
char_far * localBufPtr=(ehar_far * )loealBuffer;
unsigned int ProgSize= 276; / * Size of the program in paragraphs * /
void
main(int argc,char * * argv)
{
int i, idx;
/ * Set up all far pointers * /
SetupPointers () ;
/ * Use a cheap hack to see if the TSR is already loaded
tf it is, exit,doing nothing * /
comm_veetor =_dos_getvect (COMM_VECTOR) ;
if(((long)eomm_vector & 0xFFFFL) ==
((long) NewCommVector & OxFFFFL ) ) {
OutputString("Error :TSR appears to already be loaded. \n");
return ;
/ * If everything's set,then chain in the TSR * /
_dos_setvect (COMM_VECTOR ,NewCommVector) ;
/ * Say we are loaded * /
OutputString("TSR is now loaded at 0x63\n");
/ * Terminate, stay resident * /
dos_keep (0, ProgSize ) ;
}
/ * Initializes all the pointers the program will use * /
void
Set upPointers ( )
{
int idx ;
/ * Save segment and offset of MyStackPtr for stack switching * /
MyStackSeg = FP_SEG (MyStackPtr) ;
MyStackOff = FP_OFF (MyStackPtr) ;
/ * Initialize my stack to hex 55 so I can see its footprint
if I need to do debugging * /
for (idx = 0 ;idx<STACKSIZE ; idx ++ ) {
MyStack [idx] = 0x55 ;
}
}
void _interrupt_ far_cdecl NewCommVector (
unsigned short es, unsigned short ds, unsigned short di,
unsigned short si, unsigned short bp, unsigned short sp,
unsigned short bx, unsigned short dx, unsigned short cx,
unsigned short ax, unsigned short ip, unsigned short cs,
unsigned short flags)
{
AX = ax;
BX = bx ;
CX = cx;
DX = dx ;
ES = es ;
/ * Switch to our stack so we won't run on somebody else's * /
_asm {
;set up a local stack
eli ; stop interrupts
mov OldStackSeg,ss ; save stack segment
mov OldStackPtr,sp ; save stack pointer (offset)
mov ax,ds ; replace with my stack s
mov ss,ax ; ditto
mov ax,MyStackOff ; replace with my stack s
add ax,STACKSIZE-2 ;add in my stack size
mov sp ,ax ; ditto
sti ; OK for interrupts again
}
switch (AX) {
case 0x10; / * print string found in ES:BX */
/ * Copy data from other application locally * /
FP_ SEG (callerBufPtr) = ES ;
FP_OFF (callerBufPtr) = BX ;
_fstrcpy (localBufPtr, callerBufPtr ) ;
/ * print buffer 'CX' number of times * /
for(; CX>0; CX--)
OutputString (localBufPtr) ;
AX=1; /* show success */
break ;
case 0x30: /* Unload~ stop processing interrupts * /
_dos_setvect (COMM_VECTOR ,comm_vector) ;
AX=2; /* show success */
break ;
default :
OutputString (" Unknown command\r\n" ) ;
AX= 0xFFFF; / * unknown command-1 * /
break ;
}
/ * Switch back to the caller's stack * /
asm {
cli ;turn off interrupts
mov ss,OldStackSeg ;reset old stack segment
mov sp,OldStackPtr ;reset old stack pointer
sti ;back on again
}
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)稍大于整个可执行程序的大小。当你的程序变大时,提供给该函数的段数也必须增大,否则就会出现一些异常现象。