• 沒有找到結果。

汇编语言与高级语言的混合编程

; WndMainProc 结束

;--- _Init proc

invoke SendMessage,hWinMain,WM_SETICON,ICON_SMALL,hIcon ret

_Init endp

;---

; _Init 结束

;--- _Quit proc

invoke DestroyWindow,hWinMain invoke PostQuitMessage,NULL ret

_Quit endp

;---

; _Quit 结束

;--- end start

3.代码分析

程序首先调用_WinMain,在_WinMain 中定义了两个局部变量:@stMsg 和@stWinMain,

数据类型分别是MSG 和 WNDCLASSEX 结构,WNDCLASSEX 结构定义了一个窗口的所有 参数,包括所使用的菜单、光标、颜色、窗口过程等,接下来的一系列 mov 指令实际上就是 在填写这个数据结构。mov @stWcMain.lpfnWndProc,offset WndMainProc 定义了处理消息的窗 口过程,mov @stWcMain.lpszClassName,offset szClassName 定义了要创建的类的名称,然后使 用RegisterClassEx API 函数注册这个窗口类。此时窗口并没有创建,只是定义好了一个子类,

接下来的工作是用刚才定义的类去创建一个窗口。这是通过 CreateWindowEx 函数实现的。

在窗口创建好之后,应用程序的主要任务就是执行消息循环,捕获用户在窗口中发送的各种消 息并进行处理,当用户单击关闭窗口按钮后,执行窗口的销毁操作,窗口生命周期结束。

5.5 汇编语言与高级语言的混合编程

与高级语言相比,汇编语言编写的程序目标代码占用存储空间小、运行效率高,它的运 行速度是高级语言无法比拟的。但是如果全部采用汇编语言编程,则工作量又太大。高级语言 采用结构化程序设计技术,代码简洁、开发效率高,但在有些要求由硬件直接进行操作的场合,

或者要求执行速度很快的场合,仍然需要用汇编语言实现。为了更好地发挥高级语言和汇编语 言各自的优点,将两者有机地结合起来,采用混合编程方法能更好地达到设计要求,完成设计 功能。

5.5.1 汇编语言与 C/C++的混合编程

C/C++语言是一种被广泛使用的程序设计语言,它具有一些汇编语言的特点,如可以使用 寄存器变量、可以进行位操作等。所以,C/C++语言与汇编语言程序之间能很平滑地衔接。另

外,目前主要的C/C++语言程序开发环境,如 Turbo C/C++、Borland C/C++和 Visual Studio 2005

(或Visual Studio 2008)等,也提供了很好的混合编程手段。本节主要介绍汇编语言和 C 语 言的混合编程方法。C/C++和汇编语言的混合编程有两种方式:一种是在 C/C++程序中嵌入汇 编指令;另一种是将独立的汇编模块与C/C++模块相连接。它们的编程规则有一定的区别。

1.混合编程的基本规则

混合编程主要应解决两种语言的接口及参数传递等问题。

(1)参数传递规则。编译器的调用规范将影响函数命名、参数传递顺序、堆栈清理责任、

参数传递机制等,常用的函数调用规范有_cdecl、_stdcall 和_pascal。C 语言默认的调用规范是 _stdcall,即函数调用时通过堆栈传递参数,传递顺序是从右向左,由调用者恢复堆栈指针。

如对于函数Add(x,y),函数被调用时,先将参数 y 压栈,再将参数 x 压栈,关于函数调用的详 细讨论请参见“2.函数调用约定:_stdcall 和_cdecl”。

(2)返回值规则。C 语言函数调用返回后,根据返回值的长度不同,由不同的寄存器提 供返回值。若返回值为32 位,则返回值存放在 EAX 中;若返回值为 64 位,则将返回值存放 在EDX:EAX 中,其中 EDX 存放高 32 位,EAX 存放低 32 位。

(3)寄存器使用规则。对于寄存器 CS、EIP、SS、ESP、EDS、EBP、ESI 和 EDI,在函 数中使用前应该先保存,使用完后再恢复它们的值。寄存器 EAX、EBX、ECX、EDX 和 ES 的值不需要保存,可以在程序中任意使用。

