作 者: hjjdebug
时 间: 2008-06-18, 20:07

虽然我们不提倡用宏,宏本质上只不过是一种替换而已。
但宏汇编语言控制台程序中却大量使用了宏。经研究后,发现某些宏用起来确实方便。
这里推荐4个,chr$, str$,print,input
当然,象那些简单的宏例如exit 宏,就随你意愿可用可不用了。

下面是控制台程序中常用的宏:
1. 控制台程序 print 宏的完全解析

例子程序可以参考宏汇编的tutorial\console 下的程序,我们重点分析一句代码:
print chr$("Hey, this actually works.",13,10)
先看chr$ 宏
      chr$ MACRO any_text:VARARG
        LOCAL txtname
        .data
          txtname db any_text,0
        .code
        EXITM <OFFSET txtname>
      ENDM
chr$ 是带一个参数的宏。
1. 在数据区中定义一个标号txtname,这个标号是局部的,意思是说,仅在该宏中有意义。
  然后把后续的参数定义到数据区中,尾巴补0.
2. 宏结束,返回一个标号地址。

再看print 宏
    print MACRO arg1:REQ,varname:VARARG      ;; display zero terminated string
        invoke StdOut,reparg(arg1)
      IFNB <varname>
        invoke StdOut,chr$(varname)
      ENDIF
    ENDM

1. print 宏似乎带2个参数的宏?一个arg1, 一个varname, varname 可以为空。当为空时,只替代第一句。
invoke  StdOut, reparg(arg1)       

再看reparg (arg1) 宏
 ; -----------------------------------------------------------
 ; This macro replaces quoted text with a DATA section OFFSET
 ; and returns it in ADDR "name" format. It is used by other
 ; macros that handle optional quoted text as a parameter.
 ; -----------------------------------------------------------
   reparg MACRO arg
     LOCAL nustr
       quot SUBSTR <arg>,1,1
     IFIDN quot,<">            ;; if 1st char = "
       .data
         nustr db arg,0        ;; write arg to .DATA section
       .code
       EXITM <ADDR nustr>      ;; append name to ADDR operator
     ELSE
       EXITM <arg>             ;; else return arg
     ENDIF
   ENDM  
   
1. 判断参数第一个字符是否为", 如是, 定义为数据区,末尾补0,返回地址。否则,直接退出。返回原arg.
  可见,args 经replace 宏替代后,可以不用chr$ 宏
; ---------------------------------------------------------------------------
分析完后,可以做个新实验。
改原句为如下,应也可工作   
print "Hey, this actually works.",13,10

虽然打印结果没有变化,但实际宏的控制过程却是改变了的。   
实际宏的工作过程是,将print 后所跟参数看成两个参数, "Hey, this actually works." 为第一参数,replace 参数,
第二部分13,10 ... 为可变参数,print 的第二参数, 用chr$ 宏修饰之。所以编译的结果形成两条语句。
       invoke StdOut,reparg(arg1)          .data
                    ??0019 db "Hey, this actually works.",0        
                  .code
                  invoke StdOut,ADDR ??0019

       invoke StdOut,chr$(varname)       .data
                    ??001A db 13,10,0
                  .code
                  invoke StdOut,OFFSET ??001A  
  也一样能满足要求。
如此看来,用chr$ 修饰字符串,有更高的效率。
print chr$("Hey, this actually works.",13,10)  .data
                    ??0019 db "Hey, this actually works.",13,10,0
            invoke StdOut,OFFSET ??0019
; ---------------------------------------------------------------------------
说了一大堆,到底得出点什么结论呢?两点最重要
1. chr$ 是个宏,用来定义0字终结字符串。
2. print 是个宏,用来执行StdOut 函数。


另外,关于exit 宏:如下定义
 ; --------------------------------------------------------
 ; exit macro with an optional return value for ExitProcess
 ; --------------------------------------------------------
   exit MACRO optional_return_value
     IFNDEF optional_return_value
       invoke ExitProcess, 0
     ELSE
       invoke ExitProcess,optional_return_value
     ENDIF
   ENDM
也就是说,根据exit 宏是否带参数,将被翻译成不同的ExitProcess 函数。

  • 标 题:答复
  • 作 者:hjjdebug
  • 时 间:2008-06-18 20:10:44

