_cdecl 与 _stdcal 区别 以及 extern “c‘ 作用

2023-11-12 01:36

本文主要是介绍_cdecl 与 _stdcal 区别 以及 extern “c‘ 作用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 目的
    • _stdcal
    • _cdecl
    • 结果
    • dll extern "c"
    • 补充

目的

1) 了解windows 下 _cdecl 与 _stdcal区别, 方便更好运用修饰函数进行函数调用时,有几种调用方法,分为C式,Pascal式。总的区别来说。

  • _cdecl(c/c++缺省的,支持变参函数,但其他非c/c++语言不支持)
  • _stdcall(依词义,标准调用,不支持变参)

2) dll extern "c‘ 作用

_stdcal

WINAPI (实际上就是PASCAL,CALLBACK,_stdcall)

  void   WINAPI   Input(   int   &m,int   &n);  

**特点:**在主调用函数中负责压栈,在被调用函数中负责弹出堆栈中的参数,并且负责恢复堆栈。因此不能实现变参函数,参数传递是从右到左。另外,命名修饰方法是在函数前加一个下划线(_),在函数名后有符号(@),在@后面紧跟参数列表中的参数所占字节数(10进制),如:void Input(int &m,int &n),被修饰成:_Input@8
对于大多数api函数以及窗口消息处理函数皆用 CALLBACK ,所以调用前,主调函数会先压栈,然后api函数自己恢复堆栈。

_cdecl

在C和C++中C式调用是缺省的,除非特殊声明。

  void   Input(   int   &m,int   &n);  

特点: 在C或C++语言调用中默认的函数修饰_cdecl,由主调用函数进行参数压栈并且恢复堆栈,实参的压栈顺序是从右到左,最后由主调函数进行堆栈恢复。由于主调用函数管理堆栈,所以可以实现变参函数。另外,命名修饰方法是在函数前加一个下划线(_).

证明实际上引用传递的是变量的地址(类似指针)

结果

__stdcall 与 _cdecl

The __stdcall calling convention is used to call Win32 API functions.The callee cleans the stack, so the compiler makes vararg functions __cdecl. Functions that use this calling convention require a function prototype. The __stdcall modifier is Microsoft-specific.

__stdcall 调用约定用于调用 Win32 API 函数。被调用者清理堆栈,因此编译器将可变参数函数设为 __cdecl。使用此调用约定的函数需要函数原型。 __stdcall 修饰符是 Microsoft 特定的。

_cdecl 与 _stdcall 详解

__stdcall

dll extern “c”

动态链接库的使用有两种方式,一种是显式调用。一种是隐式调用。

  • 显式调用:使用LoadLibrary载入动态链接库、使用GetProcAddress获取某函数地址。
  • 隐式调用:可以使用#pragma comment(lib, “XX.lib”)的方式,也可以直接将XX.lib加入到工程中。

dll编写需要解决函数重命名——Name-Mangling。解决方式有两种,一种是直接在代码里解决采用extent”c”、_declspec(dllexport)、#pragma comment(linker, “/export:[Exports Name]=[Mangling Name]”),另一种是采用def文件。

因为C和C++的重命名规则是不一样的。这种重命名称为“Name-Mangling”(名字修饰或名字改编、标识符重命名)。

据说,C++标准并没有规定Name-Mangling的方案,所以不同编译器使用的是不同的,例如:Borland C++跟Mircrosoft C++就不同,而且可能不同版本的编译器他们的Name-Mangling规则也是不同的。这样的话,不同编译器编译出来的目标文件.obj 是不通用的,因为同一个函数,使用不同的Name-Mangling在obj文件中就会有不同的名字。如果DLL里的函数重命名规则跟DLL的使用者采用的重命名规则不一致,那就会找不到这个函数。

C标准规定了C语言Name-Mangling的规范(林锐的书有这样说过)。这样就使得,任何一个支持C语言的编译器,它编译出来的obj文件可以共享,链接成可执行文件。这是一种标准,如果DLL跟其使用者都采用这种约定,那么就可以解决函数重命名规则不一致导致的错误。

影响符号名的除了C++和C的区别、编译器的区别之外,还要考虑调用约定导致的Name Mangling。如extern “c” __stdcall的调用方式就会在原来函数名上加上写表示参数的符号,而extern “c” __cdecl则不会附加额外的符号。