2.函数调用约定:_stdcall 和_cdecl

函数调用约定规定了参数传递顺序、参数传递规则(传值还是传引用)、堆栈参数维 护、函数名修饰。进行高级语言和汇编语言混合编程时,必须满足函数调用约定,否则无 法连接。

Visual C++有两种常用的函数调用约定:_stdcall 和_cdecl,如表 5-6 所示。

表 5-6 函数调用约定

_stdcall _cdecl

参数传递顺序 从右向左 从左向右

参数传递规则 传值,除非传指针或引用 传值,除非传指针或引用

堆栈参数维护 被调用函数清除堆栈参数 调用函数清除堆栈参数

函数名修饰 编译器自定 编译器自定

从表 5-5 可以看出,_stdcall 和_cdecl 的一个重要区别在于:谁对堆栈参数的维护负责,

是调用函数还是被调用函数负责清理堆栈。

_stdcall 约定被调用函数清除堆栈参数,这样做的好处是可以减少源代码的大小,因为每 次函数调用完成后,调用函数不再需要清理堆栈的指令;但是有些函数必须使用_cdecl 调用约 定,这种情况是参数个数未知的函数,这时只能由调用函数清除堆栈参数,比如库函数printf。

代码片段如表5-7 和表 5-8 所示。

3.使用内嵌汇编器进行混合编程

有时为了提高 C/C++语言源程序中某段程序的处理效率,可以在 C/C++程序中嵌入一段 汇编程序。虽然这样做可以提高程序处理效率,但必须明确,这是以丧失程序的可移植性为代 价的。因此,对C 语言和汇编语言进行混合编程时,一定要仔细权衡采用该方法的利与弊。

表 5-7 _stdcall 调用代码 表 5-8 _cdec 调用代码 _asm MOV DL,42H _asm INT 21H

MOV data1,EAX void disp (int base,int data)

{

char*info="Enter a number between 0 and 1000:";

//提示信息

printf(":");

disp(dbase[i],a);

printf("\n");

i++;

} }

在上述程序中,main 函数实现输入输出。函数 disp 将参数 data 指定的数以 base 指定的值 为基数进行显示。其中,循环LOP1 进行进制转换,并将转换后得到的每一位数压栈保存。循 环LOP2 则从堆栈中逐位取出,转换为 ASCII 码并显示在屏幕上。

在函数disp 中,多次出现内嵌汇编语句直接访问用 C 语言定义的数据的情形,这正是混 合编程的灵活之处。但在应用时需要注意数据长度的一致性。

本小节通过具体的例子分析了汇编函数和高级语言函数间通过内嵌汇编代码方式进行混 合调用的情况,下面通过由C/C++和汇编语言编译器分别生成目标文件,然后进行连接,生成 可执行程序,进一步讨论C/C++和汇编语言进行混合编程的问题。

4.将汇编源程序和 C/C++源程序分别编译然后对目标文件连接

如前所述,内嵌汇编器有一定的限制,不能充分利用MASM 的高级宏汇编功能。在某些 情况下,可开发独立的汇编语言模块,然后与C/C++模块连接,生成一个独立的可执行程序,

这种方法更加灵活。

下面的实例介绍了在C/C++中调用外部汇编代码的过程。

例 5-41 主函数用C 实现,从键盘接收两个整数,并作为参数传递给汇编过程,汇编过 程实现两个数相加的功能,程序如下:

#include<stdio.h>

extern "C" int plus(int,int); //声明为外部函数 void main()

{

int x,y;

printf("Enter two numbers:");

scanf("%d","%d",&x,&y); //接收键盘输入 printf("x+y=%d",plus(x,y));

}

上述程序的extern 语句说明 plus 是一个外部过程(在本例中是用汇编语言编写的),本程 序中将会使用该函数。

注意:过程名(函数名)是大小写敏感的,应该保证它在汇编程序和C 程序模块中一致。

