- UID
- 1
- 精华
- 积分
- 76361
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
_stdcall关键字主要见于VC、VC艹。被_stdcall修饰过的函数有以下特点:
1、参数从右往左入栈。
2、参数个数固定。
3、被调用者维护栈
4、如果是C语言定义的_stdcall函数,函数在OBJ的导出符号是“_函数名@参数字节个数”;如果是C艹的话就不确定了。
5、返回值我会在后面介绍。6、对于GNU的C、C艹编译器(MinGW),_stdcall函数的修饰应该改成__attribute__((stdcall))
Win32的API基本上都是_stdcall风格的函数。APIENTRY、CALLBACK、WINAPI这些修饰符其实都是_stdcall。
_stdcall的好处是维护栈比较方便。对不同的编程语言之间的兼容性较好。
我这里用NASM汇编演示一下调用_stdcall风格的函数:
%define NULL 0
push 0
push NULL
push NULL
push NULL
call _MessageBoxA@16;显示一个对话框,标题栏和内容都是“错误”
;C语言表示:MessageBoxA(NULL,NULL,NULL,0);
因为是被调用者维护栈,所以我们并不需要把参数弹出。只需要PUSH好参数,然后CALL就行。
相比较而言,_cdecl就必须在调用后“add esp,参数个数”来维护栈。
然后我再用NASM演示一下,_stdcall函数在更底层的方面是个什么样的原型。
global _HelloWorld@8;输出函数:int _stdcall HelloWorld(int,char);
segment .text;代码段
_HelloWorld@8:
mov eax,[esp+4]
add eax,[esp+8]
ret 8
;对应C语言的表达方式:
;int _stdcall HelloWorld(int i,char c)
;{
; return i+c;
;}
好,现在我在这里讲返回值与寄存器的关系。
·如果返回值是整数、指针,那么通常用eax存储返回值。
·如果是浮点数,用st0存储返回值。
·如果返回值是结构体:
··如果结构体大小是8个字节以内的,返回值在eax:edx里面。
··如果结构体大小超过8个字节,那么C语言、C艹编译器会“暗中”把你的函数原型修改成第一个参数是存储返回的结构体的地址的指针,其余参数不变(也就是添加了一个参数),然后返回值是这个结构体的指针,用eax存储这个结构体的指针。
返回值这里我举个例,假设你的结构体是这样的:- #pragma pack(push,1)
- struct 结构体
- {
- char a,b,c,d;
- };
- #pragma pack(pop)
复制代码 当这个结构体作为返回值返回的时候(注意不是作为结构体指针返回的时候),因为结构体的大小只有4个字节,所以返回值是存放在eax里面的,al存放a的值,ah存放b的值,c存放在eax的第16~第23位,d存放在eax的第24~第31位。
假设你的结构体是这样的:- #pragma pack(push,1)
- struct 结构体
- {
- char a,b,c,d;
- char e,f,g,h;
- };
- #pragma pack(pop)
复制代码 因为结构体的大小有8个字节,所以返回值是存放在eax:edx里面的,al存放a的值,ah存放b的值,c存放在eax的第16~第23位,d存放在eax的第24~第31位,dl存放e的值,dh存放f的值,g存放在edx的第16~第23位,h存放在edx的第24~第31位。
假设你的结构体是这样的:- #pragma pack(push,1)
- struct 结构体
- {
- char a,b,c,d;
- char e,f,g,h;
- char i,j,k,l;
- };
- #pragma pack(pop)
复制代码 因为结构体的大小有12个字节,所以返回值要另外看待。
这个时候假设你的程序是这样的:- struct 结构体 _stdcall Func(char val)
- {
- struct 结构体 retStruct;
- retStruct.a=val;
- retStruct.b=val;
- retStruct.c=val;
- retStruct.d=val;
- retStruct.e=val;
- retStruct.f=val;
- retStruct.g=val;
- retStruct.h=val;
- retStruct.i=val;
- retStruct.j=val;
- retStruct.k=val;
- retStruct.l=val;
- return retStruct;
- }
复制代码 那么C、C艹的编译器就会“暗中”把你的程序改成这样:- struct 结构体* _stdcall Func(struct 结构体 *pRetStruct,char val)
- {
- struct 结构体 *retStruct=pRetStruct;
- retStruct->a=val;
- retStruct->b=val;
- retStruct->c=val;
- retStruct->d=val;
- retStruct->e=val;
- retStruct->f=val;
- retStruct->g=val;
- retStruct->h=val;
- retStruct->i=val;
- retStruct->j=val;
- retStruct->k=val;
- retStruct->l=val;
- return retStruct;
- }
复制代码 由此可见,当返回值是结构体的时候,返回的结构体就被放到了第一个参数作为指针指向的位置。然后eax的值是这个指针的地址。 |
|