汇编语言用INVOKE和PROTO新建模块


32 位模式中,可以用 Microsoft 的 INVOKE、PROTO 和扩展 PROC 伪指令新建多模块程序。与更加传统的 CALL 和 EXTERN 相比,它们的主要优势在于:能够将 INVOKE 传递的参数列表与 PROC 声明的相应列表进行匹配。

现在用 INVOKE、PROTO 和高级 PROC 伪指令重新编写 ArraySum。为每个外部过程创建含有 PROTO 伪指令的头文件是很好的开始。每个模块都会包含这个文件 ( 用 INCLUDE 伪指令) 且不会增加任何代码量或运行时开销。

如果一个模块不调用特定过程,汇编器就会忽略相应的 PROTO 伪指令。

sum.inc 头文件本程序的 sum.inc 头文件如下所示:
; (sum.inc)
INCLUDE Irvine32.inc

PromptForIntegers PROTO,
    ptrPrompt:PTR BYTE,        ; 提示字符串
    ptrArray:PTR DWORD,        ; 数组指针
    arraySize:DWORD            ; 数组大小

ArraySum PROTO,
    ptrArray:PTR DWORD,        ; 数组指针
    arraySize:DWORD            ; 数组大小

DisplaySum PROTO,
    ptrPrompt:PTR BYTE,        ; 提示字符串
    theSum:DWORD               ; 数组之和

_prompt 模块

_prompt.asm 文件用 PROC 伪指令为 PromptForIntegers 过程声明参数,用 INCLUDE 将 sum.inc 复制到本文件:
; 提示整数输入请求          (_prompt.asm)
INCLUDE sum.inc        ; 获得过程原型
.code
;-----------------------------------------------------
PromptForIntegers PROC,
  ptrPrompt:PTR BYTE,        ; 提示字符串
  ptrArray:PTR DWORD,        ; 数组指针
  arraySize:DWORD            ; 数组大小
;
; 提示用户输入数组元素值,并用用户输入
; 填充数组
; 返回:无
;-----------------------------------------------------
    pushad                 ; 保存所有寄存器
       
    mov  ecx,arraySize
    cmp  ecx,0             ; 数组大小 <= 0?
    jle  L2                ; 是: 退出
    mov  edx,ptrPrompt     ; 提示信息的地址
    mov  esi,ptrArray

L1:    call WriteString    ; 显示字符串
    call ReadInt           ; 把整数读入EAX
    call Crlf              ; 换行
    mov  [esi],eax         ; 保存入数组
    add  esi,4             ; 下一个整数
    loop L1

L2:    popad               ; 恢复所有寄存器
    ret
PromptForIntegers ENDP
END
与前面的 PromptForIntegers 版本比较,语句 enter 0,0 和 leave 不见了,这是因为当 MASM 遇到 PROC 伪指令及其声明的参数时,会自动生成这两条语句。同样,RET 指令也不需要自带常数参数了,PROC 会处理好。

_arraysum 模块

接下来,_arraysum.asm 文件包含了 ArraySum 过程:
; ArraySum 过程                 (_arrysum.asm)
INCLUDE sum.inc
.code
;-----------------------------------------------------
ArraySum PROC,
    ptrArray:PTR DWORD,    ; 数组指针
    arraySize:DWORD        ; 数组大小
;
; 计算 32 位整数数组之和
; 返回:  EAX = 和数
;-----------------------------------------------------
    push ecx              ; EAX 不入栈
    push esi

    mov  eax,0            ; 和数清零
    mov  esi,ptrArray
    mov  ecx,arraySize
    cmp  ecx,0            ; 数组大小 <= 0?
    jle  L2               ; 是: 退出

L1:    add  eax,[esi]     ; 将每个整数加到和数中
    add  esi,4            ; 指向下一个整数
    loop L1               ; 按数组大小重复

L2:    pop esi
    pop ecx               ; 用 EAX 返回和数
    ret
ArraySum ENDP
END

_display 模块

_display.asm 文件包含了 DisplaySum 过程:
; DisplaySum 过程        (_display.asm)
INCLUDE Sum.inc
.code
;-----------------------------------------------------
DisplaySum PROC,
    ptrPrompt:PTR BYTE,    ; 提示字符串
    theSum:DWORD           ; 数组之和
;
; 控制台显示和数
; 返回:无
;-----------------------------------------------------
    push    eax
    push    edx

    mov    edx,ptrPrompt        ; 提示信息的指针
    call    WriteString
    mov    eax,theSum
    call    WriteInt            ; 显示 EAX
    call    Crlf

    pop    edx
    pop    eax
    ret
DisplaySum ENDP
END

Sum_main 模块

Sum_main.asm ( 启动模块 ) 包含主程序并调用所有其他的过程。它使用 INCLUDE 从 sum.inc 复制过程原型:
; 整数求和程序         (Sum_main.asm)
INCLUDE sum.inc
Count = 3
.data
prompt1 BYTE  "Enter a signed integer: ",0
prompt2 BYTE  "The sum of the integers is: ",0
array   DWORD  Count DUP(?)
sum     DWORD  ?

.code
main PROC
    call Clrscr

    INVOKE PromptForIntegers, ADDR prompt1, ADDR array, Count
    INVOKE ArraySum, ADDR array, Count
    mov    sum,eax
    INVOKE DisplaySum, ADDR prompt2, sum

    call Crlf
    exit
main ENDP
END main
小结 本节与上一节《用Extern伪指令新建模块》展示了在 32 位模式中新建多模块程序的两种方法:
  • 第一种使用 的是更传统的EXTERN伪指令;
  • 第二种使用的是INVOKE. PROTO和PROC的高级功能。

后一种中的伪指令简化了很多细节,并为 Windows API 函数调用进行了优化。此外,它们还隐藏了一些细节,因此,编程者可能更愿意使用显式的堆栈参数和 CALL 及 EXTERN 伪指令。