在extern 命令后面的“C”用于告诉编译器该外部函数遵循 C 语言的命名约定,编译时不加任 何修饰。

用汇编程序实现加法功能,程序如下:

. 386 ;386 指令集

. MODEL FLAT,C ;内存使用模式为 FLAT . CODE

PUBLIC PLUS

PLUS PROC ;过程定义

PUSH EBP

MOV EBP,ESP

MOV EAX,[EBP+8] ;取参数 MOV EBX,[EBP+12] ;取参数

DD EAX,EBX

POP EBP

RET PLUS ENDP

END

在上述汇编程序中,语句.386 指示汇编器选择实模式下的 386 指令系统,若没有该语句,

则汇编程序采用默认的 8086 指令系统。.MODEL 语句告诉汇编程序使用何种内存模式。

MODEL 语句中的“C”指示汇编程序,该程序中的过程将被 C 语言程序调用。第 4 行的 PUBLIC 语句将PLUS 过程声明为全局过程。

在 32 位应用程序中,C/C++与汇编过程传递参数时,除数组参数外,所有参数都采用传 值的方式,并且都扩展为32 位。数组参数传递的是 32 位的数组首地址偏移量。在 PLUS 过程 中,通过寄存器EBP 来访问参数。在使用 EBP 前,先将 EBP 压栈,以保证使用前后一致。

在EBP 压栈后,堆栈中的数据如图 5-9(a)所示。这时,参数 x 和参数 y 的偏移量分别是[EBP+8]

和[EBP+12]。两数相加的结果通过寄存器 EAX 返回到主调函数。执行 RET 指令后,ESP 即指 向参数x,如图 5-9(b)所示,这时的参数 x 和 y 已经不再有用,应该及时清除这两个参数。

这里没有使用带参数的RET 指令,是因为 C 语言的调用规范中规定由主函数自动调整堆栈指 针,因此函数返回到main 之后,堆栈指针恢复到调用函数之前,如图 5-9(c)所示。

图5-9 堆栈变化示意图

假设C 语言程序的名字是 exam.c,汇编源程序的名字是 plus.asm。完成了 C 程序和汇编 源程序的编写后,将exam.c 编译为 exam.obj,将 plus.asm 汇编为 plus.obj。在 C 中建立项目文 件,将C 程序和汇编后的目标代码都加入到工程项目文件中,然后进行连接生成可执行文件。

这里的汇编程序必须是MASM 6.11 以上版本,汇编程序名是 ML.EXE,使用格式为:

ml /c /coff plus.asm

这里的开关/c 指示汇编程序只进行汇编,不进行连接;开关/coff 指示汇编程序生成 COFF

(Common Object File Format)格式的文件。这两个参数是必需的,且参数只能出现在文件名 的左边,否则,参数将被忽略。

5.5.2 MASM 32 汇编与连接命令

MASM 32 是自由软件开发者推出的基于 32 位操作系统的宏汇编程序开发环境,它包括 宏编译器(ML.EXE)、连接器(LINK.EXE)和资源编译器(RC.EXE)。编译器 ML.EXE 的作

用是把源程序编译成二进制文件,与宏汇编程序MASM 的作用是一样的。LINK.EXE 的作用 是把各个模块连接起来,并完成再定位的工作,对于COFF 格式的 LIB 文件中的代码和数据 也要连接进来(如MASM32.LIB)。RE.EXE 的作用是把资源编译成二进制 RES 文件。连接时 将调用cvtres.exe 将 RES 文件转换为 COFF 格式,组成一个应用程序。

1.编译和连接

MASM 32 提供了集成开发环境,即集编辑、编译、连接于一体,集成编辑器的执行文件 为QEditor.exe。但建议不要使用集成环境进行源程序编辑。

Win32 汇编源程序的编辑与在 DOS 下完全相同,任何文本编辑器都可以以纯文本方式保 存,扩展名为.asm,如 main.asm。可以直接在 DOS 命令提示符下编译和连接。

(1)编译格式:

(1)编译格式:

相關文件