dll中的函数在被调用时是以函数名或函数编号的方式被索引的。这就意味着采用某编译器的C++的Name-Mangling方式产生的dll文件可能不通用。因为它们的函数名重命名方式不同。为了使得dll可以通用些,很多时候都要使用C的Name-Mangling方式,即是对每一个导出函数声明为extern “C”,而且采用_stdcall调用约定,接着还需要对导出函数进行重命名,以便导出不加修饰的函数名。

注意到extern “C”的作用是为了解决函数符号名的问题,这对于动态链接库的制造者和动态链接库的使用者都需要遵守的规则。

动态链接库的显式装入就是通过GetProcAddress函数,依据动态链接库句柄和函数名,获取函数地址。因为GetProcAddress仅是操作系统相关,可能会操作各种各样的编译器产生的dll,它的参数里的函数名是原原本本的函数名,没有任何修饰,所以一般情况下需要确保dll’里的函数名是原始的函数名。分两步:一,如果导出函数使用了extern”C” _cdecl,那么就不需要再重命名了,这个时候dll里的名字就是原始名字;如果使用了extern”C” _stdcall,这时候dll中的函数名被修饰了,就需要重命名。二、重命名的方式有两种,要么使用*.def文件,在文件外修正,要么使用#pragma,在代码里给函数别名。

  • _declspec(dllexport)和_declspec(dllimport)的作用

    _declspec还有另外的用途,这里只讨论跟dll相关的使用。正如括号里的关键字一样,导出和导入。_declspec(dllexport)用在dll上,用于说明这是导出的函数。而_declspec(dllimport)用在调用dll的程序中,用于说明这是从dll中导入的函数。

     因为dll中必须说明函数要用于导出,所以_declspec(dllexport)很有必要。但是可以换一种方式,可以使用def文件来说明哪些函数用于导出,同时def文件里边还有函数的编号。
    

而使用_declspec(dllimport)却不是必须的,但是建议这么做。因为如果不用_declspec(dllimport)来说明该函数是从dll导入的,那么编译器就不知道这个函数到底在哪里,生成的exe里会有一个call XX的指令,这个XX是一个常数地址,XX地址处是一个jmp dword ptr[XXXX]的指令,跳转到该函数的函数体处,显然这样就无缘无故多了一次中间的跳转。如果使用了_declspec(dllimport)来说明,那么就直接产生call dword ptr[XXX],这样就不会有多余的跳转了

  • __stdcall影响

这是一种函数的调用方式。默认情况下VC使用的是__cdecl的函数调用方式,如果产生的dll只会给C/C++程序使用,那么就没必要定义为__stdcall调用方式,如果要给Win32汇编使用(或者其他的__stdcall调用方式的程序),那么就可以使用__stdcall。这个可能不是很重要,因为可以自己在调用函数的时候设置函数调用的规则。像VC就可以设置函数的调用方式,所以可以方便的使用win32汇编产生的dll。不过__stdcall这调用约定会Name-Mangling,所以我觉得用VC默认的调用约定简便些。但是,如果既要__stdcall调用约定,又要函数名不给修饰,那可以使用*.def文件,或者在代码里#pragma的方式给函数提供别名(这种方式需要知道修饰后的函数名是什么)。

·extern “C” __declspec(dllexport) bool  __stdcall cswuyg();
·extern “C”__declspec(dllimport) bool __stdcall cswuyg();
·#pragma comment(linker, "/export:cswuyg=_cswuyg@0")

结论:
总的来说,在编写DLL的时候,写个头文件,头文件里声明函数的NameMingling方式、调用约定(主要是为了隐式调用)。再写个
.def文件把函数重命名了(主要是为了显式调用)。提供
.DLL*.lib*.h给dll的使用者,这样无论是隐式的调用,还是显式的调用,都可以方便的进行。**

很感谢这位博客主写的DLL编写中extern “C”和__stdcall的作用 他写的详细和很清楚了。

补充

1)调用协议常用场合

  • __stdcall:Windows API默认的函数调用协议。
  • __cdecl:C/C++默认的函数调用协议。
  • __fastcall:适用于对性能要求较高的场合。

2)函数参数入栈方式

  • __stdcall:函数参数由右向左入栈。
  • __cdecl:函数参数由右向左入栈。
  • __fastcall:从左开始不大于4字节的参数放入CPU的ECX和EDX寄存器,其余参数从右向左入栈。
    问题一:__fastcall在寄存器中放入不大于4字节的参数,故性能较高,适用于需要高性能的场合。