input 宏有如下的解释和定义
comment * -------------------------------------

    use the "input" macro as follows,

    If you want a prompt use this version
    mov lpstring, input("Type text here : ")

    If you don't need a prompt use the following
    mov lpstring, input()

    NOTE : The "lpstring" is a preallocated
           DWORD variable that is either LOCAL
           or declared in the .DATA or .DATA?
           section. Any legal name is OK.

    LIMITATION : MASM uses < > internally in its
    macros so if you wish to use these symbols
    in a prompt, you must use the ascii value
    and not use the symbol literally.

    EXAMPLE mov var, input("Enter number here ",62," ")

    ------------------------------------------- *
  input MACRO prompt:VARARG
      LOCAL txt
      LOCAL buffer
    IFNB <prompt>
      .data
        txt db prompt, 0
        buffer db 128 dup (0)
        align 4
      .code
      invoke StdOut,ADDR txt
      invoke StdIn,ADDR buffer,LENGTHOF buffer
      invoke StripLF,ADDR buffer
      mov eax, offset buffer
      EXITM <eax>
    ELSE
      .data
        buffer db 128 dup (0)
        align 4
      .code
      invoke StdIn,ADDR buffer,LENGTHOF buffer
      invoke StripLF,ADDR buffer
      mov eax, offset buffer
      EXITM <eax>
    ENDIF
  ENDM
  ; ---------------------------------------------------------------------------
有了print 宏解释的基础,input 宏就好理解了。我这里简单翻译一下,为意译非逐字译。
input 宏有两种用法,根据是否有提示信息而划分.
  一种带提示,例如:
      mov lpstring, input("Type text here : ")
      一种不带提示:例如:
         mov lpstring, input()
其中 lpstring 是预先分配好的变量,可以为局部变量或存在于data, data?段中。它是一个
只占4字节的指针。 而那个input 为我们提供的缓冲区,是128字节。定义在数据区中,如果
promp 不为空,promp 字符串也会被定义在数据区中。


; ----------------------------------------------------------
;  str$ macros that takes a DWORD parameter and
; returns the address of the buffer that holds the result.
; The return format is for use within the INVOKE syntax.
; ----------------------------------------------------------
  str$ MACRO DDvalue
    LOCAL rvstring
    .data
      rvstring db 20 dup (0)
      align 4
    .code
    invoke dwtoa,DDvalue,ADDR rvstring
    EXITM <ADDR rvstring>
  ENDM
  
str$ 宏以DWORD 值为参数,定义一个缓冲区20bytes 接受转换后的字符串。
然后调用dwtoa 函数将数值转换为字符串。注意,字符串是10进制的。
str$ 宏把数值转变成字符串。在数据区定义了20 bytes 空间,隐含调用dwtoa (或dw2a)函数。
chr$ 宏在数据区定义了相关数据。

这两个宏比较重要,可以常用,再加上print 宏, input宏,
print 宏,隐含调用了StdOut 向屏幕输出字符串。字符串定义在堆中。
input 宏,隐含调用了StdIn, 并在堆中定义了128 个bytes 来接受键盘的输入。
这四个宏需要掌握。
其它宏视情况了,理解了可以用,不理解就不用,尽量少用。

宏与函数的显著差别是什么?
宏是编译时的替代,函数是运行时的连接。这是实质差别。
调用宏是不需要invoke 修饰的,而调用函数需要用invoke 修饰(供查错用)。这是书写差别。

  • 标 题:答复
  • 作 者:hjjdebug
  • 时 间:2008-06-19 20:44:02

宏不仅在控制台下常用,在windows 程序中也可以常用。不过print 和 input 宏就不好用了,但chr$ 和 str$ 还一样能用。举一个简单例子吧。
  .386
  .model flat,stdcall
  option casemap:none
  
  include windows.inc
  include user32.inc
  include kernel32.inc
  include masm32.inc
  include c:\masm32\macros\macros.asm
  includelib masm32.lib
  includelib kernel32.lib
  includelib user32.lib
  
  .code
start:  
  mov  eax,100
  invoke  MessageBox,NULL,str$(eax), chr$("hello"), MB_OK
  invoke ExitProcess,NULL
  end  start

如果不用宏完成这个功能,既要在数据区定义字符串,又要定义数据转换接受区空间,又要调用整数到ascii 的转换函数。而这个程序,在一个messagebox 中全部搞定。