您的位置:首页 > 其它

调用约定设置不当会造成运行时错误

2008-01-12 11:57 218 查看
/Gd /Gr /Gz 调用方式如果不匹配会造成一些列的运行时错误。

调用声明调用方式为__stdcall的dll函数接口时如果使用__cdecl 调用方式则会产生Run-Time Check Failure #0运行时错误。

下面转一些关于调用方式的资料:

————————————————————————————————————————————————

(1) _stdcall调用
  _stdcall是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,被调函数自身在返回前清空堆栈。
  WIN32 Api都采用_stdcall调用方式,这样的宏定义说明了问题:
  #define WINAPI _stdcall
 按C编译方式,_stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionname@number

(2) _cdecl调用
  _cdecl是C/C++的缺省调用方式,参数采用从右到左的压栈方式,传送参数的内存栈由调用者维护。_cedcl约定的函数只能被C/C++调用,每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。
  由于_cdecl调用方式的参数内存栈由调用者维护,所以变长参数的函数能(也只能)使用这种调用约定。关于C/C++中变长参数(…)的问题,笔者将另文详述。
  由于Visual C++默认采用_cdecl 调用方式,所以VC中中调用DLL时,用户应使用_stdcall调用约定。
  按C编译方式,_cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。

(3) _fastcall调用
  _fastcall调用较快,它通过CPU内部寄存器传递参数。
  按C编译方式,_fastcall调用约定在输出函数名前面加“@”符号,后面加“@”符号和参数的字节数,形如@functionname@number。

——————————————————————————————————————————————

_cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,
所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式
。VC将函数编译后会在函数名前面加上下划线前缀。
_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压
栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在
函数名后加上"@"和参数的字节数。
_fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上"@"前
缀,在函数名后加上"@"和参数的字节数。

一篇文章from vckbase
__stdcall和_cdecl
(xulion发表于2001-8-21 10:28:16)
[精彩文章]
这两个关键字看起来似乎很少和我们打交道,但是看了下面的定义(来自windef.h
),你一定会觉得惊讶:
#define CALLBACK     __stdcall
#define WINAPI       __stdcall
#define WINAPIV      __cdecl
#define APIENTRY     WINAPI
#define APIPRIVATE   __stdcall
#define PASCAL       __stdcall
#define cdecl _cdecl
#ifndef CDECL
#define CDECL _cdecl
#endif
几乎我们写的每一个WINDOWS API函数都是__stdcall类型的,为什么??
首先,我们谈一下两者之间的区别:
WINDOWS的函数调用时需要用到栈(STACK,一种先入后出的存储结构)。当函数
调用完成后,栈需要清除,这里就是问题的关键,如何清除??
如果我们的函数使用了_cdecl,那么栈的清除工作是由调用者,用COM的术语来讲
就是客户来完成的。这样带来了一个棘手的问题,不同的编译器产生栈的方式不尽相同
,那么调用者能否正常的完成清除工作呢?答案是不能。
如果使用__stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨
(开发)平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现)。
那么为什么还需要_cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变
的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行
,因此,这种情况我们只能使用_cdecl。
到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用__stdcal
l关键字


函数参数入栈的方式
---------------------------------------------------------------

在函数调用过程中,会使用堆栈,这三个表示不同的堆栈调用方式和释放方式。
比如说__cdecl,它是标准的c方法的堆栈调用方式,就是在函数调用时的参数压入堆栈是与函数的声明顺序相反的,其它两个可以看MSDN,不过这个对我们编程没有太大的作用
---------------------------------------------------------------

调用约定

调用约定(Calling convention)决定以下内容:函数参数的压栈顺序,由调用者还是被调用者把参数弹出栈,以及产生函数修饰名的方法。MFC支持以下调用约定:

_cdecl

按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于"C"函数或者变量,修饰名是在函数名前加下划线。对于"C++"函数,有所不同。

如函数void test(void)的修饰名是_test;对于不属于一个类的"C++"全局函数,修饰名是?test@@ZAXXZ

这是MFC缺省调用约定。由于是调用者负责把参数弹出栈,所以可以给函数定义个数不定的参数,如printf函数。

_stdcall

按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。对于"C"函数或者变量,修饰名以下划线为前缀,然后是函数名,然后是符号"@"及参数的字节数,如函数int func(int a, double b)的修饰名是_func@12。对于"C++"函数,则有所不同。

所有的Win32 API函数都遵循该约定。

_fastcall

头两个DWORD类型或者占更少字节的参数被放入ECX和EDX寄存器,其他剩下的参数按从右到左的顺序压入栈。由被调用者把参数弹出栈,对于"C"函数或者变量,修饰名以"@"为前缀,然后是函数名,接着是符号"@"及参数的字节数,如函数int func(int a, double b)的修饰名是@func@12。对于"C++"函数,有所不同。

未来的编译器可能使用不同的寄存器来存放参数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