From:
http://jpkc.zzu.edu.cn/hbyycai/courses/list.asp?id=310
上面,我们主要以C程序调用汇编子程序的情况介绍了两者进行混合编程的问题。虽然,汇编语言程序调用C语言函数的情况不经常使用,但这是可以实现的。本节简单说明这种混合编程的方法。
这种情况下,前面介绍的混合编程的各种约定仍然适用,必须遵循。另外,还要注意以下几点。
为了使C函数对汇编语言程序可见,汇编语言程序需要对所调用的C语言函数、变量用关键字EXTERN进行说明,形式如下:
EXTERN 被调用函数名∶函数属性 EXTERN 变量名∶变量属性 |
其中函数属性可以是near和far。如果C的存储模式为微型、小型和紧凑模式,则汇编语言程序需要将C函数说明为near属性;如果C采用中型、大型、巨型存储模式,则汇编语言程序需说明为far属性。
如在C语言程序中有如下说明:
int i,array[10];
char ch;
long result;
char ch;
long result;
汇编语言程序中,应说明为:
EXTERN i:word,arrray:word,ch:byte,result:dword
参数的正确传递是混合编程的关键。利用外部变量传递参数的方法在例题7.7和7.8已经用到。汇编语言程序向C语言函数传递参数的另一种方法是通过堆栈。由于C程序是按参数顺序的相反顺序压栈,所以在汇编程序中参数的压栈顺序要与C程序接收参数的顺序相反。若参数传递采用传值方式,汇编语言程序就把参数值或已赋值的变量压入堆栈;若汇编语言程序向C函数以传址的方式传递数据,则应该把参数的地址压入堆栈。
不过注意啦,在汇编语言程序调用C函数完成后,应该立即平衡堆栈。即清除堆栈里的参数,恢复堆栈到调用前的情形。这里可以利用“
add sp,imm”指令来完成,使SP的值增加一个指定的值。imm这个值应该是堆栈中返回地址所占字节数与传送参数所占用字节数之和。
若汇编语言程序以无参数的形式调用一个C语言函数,那么,在C语言程序中对此函数进行定义时,函数后面应该跟一个空括号。
如果把参数传递给C语言函数,一般利用堆栈进行传送。例如汇编语言程序把参数a、b、c依次传送给C函数的三个形式参数x、y、z。若压栈顺序为c、b、a,则C语言函数的参数表的顺序必须为x、y、z。
若汇编语言子程序以传值方式向C语言函数传送数据,则C语言函数可用基本类型对参数进行说明。若汇编语言子程序以传地址方式向C语言函数传送数据,则C语言函数应该使用指针类型对参数进行说明。
若C函数向汇编语言程序送返回值,则C语言函数体必须用RETURN返回。返回值的传递约定同前所述,即:如果返回值是一个字,则送给AX;如果为32位,则低16位在AX中,高16位在DX中;更多的数据则利用DX∶AX返回指针。若没有返回值,可以不使用RETURN。
; C程序说明:x是传数值参数;y和z是传地址参数
; long examle(int x,int *y,int *z);
; 对应汇编语言程序的说明(小型模式):
extren examle :near
; C程序调用:xyz=examle(100,&vary,&varz);
; 对应的汇编语言程序段(近指针)
mov ax,offset varz
push ax ;压入varz的地址
mov ax,offset vary
push ax ;压入vary的地址
mov ax,100
push ax ;压入常数100
call example ;调用C函数
add sp,6 ;平衡堆栈
mov xyz,ax ;取得返回值低16位
mov xyz+2,dx ;取得返回值高16位
push ax ;压入varz的地址
mov ax,offset vary
push ax ;压入vary的地址
mov ax,100
push ax ;压入常数100
call example ;调用C函数
add sp,6 ;平衡堆栈
mov xyz,ax ;取得返回值低16位
mov xyz+2,dx ;取得返回值高16位
一般C语言程序具有起始模块,即具有主函数。这是因为高级语言程序假定某些初始化代码已经预先执行过了,故能确信在高级语言模块启动时,完成了适当的初始化操作。这样,程序从C语言程序开始执行;然后调用汇编语言子程序,进而又调用C语言函数。
结合下面的例题对于汇编语言程序对C语言程序的调用可以有个更好的理解。其中
例7.10:汇编语言程序以传址方式传递参数调用C函数
/* C语言程序:lt610.c */
extern asub();
main()
{ asub(); /* 调用汇编语言子程序 */
}
csub(char * str) /* C语言函数,str是地址参数 */
{ printf("%s\n",str);
}
; 汇编语言程序:lt710s.asm
.model small,c
extern csub:near
.data
astring db ’OK, Assembly !’,0dh,0ah,’$’
cstring db ’Good, Turbo C 2.0 !’,0
.code
PUBLIC asub
asub proc
mov dx,offset astring ;汇编语言子程序显示信息
mov ah,09h
int 21h
mov ax,offset cstring ;得到字符串的偏移地址
push ax ;压入调用参数
call csub ;调用C函数
add sp,2 ;平衡堆栈
ret
asub endp
end
main()
{ asub(); /* 调用汇编语言子程序 */
}
csub(char * str) /* C语言函数,str是地址参数 */
{ printf("%s\n",str);
}
; 汇编语言程序:lt710s.asm
.model small,c
extern csub:near
.data
astring db ’OK, Assembly !’,0dh,0ah,’$’
cstring db ’Good, Turbo C 2.0 !’,0
.code
PUBLIC asub
asub proc
mov dx,offset astring ;汇编语言子程序显示信息
mov ah,09h
int 21h
mov ax,offset cstring ;得到字符串的偏移地址
push ax ;压入调用参数
call csub ;调用C函数
add sp,2 ;平衡堆栈
ret
asub endp
end
例7.11:汇编语言程序调用C函数计算正弦值,并显示
/* C语言程序:lt711.c */
#include #define PI 3.1415926 /* 定义常量 */ extern void asub(void); main() { asub(); } void sincall(int angle) /* 显示给定度数的正弦值的函数 */ { double radian; radian=PI*angle/180.0; /* 度转换成弧度 */ printf("%f\n",sin(radian)); /* 计算,并显示结果 */ } ; 汇编语言程序:lt711L.asm .model large,c extern sincall:far ;外部函数sincall public asub .data sinangle dw 35 .code asub proc mov ax,@data mov es,ax push es:sinangle call sincall ;调用显示正弦值的C函数 add sp,2 ret asub endp end
例7.12:80x86微处理器识别程序
/* C语言程序:lt712.c */
#include
extern void get_cpu_type(void); extern char cpu_type; main() { get_cpu_type(); printf(“Your Personel Computer Has a“); switch(cpu_type){
case 0: printf(“n 8086/8088 Processor !\n”);break;
case 2: printf(“n 80286 Processor !\n”);break; case 3: printf(“n 80386 Processor !\n”);break; case 4: printf(“n 80486 Processor !\n”);break; case 5: printf(“ Pentium Processor !\n”);break; case 6: printf(“ Pentium Pro Processor !\n”);break; default: printf(“n unknown Processor !\n”); } } ; 汇编语言程序:lt610.asm .model small,c .386P public cpu_type, get_cpu_type CPU_ID MACRO ;处理器识别指令 DB 0FH,0A2H ;CPUID指令的机器代码:0F A2 ENDM .data cpu_type db 0 intel_id db "GenuineIntel" .code get_cpu_type proc ;8086判定:8086标志寄存器的最高4位总是1,根据此特性判断是否为8086 get8086:pushf pop ax and ax,0fffh push ax ;替换当前标志寄存器(欲使D 15~D 12复位) popf pushf pop ax ;得到新标志寄存器内容 and ax,0f000h cmp ax,0f000h ;如果D 15~D 12仍置位,则为8086/8088 mov cpu_type,0 ;设置CPU类型变量为0 jnz get286 ;不是8086,则转向判定80286 ret ;80286判定:实方式下,80286标志寄存器的最高4位总是0,根据此特性判断是否为80286 get286:pushf pop ax or ax,0f000h push ax popf pushf pop ax and ax,0f000h mov cpu_type,2 ;设置CPU类型变量为2 jnz get386 ret
;80386判定:EFLAFS的AC标志(D
18)是在80486才引入的,在80386中该位不能改变
;现在可以使用80386的指令 get386: pushfd pop eax mov ecx,eax ;保存原来EFLAGS xor eax,40000h ;求反D 18 push eax popfd pushfd pop eax xor eax,ecx ;不能改变AC标志,为80386 mov cpu_type,3 jz end_type push ecx popfd ;恢复EFLAGS ;CPUID指令判定:能否改变EFLAGS中的ID标志(D 21),说明是否可以使用CPUID指令 get486: mov cpu_type,4 ;现在至少是80486 mov eax,ecx ;取得原来EFLAGS xor eax,200000h ;求反D 21 push eax popfd pushfd pop eax xor eax,ecx jz end_type ;不能改变ID标志,是80486 ;下面用CPUID指令判定处理器类型 get586: mov eax,0 ;设置EAX=0 CPU_ID ;执行CPUID指令,得到厂商标识串 cmp dword ptr intel_id,ebx jne end_type cmp dword ptr intel_id[+4],edx jne end_type cmp dword ptr intel_id[+8],ecx jne end_type ;判断是否为Intel的产品 cmp eax,1 ;判断EAX是否可以取值为1时执行CPUID指令 jl end_type mov eax,1 ;设置EAX=1 CPU_ID ;执行CPUID指令,得到CPU说明信息 and eax,0f00h ;取得处理器类型 mov cpu_type,ah ;设置CPU类型 end_type: ret get_cpu_type endp end |