在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:
评论加载中...
|