设为主页 | 加入收藏 | 繁體中文

在Visual C++中使用内联汇编

  本文出自 Yonsm 年老之手
  (老罗:这可是佳构中的佳构哦!强烈推荐!大家好好学学 :))
  在 Visual C++ 中利用内联汇编
  一、内联汇编的优缺陷
  由于在Visual C++中利用内联汇编不必要分外的编译器和联接器,且可以处理Visual C++中不克不及处理的一些事变,而且可以利用在C/C++中的变量,以是十分方便。内联汇编主要用于如下场所:
  1.利用汇编言语写函数;
  2.对速率要求十分高的代码;
  3.设置装备摆设驱动步伐中间接拜访硬件;
  4."Naked" Call的初始化和结束代码。
  //(."Naked",明白了意思,但是不知道怎么翻译^_^,大概便是不必要C/C++的编译器(自作聪明)天生的函数初始化和扫尾代码,请参看MSDN的"Naked functions"的说明)
  内联汇编代码不易于移植,要是你的步伐计划在差别范例的呆板(比如x86和Alpha)上运行,应当尽量避免利用内联汇编。这时候你可以利用MASM,由于MASM支持更方便的的宏指令和数据指示符。
  二、内联汇编要害字
  在Visual C++利用内联汇编用到的是__asm要害字,这个要害字有两种利用要领:
  1.简单__asm块
  __asm
  {
  MOV     AL, 2
  MOV     DX, 0xD007
  OUT     AL, DX
  }
  2.在每条汇编指令之前加__asm要害字 
  __asm MOV   AL, 2
  __asm MOV   DX, 0xD007
  __asm OUT   AL, DX
  由于__asm要害字是语句分开符,因此你可以把汇编指令放在统一行:
  __asm MOV AL, 2     __asm MOV DX, 0XD007    __asm OUT AL, DX
  显然,第一种要领和C/C++的气势派头很同等,而且有很多其它优点,因此推荐利用第一种要领。
  不象在C/C++中的"{}",__asm块的"{}"不会影响C/C++变量的作用范围。同时,__asm块可以嵌套,嵌套也不会影响变量的作用范围。
  三、在__asm块中利用汇编言语
  1.内联汇编指令集
  内联汇编完全支持的Intel 486指令集,容许利用MMX指令。不支持的指令可以利用_EMIT伪指令界说(_EMIT伪指令说明见下文)。
  2.MASM表达式
  内联汇编可以利用MASM中的表达式。比如: MOV EAX, 1。
  3.数据指示符和操作符
  固然__asm块中容许利用C/C++的数据范例和工具,但它不克不及用MASM指示符和操作符界说数据工具。这里特别指出,__asm块中不容许MASM中的界说指示符: DB、DW、DD、DQ、DT和DF,也不容许DUP和THIS操作符。MASM结谈判记录也不再有用,内联汇编不接受STRUC、RECORD、WIDTH大概MASK。
  4.EVEN和ALIGN指示符
  只管内联汇编不支持大少数MASM指示符,但它支持EVEN和ALIGN,当必要的时候,这些指示符在汇编代码内里加入NOP(空操作)指令使标号对齐到特定边界。这样可以使某些处理器取指令时具有更高的效率。
  5.MASM宏指示符
  内联汇编不是宏汇编,不克不及利用MASM宏指示符(MACRO、REPT、IRC、IRP和ENDM)和宏操作符(<>、!、&、%和.TYPE)。
  6.段说明
  必需利用寄存器来说明段,跨越段必需显式地说明,如ES:[BX]。
  7.范例和变量大小
  我们可以利用LENGTH来取得C/C++中的数组中的元素个数,要是不是一个数组,则效果为一。利用SIZE来取得C/C++中变量的大小,一个变量的大小是LENGTH和TYPE的乘积。TYPE用来取得一个变量的大小,要是是一个数组,它得到的一个数组中的单个元素的大小。
  8.注释
  可以利用C/C++的注释,但推荐用ASM的注释,即";"号。
  9._EMIT伪指令
  _EMIT伪指令相当于MASM中的DB,但一次只能界说一个字节,比如:
  __asm
  {
  JMP     _CodeOfAsm
  _EMIT   0x00    ; 界说混合在代码段的数据
  _EMIT   0x01
  _CodeOfAsm:
  ; 这里是代码
  _EMIT   0x90    ; NOP指令
  }
  四、在__asm块中利用C/C++言语元素
  C/C++与汇编可以混合利用,在内联汇编可以利用C/C++的变量和很多其它C/C++的元素。在__asm块中可以利用以下C/C++元素:
  1.标记,包罗标号、变量和函数名;
  2.常量,包罗标记常量和枚举型(enum)成员;
  3.宏界说和预处理指示符;
  4.注释,包罗"/**/"和"//";
  5.范例名,包罗全部MASM中正当的范例
  6.typedef称号, 像PTR、TYPE、特定的布局成员或枚举成员这样的通用操作符。
  在__asm块中,可以利用C/C++或ASM的基数计数法(比如: 0x100和100H是相等的)。
  __asm块中不克不及利用像<<一类的C/C++操作符。C/C++和MASM通用的操作符,比如"*"和"[]"操作符,都被认为是汇编言语的操作符。举个例子:
  int array[[10]];
  __asm MOV array[[6]], BX ;  Store BX at array+6 (not scaled)
  array[[6]] = 0;         /* Store 0 at array+12 (scaled) */
  * 小本领: 内联汇编中,你可以利用TYPE操作符使作其与C同等。比如,上面两条语句是一样的:
  __asm MOV array[[6 * TYPE int ], 0 ; Store 0 at array + 12
  array[[6]] = 0;                   /* Store 0 at array + 12 */
  内联汇编能经过变两名间接引用C/C++的变量。__asm块中可以引用任何标记,包罗变量名。
  要是C/C++中的类、布局大概枚举成员具有独一的称号,要是在"."操作符之前不指定变量大概typedef称号,则__asm块中只能引用成员称号。但是,要是成员不是独一的,你必需在"."操作符之前加上变量名或typedef称号。比方,上面的两个布局都具有same_name这个成员变量:
  struct first_type
  {
  char *weasel;
  int same_name;
  };
  struct second_type
  {
  int wonton;
  long same_name;
  };
  要是按上面声明变量:
  struct first_type hal;
  struct second_type oat;
  那么,全部引用same_name成员的中央都必需利用变量名,由于same_name不是独一的。别的,上面的weasel变量具有独一的称号,你可以仅仅利用它的成员称号来引用它:
  __asm
  {
  MOV EBX, OFFSET hal
  MOV ECX, [EBX]hal.same_name ; 必需利用 'hal'
  MOV ESI, [EBX].weasel       ; 可以省略 'hal'
  }
  注意,省略了变量名仅仅是为了写代码的方便,天生的汇编指令的照旧一样的。
  可以不受限定地拜访C++成员变量,但是不克不及挪用C++的成员函数。
  五、寄存器利用
  一样平常来说,在__asm块开端的时候,寄存器是空的,不克不及在两个__asm之间生存寄存器的值。(这是MSDN上说的,我在实际利用时发明,好像并不是这样。不外它是说"一样平常",我是特殊:))
  要是一个函数被声明成了__fastcall,则其参数将放在寄存器中,这将给寄存器的办理带来题目。以是,要是要将一个函数声明成__fastcall,必需生存ECX寄存器。为了避免以上的辩论,在声明为__fastcall的函数中不要有__asm块。要是用了/Gr编译选项(它全局的变成__fastcall),将每个函数声明成__cdecl大概__stdcall,这个属性告诉编译器用传统的C要领。
  要是利用EAX、EBX、ECX、EDX、ESI和EDI寄存器,你不必要生存它;但要是你用到了DS、 SS、SP、BP和标记寄存器,那就应该PUSH生存这些寄存器。
  要是步伐中改变了用于STD和CLD的方向标记,你必需将其恢复到原来的值。
  六、转跳
  可以在C内里利用goto调到__asm块中的标号处,也可以在__asm块直达跳到__asm块内里和表面的标号处。__asm块内的标号是不区分大小写的(指令、指示符等也是不区分大小写的)。例:
  void func()
  {
  goto C_Dest;    /* 正当 */
  goto c_dest;    /* 错误 */
  goto A_Dest;    /* 正当 */
  goto a_dest;    /* 正当 */
  __asm
  {
  JMP C_Dest  ; 正当
  JMP c_dest  ; MSDN上说正当,但是我在VS.NET中编译,认为这样不正当
  JMP A_Dest  ; 正当
  JMP a_dest  ; 正当
  a_dest:     ; __asm 标号
  }
  C_Dest:     /* C的标号 */
  return;
  }
  不要利用函数称号当作标号,不然将使其跳到函数实行而不是标号处。如下所示:
  ; 错误: 利用函数名作为标号
  JNE exit
  .
  .
  .
  exit:
  ; 上面是更多的ASM代码
  美元标记$用于指定以后地位,如下所用,常用于条件跳转:
  JNE $+5 ; 上面这条指令的长度是5个字节
  JMP farlabel
  ;$+5,跳到了这里
  .
  .
  .
  farlabel:
  七、挪用函数
  内联汇编挪用C/C++函数必需本身扫除货仓,上面是一个挪用C/C++函数例子:
  #include
  char szformat[] = "%s %s\n";
  char szHello[] = "Hello";
  char szWorld[] = " world";
  void main()
  {
  __asm
  {
  MOV     EAX, OFFSET szWorld
  PUSH    EAX
  MOV     EAX, OFFSET szHello
  PUSH    EAX
  MOV     EAX, OFFSET szformat
  PUSH    EAX
  CALL    printf
  //内联汇编挪用C函数必需本身扫除货仓
  //用不利用的EBX寄存器扫除货仓,或ADD ESP, 12
  POP     EBX
  POP     EBX
  POP     EBX
  }
  }
  注意:函数参数是从右向左压栈。
  不克不及够拜访C++中的类成员函数,但是可以拜访extern "C"函数。
  要是挪用Windows API函数,则不必要本身扫除货仓,由于API的前往指令是RET n,会自动扫除货仓
  比如上面的例子:
  #include
  char szAppName[] = "API Test";
  void main()
  {
  char szHello[] = "Hello, world!";
  __asm
  {
  PUSH    MB_OK OR MB_ICONINformATION
  PUSH    OFFSET szAppName    ; 全局变量用OFFSET
  LEA     EAX, szHello        ; 局部变量用LEA
  PUSH    EAX
  PUSH    0
  CALL    DWORD PTR [MessageBoxA]     ; 注意这里,我费了好大周折才发明不是CALL MessageBoxA
  }
  }
  一样平常来说,在Visual C++中利用内联汇编是为了提高速率,因此这些函数挪用尽可能用C/C++写。
  八、一个例子
  上面的例子是在VS.NET(即VC7)中C言语写的。先建一个工程,将下列代码放到工程中的.c文件中编译,无需作特别的设置,即可编译经过。
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  //预处理
  #include
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  //全局变量
  HWND g_hWnd;
  HINSTANCE g_hInst;
  TCHAR szTemp[1024];
  TCHAR szAppName[] = "CRC32 Sample";
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  //函数声明
  DWORD GetCRC32(const BYTE *pbData, int nSize);
  int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow);
  LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  //主函数
  int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
  {
  MSG msg;
  WNDCLASSEX wndClassEx;
  g_hInst = hInstance;
  wndClassEx.cbSize = sizeof(WNDCLASSEX);
  wndClassEx.style = CS_VREDRAW | CS_HREDRAW;
  wndClassEx.lpfnWndProc = (WNDPROC) WindowProc;
  wndClassEx.cbClsExtra = 0;
  wndClassEx.cbWndExtra = 0;
  wndClassEx.hInstance = g_hInst;
  wndClassEx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wndClassEx.hCursor = LoadCursor(NULL, IDC_ARROW);
  wndClassEx.hbrBackground = (HBRUSH) (COLOR_WINDOW);
  wndClassEx.lpszMenuName = NULL;
  wndClassEx.lpszClassName = szAppName;
  wndClassEx.hIconSm = NULL;
  RegisterClassEx(&wndClassEx);
  g_hWnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRame | WS_MINIMIZEBOX,
  CW_USEDEFAULT, CW_USEDEFAULT, 300, 70,
  NULL, NULL, g_hInst, NULL);
  ShowWindow(g_hWnd, iCmdShow);
  UpdateWindow(g_hWnd);
  while (GetMessage(&msg, NULL, 0, 0))
  {
  TranslateMessage(&msg);
  DispatchMessage(&msg);
  }
  return ((int) msg.wParam);
  }
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  //主窗口回调函数
  LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  {
  switch (uMsg)
  {
  case WM_CREATE:
  CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_NOHIDESEL | WS_OVERLAPPED,
  7, 12, 220, 22,
  hWnd, (HMENU)1000, g_hInst, NULL);
  CreateWindowEx(0, "BUTTON", "&OK", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_OVERLAPPED | BS_FLAT,
  244, 12, 40, 20,
  hWnd, (HMENU)IDOK, g_hInst, NULL);
  break;
  case WM_COMMAND:
  switch (LOWORD(wParam))
  {
  case IDOK:
  GetDlgItemText(g_hWnd, 1000, szTemp + 100, 800);
  wsprintf(szTemp, "以后文本框内的字符串的CRC32校验码是: 0x%lX", GetCRC32(szTemp + 100, (int)strlen(szTemp + 100)));
  MessageBox(g_hWnd, szTemp, szAppName, MB_OK|MB_ICONINformATION);
  }
  break;
  case WM_DESTROY:
  PostQuitMessage(0);
  break;
  default:
  return (DefWindowProc(hWnd, uMsg, wParam, lParam));
  }
  return (0);
  }
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  //GetCRC32: 求字节省的CRC32校验码
  //参数:
  //      pbData: 指向字节省缓冲区首地点
  //      nSize: 字节省长度
  //
  //前往值:
  //      字节省的CRC32校验码
  //
  //这里利用查表法求CRC32校验码,这部门是参考老罗的文章《 矛与盾的比力(2)——CRC原理篇》该写的。
  //原文的详细内容请参看: http://www.luocong.com/articles/show_article.asp?Article_ID=15
  //
  //上面利用内联汇编求CRC32校验码,充实利用了CPU中的寄存器,速率和方便性都是利用C/C++所不克不及比拟的
  //
  DWORD GetCRC32(const BYTE *pbData, int nSize)
  {
  DWORD dwCRC32Table[256];
  __asm   //这片内联汇编是初始化CRC32表
  {
  MOV     ECX, 256
  _NextTable:
  LEA     EAX, [ECX-1]
  PUSH    ECX
  MOV     ECX, 8
  _NextBit:
  SHR     EAX, 1
  JNC     _NotCarry
  XOR     EAX, 0xEDB88320
  _NotCarry:
  DEC     ECX
  JNZ     _NextBit
  POP     ECX
  MOV     [dwCRC32Table + ECX*4 - 4], EAX
  DEC     ECX
  JNZ     _NextTable
  }
  __asm   //上面是求CRC32校验码
  {
  MOV     EAX, -1
  MOV     EBX, pbData
  OR      EBX, EBX
  JZ      _Done
  MOV     ECX, nSize
  OR      ECX, ECX
  JZ      _Done
  _NextByte:
  MOV     DL, [EBX]
  XOR     DL, AL
  MOVZX   EDX, DL
  SHR     EAX, 8
  XOR     EAX, [dwCRC32Table + EDX*4]
  INC     EBX
  LOOP    _NextByte
  _Done:
  NOT     EAX
  }
  }
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  九、后话
  原来计划写个综合各种内联汇编用法的例子,由于本人拖拖沓拉的风俗,虽经dREAMtHEATER屡次催促,到最后照旧不免敷衍了事。要是有什么谬误或不明之处,欢迎品评指正并一同探讨(QQ: 123018)。
  Yonsm@163.com
  2002.9.3
  ==============================================================
  >> 参考材料:
  1.Sunwen,《VC内联ASM汇编学习笔记》
  2.老罗,《矛与盾的比力(2)——CRC原理篇》
  3.Microsoft,《MSDN Library - January 2001》
  ~·  全文完 ·~
 


    文章作者: 福州军威计算机技术有限公司
    军威网络是福州最专业的电脑维修公司,专业承接福州电脑维修、上门维修、IT外包、企业电脑包年维护、局域网网络布线、网吧承包等相关维修服务。
    版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处 、作者信息和声明。否则将追究法律责任。

TAG:
评论加载中...
内容:
评论者: 验证码: