用程序向子程序传递的参数称为子程序的入口参数,子程序向调用它的程序传递的参数称为子程序的出口参数。子程序的入口参数和出口参数都是任意项,对某个具体的子程序来说,要根据具体情况来确定其入口和出口参数,也可以二者都没有。
程序和被调用子程序之间的参数传递方法是程序员自己或和别人事先约定好的信息传递方法。这种信息传递方法可以是多种多样的,在本节,我们只介绍常用的、行之有效的参数传递方法有:寄存器传递参数、约定存储单元传递参数和堆栈传递参数等。如果对其它的参数传递方法感兴趣的话,可参考其它《汇编语言程序设计》书籍。
7.3.1 寄存器传递参数
一方面,由于CPU中的寄存器在任何程序中都是“可见”的,一个程序对某寄存器赋值后,在另一个程序中就能直接使用,所以,用寄存器来传递参数最直接、简便,也是最常用的参数传递方式。但另一方面,CPU中寄存器的个数和容量都是非常有限,所以,该方法适用于传递较少的参数信息。
例7.1是用寄存器传递参数的例子,子程序处理的数据被保存在寄存器AL中。假设有下列的程序段:
…
MOVAL, ’b’
CALLUPPER子返回时,(AL)=’B’
…
MOVAL, ’2’
CALLUPPER子返回时,AL的亩游值不变,因为’2’不是字母
…
例7.3 按五位十进制的形式显示寄存器BX中的内容,如果BX的值小于0,则应在显示数值之前显示负号’-’。
例如:(BX)=123,显示:00123;(BX)=-234,显示:-00234;
解:
子程序功能:把寄存器BX的内容按十进制有符号数显示出来
入口参数:BX
出口参数:无,只有显示信息
算法描述:
1、定义6个字节的存储单元
2、先判断BX是否小于零,如果是,则先显示负号’-’,再取BX的绝对值;
3、采用除10,得余数的方法,从低位向高位求出每位十进制位;
4、输出数据的字符串。
SubDataSEGMENT
DB 5 DUP(’0’), 0ah, 0dh, ’$’饥耐行0ah、0dh:换行、回车
SubDataENDS
DISPBXPROC
ASSUMEDS:SubData
PUSHDS
PUSHDX
PUSHCX
PUSHAX
MOVAX, SubData取子程序所用的数据区段地址
MOVDS, AX
CMPBX, 0
JGEnext
MOVDL, ’-’
MOVAH, 2
INT21H显示负号’-’
NEGBX求-BX,使其值为正数
烂哗next:MOVSI, 4
MOVAX, BX
MOVCX, 10D
again:XORDX, DX
IDIVCXDX存放余数,AX存放商
ADDDL, ’0’
MOV[SI], DL
DECSI
JGEagain
XORDX, DX
MOVAH, 9
INT21H调用中断21的功能9,显示DS:DX指向的字符串
POPAX
POPCX
POPDX
POPDS
RET
DISPBXENDP
有好几种基本的方法可以完成这项任务----你可以通过文件或内存来传递这些数据。这些方法的步骤都相当简洁:首先,定义在何处存放数据,如何获取数据,以及如何通知另一个程序来获取或设置数据;然后,你就可以获取或设置数据了,尽管使用文件的技术定义和实现起来都比较简单,但它的速度往往比较慢(并且容易引起混乱)。因此,这里重点讨论内存数据转移技术。下面将依次详细地分析这一过程的每一个环节: 定义在何处存放数据。当你编写要共享数据的两个程序时,你应该让程序知道要访问的数据存放在何处。这个环节同样有几种实现方法:你可以在一个(或每个)程序中建立一个固定的内部缓冲区,并在两个程序之间传递指向这个缓冲区的指针;你也可以为数据分配动态内存,并在两个程序之间传递指向该数据的指针;如果要传递的数据很好旅小,你还可以通过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 OldStackPtrunsigned int near OldStackSegunsigned int _near MyStackOff unsigned int _near MyStackSegunsigned char_near MyStack[STACKSIZE]unsigned char far * MyStackPtr= (unsigned char_far * )MyStackunsigned 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 regsstruet SREGS segregs # define COMM_VECTOR 0x63 / * Software interrupt vector * / / * This is where the data gets passed into the TSR * / char_far * eallerBufPtrchar localBuffer[255]/ * Limit of 255 bytes to transfer * / char_far * localBufPtr=(ehar_far * )loealBufferunsigned 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 = axBX = bx CX = cxDX = 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>0CX--) 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>0i--,str++){ regs. h. al= * strint86 (0xl0, &regs, &regs) } } 上述程序是这两个程序中的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 regsstruct SREGS segregs char buffer[80]char _far * buf=(char_far *)buffermain (int argc,char * * argv) { intcntcnt = (argo= =1 ? 1:atoi(argv[1])) strcpy (bur, "Hello There\r\n" ) regs. x. ax= 0x10regs. x. cx=cnt regs. x. bx=FP OFF(buf)segregs, es=FP SEG(buf) int86x(COMM_VECTOR ,&regs, &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)稍大于整个可执行程序的大小。当你的程序变大时,提供给该函数的段数也必须增大,否则就会出现一些异常现象。希望采纳
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)