1.嵌入汇编语言的格式

 C++语言是C语言的超集,它是在C语言的基础上扩展形成的面向对象程序设计语言。微软Visual C++ 5.0∕6.0则是Windows 9.x平台上广泛应用的开发系统。本节以Visual C++ 5.0∕6.0为例,说明32位Windows 9.x环境下汇编语言与C++的混合编程。它也分为嵌入汇编和模块调用两种方式。

      Visual C++直接支持嵌入汇编方式,不需要独立的汇编系统和另外的连接步骤。所以,嵌入式汇编比模块连接方式更简单方便。Visual C++的嵌入汇编方式与其他C∕C++编译系统的基本原理是一样的,当然有些细节的差别。嵌入汇编指令采用_ _asm关键字( 注意,_ _asm前是两个下划线;但Visual C++ 5.0∕6.0也支持一个下划线的格式_asm,目的是与以前版本保持兼容)。

      Visual C++嵌入汇编格式_ _asm{ 指令 }是采用花括号的汇编语言程序段形式,例如:
    //__asm程序段
     __asm
     {
     mov eax,01h   //支持汇编语言的注释格式
     mov dx,0xD007 ;0xD007=D007h,支持C/C++的数据表达形式
     out dx,eax
     }
     也具有单条汇编语言指令形式:
    //单条__asm汇编指令形式
     __asm mov eax,01h
     __asm mov dx,0D007h
     __asm out dx,eax
    另外,还可以使用空格在一行分隔多个__asm 汇编语言指令:
    //多个__asm 语句在同一行时,用空格将它们分开
     __asm mov eax,01h __asm mov dx,0xD007 __asm out dx,eax
    
     上面三种形式产生相同的代码,但第一种形式具有更多的优点。因为它可以将C++代码与汇编代码明确分开,避免混淆。如果将_ _asm指令和C++语句放在同一行且不使用括号,编译器就分不清汇编代码到什么地方结束和C++从哪里开始。_ _asm花括号中的程序段不影响变量的作用范围。_ _asm块允许嵌套,嵌套也不影响变量的作用范围。

2.嵌入汇编语言的注意事项

当然在嵌入汇编语言中也有很多的规矩,很多的 注意事项:

     1.  在_ _asm中使用汇编语言的注意事项
     嵌入式汇编代码支持80486的全部指令系统。Visual C++ 5.0∕6.0还支持MMX指令集。对于还不能支持的指令,Visual C++提供了_emit伪指令进行扩展。
     _emit伪指令类似MASM中的DB伪指令,可以用来定义一个字节的内容,并且只能用于程序代码段;例如:
     #define cpu-id __asm _emit 0x0F __asm _emit 0xA2   //定义汇编指令代码的宏
      __asm{ cpu-id }   //使用C++的宏
      嵌入式汇编代码虽然可以使用C++的数据类型和数据对象,但却不可以使用MASM的伪指令和操作符定义数据。程序员不能使用DB∕DW∕DD∕DQ∕DT∕DF伪指令和DUP∕THIS操作符;也不能使用MASM的结构和记录(不接受伪指令STRUCT、RECORD、WIDTH、MASK)。
      Visual C++不支持MASM的宏伪指令(如MACRO、ENDM、REPEAT∕FOR∕FORC等)和宏操作符(如 !、&、%等)。
      虽然嵌入式汇编不支持大部分MASM伪指令,但它支持EVEN和ALIGN。这些指令将NOP指令放在汇编代码中以便对齐边界。对有些处理器来说,这样可以更加有效地读取指令。
      嵌入式汇编代码可以使用LENGTH、SIZE、TYPE操作符来获取C++变量和类型的大小。LENGTH用来返回数组元素的个数,对非数组变量返回值为1;TYPE返回C++类型或变量的大小,如果变量是一个数组,它返回数组单个元素的大小。SIZE返回C++变量的大小,即LENGTH和TYPE的乘积。
     例如,对于数据int iarray[8](int类型是32位,4个字节),则:
      LENGTH iarray返回8(等同于C++的sizeof(iarray)/sizeof(iarray[0]));
      TYPE iarray返回4(等同于C++的sizeof(iarray[0]));
      SIZE iarray返回32(等同于C++的sizeof(iarray))。
      在用汇编语言编写的函数中,不必保存EAX、EBX、ECX、EDX、ESI和EDI寄存器;但必须保存函数中使用的其他寄存器(如DS、SS、ESP、EBP和整数标志寄存器)。例如用STD和CLD改变方向标志位,就必须保存标志寄存器的值。
      嵌入式汇编引用段时应该通过寄存器而不是通过段名;段超越时,必须清晰地用段寄存器说明,例如ES∶[EBX]。
      汇编语言的编写必须把修饰符写全,不能简写。比如
      __asm{ mov  [edi],0x065B42E9 } 一般看来肯定是mov的dword 其实不然,反汇编看到这条指令是 mov byte ptr [edi],0xE9 默认为byte了 所以必须写全修饰符
      正确的应该是 __asm{ mov dword ptr [edi],0x065B42E9 }
  还有就是注意十六进制数前必须加 0x 比如  mov dword ptr [edi+0xC],0xF9A4AEE9
      2.  在_ _asm中使用C++语言的注意事项
      嵌入式汇编代码可使用C++的下列元素:符号(包括标号、变量、函数名)、常量(包括符号常量、枚举成员)、宏和预处理指令、注释(/* */和//,也可以使用汇编语言的注释风格)、类型名及结构、联合的成员。
      嵌入式汇编语句使用C++符号也有一些限制:每一个汇编语言语句只包含一个C++符号(包含多个符号只能通过使用LENGTH、TYPE和SIZE表达式);_ _asm中引用函数前必须在程序说明其原形(否则编译程序将分不出是函数名还是标号);_ _asm中不能使用和MASM保留字(例如指令助记符和寄存器名)相同的C++符号,也不能识别结构Structure和联合union关键字。
      嵌入式汇编语言语句中,可以使用汇编语言格式表示整数常量(如378h),也可以采用C++的格式(如0x378)。
     嵌入式汇编语言语句不能使用C++的专用操作符,如<< ;对两种语言都有的操作符在汇编语句中作为汇编语言操作符,如 * 、[ ] 。例如:
      int array[6];     //C++语句中,[]表示数组的某个元素
      __asm mov array[6],bx //汇编语言中,[]表示距离标识符的字节偏移量
      嵌入式汇编中可以引用包含该_ _asm作用范围内的任何符号(包括变量名),它通过使用变量名引用C++的变量。例如:若var是C++中的×××int变量,则可以使用如下语句:
     __asm mov eax,var
      如果类、结构、联合的成员名字唯一,_ _asm中可不说明变量或类型名就可以引用成员名;否者必须说明。例如:
    struct first_type
    {
        char *carray;
        int same_name;
    };
    struct second_type
    {
        int ivar;
        long same_name;
    };
    struct first_type ftype;
    struct second_type stype;
    __asm
    {
        mov ebx,OFFSET ftype
        mov ecx,[ebx]ftype.same_name //必须使用ftype
        mov esi,[ebx].carray         //可以不使用ftype(也可以使用)
    }
     #define PORTIO __asm    \
    /* Port output */        \
    {                              \
        __asm mov eax,01h   \
        __asm mov dx,0xD007   \
        __asm out dx,eax    \
    }
     该宏展开为一个逻辑行(其中“\”是续行符):
    __asm /* Port output */ {__asm mov eax,01h __asm mov dx,0xD007 __asm out dx,eax}
      _ _asm 块中定义的标号对大小写不敏感,汇编语言指令跳转到C++中的标号也不分大小写,C++中的标号只有使用goto语句时对大小写敏感。

在VS2012中赋值操作中需要注意 不能直接赋值 这样得不到对应的值比如:

int nStatus; 	__asm 	{ 		mov nStatus,dword ptr [0x49419C] 	}

这种写法 编译出来的结果是 mov     dword ptr [ebp-0xC], 0x49419C 和预想结果不符合 写法上需要转换一下

正确的需要这样写:

int nStatus;     __asm     {         push eax         mov eax,dword ptr [0x49419C]         mov nStatus,eax                                pop eax     }

3.嵌入汇编语言的示例

 用_ _asm程序段编写函数

     嵌入式汇编不仅可以编写C∕C++函数,还可以调用C函数(包括C库函数)和非重载的全局C++函数,也可以调用任何用extern “C”说明的函数,但不能调用C++的成员函数。因为所有的标准头文件都采用extern “C”说明库函数,所以C++程序中的嵌入式汇编可以调用C库函数。
 
例7.13:嵌入式汇编编写函数
 
    // C++程序:LT713.CPP
    #include
    int power2(int,int);
    void main(void)
    {
        cout<<"2的6次方乘5等于:\t";
        cout<    }
    int power2(int num,int power)
    {
      __asm
      {
           mov eax,num ;取第一个参数
           mov ecx,power    ;取第二个参数
           shl eax,cl  ;计算EAX=EAX×(2^CL )
      }       //返回值存于EAX
    }
 
      汇编语句通过参数名就可以引用参数,采用return返回出口参数。本例中虽没有使用return语句,但仍然返回值,只是编译时可能产生警告(在设置警告级别为2或更高时)。返回值的约定是:对于小于等于32位的数据扩展为32位,存放在EAX寄存器中返回;4~8字节的返回值存放在EDX.EAX寄存器对中返回;更大字节数据则将它们的地址指针存放在EAX中返回。
      在Developer Studio开发系统中,建立一个WIN32控制台程序的项目,创建上述源程序后加入该项目。然后,进行编译连接就产生一个可执行文件。该程序运行后显示如下:
      2的6次方乘5等于:  320
      在Developer Studio开发系统中,可以通过Projects菜单Settings命令的Link标签设置加入调试信息(即∕Zi选项),嵌入式汇编就可以在源程序级进行调试;还可以在C∕C++标签中的Listing Files选择输出具有汇编语言程序输出列表(即∕FA、∕FAc、∕FAs、∕FAcs选项)。


4.调用规范

C∕C++与汇编语言混合编程的参数传递通常利用堆栈,调用规范决定利用堆栈的方法和命名约定,两者要一致,例如Visual C++的_cdecl调用规范与MASM的C语言类型。

      Visual C++语言具有三种调用规范(Calling Convntions):_cdecl、_stdcall和_fastcall。Visual C++缺省采用_cdecl调用规范;它在名字前自动加一个下划线,从右到左将实参压入堆栈,由调用程序进行堆栈的平衡。
      Windows图形用户界面过程和API函数等采用_stdcall调用规范;它在名字前自动加一个下划线,名字后跟@和表示参数所占字节数的十进制数值,从右到左将实参压入堆栈,由被调用程序平衡堆栈。
       Visual C++的_fastcall调用规范是在名字前、后都加一个@,后再跟表示参数所占字节数的十进制数值;首先利用寄存器ECX、EDX传递前两个双字参数,其他参数再通过堆栈传递(从右到左),被调用程序平衡堆栈。与其他语言进行混合编程时不要使用_fastcall规范。
       MASM汇编语言利用“语言类型”(Language Type)确定调用规范和命名约定,支持的语言类型有:C、SYSCALL、STDCALL、PASCAL、BASIC和FORTRAN。例如,我们通常采用C语言规范:它在标识符前自动加一个下划线,按照从右到左的顺序将调用参数压入堆栈,由调用程序平衡堆栈。