3)栈内数据清除方式

  • __stdcall:函数调用结束后由被调用函数清除栈内数据。
  • __cdecl:函数调用结束后由函数调用者清除栈内数据。
  • __fastcall:函数调用结束后由被调用函数清除栈内数据。
    问题一:不同编译器设定的栈结构不尽相同,跨开发平台时由函数调用者清除栈内数据不可行。
    问题二:某些函数的参数是可变的,如printf函数,这样的函数只能由函数调用者清除栈内数据。
    问题三:由调用者清除栈内数据时,每次调用都包含清除栈内数据的代码,故可执行文件较大。

4)C语言编译器函数名称修饰规则

  • __stdcall:编译后,函数名被修饰为“_functionname@number”。
  • __cdecl:编译后,函数名被修饰为“_functionname”。
  • __fastcall:编译后,函数名给修饰为“@functionname@nmuber”。
    注:“functionname”为函数名,“number”为参数字节数。
    注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。

5)C++语言编译器函数名称修饰规则

  • __stdcall:编译后,函数名被修饰为“?functionname@@YG******@Z”。
  • __cdecl:编译后,函数名被修饰为“?functionname@@YA******@Z”。
  • __fastcall:编译后,函数名被修饰为“?functionname@@YI******@Z”。
    注:“******”为函数返回值类型和参数类型表。
    注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。

6)C语言和C++语言间如果不进行特殊处理,也无法实现函数的互相调用。

这篇关于_cdecl 与 _stdcal 区别 以及 extern “c‘ 作用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/394198

相关文章

Before和BeforeClass的区别及说明

《Before和BeforeClass的区别及说明》:本文主要介绍Before和BeforeClass的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Before和BeforeClass的区别一个简单的例子当运行这个测试类时总结Before和Befor

SpringBoot 中 CommandLineRunner的作用示例详解

《SpringBoot中CommandLineRunner的作用示例详解》SpringBoot提供的一种简单的实现方案就是添加一个model并实现CommandLineRunner接口,实现功能的... 目录1、CommandLineRunnerSpringBoot中CommandLineRunner的作用

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

Linux中的more 和 less区别对比分析

《Linux中的more和less区别对比分析》在Linux/Unix系统中,more和less都是用于分页查看文本文件的命令,但less是more的增强版,功能更强大,:本文主要介绍Linu... 目录1. 基础功能对比2. 常用操作对比less 的操作3. 实际使用示例4. 为什么推荐 less?5.

Java 关键字transient与注解@Transient的区别用途解析

《Java关键字transient与注解@Transient的区别用途解析》在Java中,transient是一个关键字,用于声明一个字段不会被序列化,这篇文章给大家介绍了Java关键字transi... 在Java中,transient 是一个关键字,用于声明一个字段不会被序列化。当一个对象被序列化时,被

解读@ConfigurationProperties和@value的区别

《解读@ConfigurationProperties和@value的区别》:本文主要介绍@ConfigurationProperties和@value的区别及说明,具有很好的参考价值,希望对大家... 目录1. 功能对比2. 使用场景对比@ConfigurationProperties@Value3. 核

Spring Boot拦截器Interceptor与过滤器Filter深度解析(区别、实现与实战指南)

《SpringBoot拦截器Interceptor与过滤器Filter深度解析(区别、实现与实战指南)》:本文主要介绍SpringBoot拦截器Interceptor与过滤器Filter深度解析... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实

关于Mybatis和JDBC的使用及区别

《关于Mybatis和JDBC的使用及区别》:本文主要介绍关于Mybatis和JDBC的使用及区别,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、JDBC1.1、流程1.2、优缺点2、MyBATis2.1、执行流程2.2、使用2.3、实现方式1、XML配置文件

$在R语言中的作用示例小结

《$在R语言中的作用示例小结》在R语言中,$是一个非常重要的操作符,主要用于访问对象的成员或组件,它的用途非常广泛,不仅限于数据框(dataframe),还可以用于列表(list)、环境(enviro... 目录1. 访问数据框(data frame)中的列2. 访问列表(list)中的元素3. 访问jav

exfat和ntfs哪个好? U盘格式化选择NTFS与exFAT的详细区别对比

《exfat和ntfs哪个好?U盘格式化选择NTFS与exFAT的详细区别对比》exFAT和NTFS是两种常见的文件系统,它们各自具有独特的优势和适用场景,以下是关于exFAT和NTFS的详细对比... 无论你是刚入手了内置 SSD 还是便携式移动硬盘或 U 盘,都需要先将它格式化成电脑或设备能够识别的「文