一文了解【Windows编程】常见关键字和概念
工作了三年,都是在Linux化境下做开发,最近开始接触Windows环境开发,常常被一些Windows编程的关键字搞得很懵,于是决定,好好的总结一下平时Windows环境编程遇到的一些概念。本文会持续更新,争取将最常用的Windows编程的概念整理清楚。
1. 调用约定
含义
函数的调用约定,顾名思义就是对函数调用的一个约束和规定(规范),描述了函数参数是怎么传递和由谁清除堆栈的。它决定以下内容:
- 函数参数的压栈顺序
- 由调用者还是被调用者把参数弹出栈
- 以及产生函数修饰名的方法
常见的函数定义
int function(int a, int b);
这种书写方式,没有显式的说明调用约定,编译器会默人我们使用__cdecl
约定。
另一种函数定义方式
int __stdecall function(int a, int b);
这种定义方式显式的指出了函数调用约定的规则是__stdcall
接下来,具体分析一下这几种调用规则的含义:
类型
常见的调用约定有__cdecl、__stdcall、fastcall
cdecl
__cdecl 是 C Declaration 的缩写,表示 C 和 C++ 默认的函数调用约定。是C/C++和MFCX的默认调用约定。
- 按从右至左的顺序压参数入栈。
- 由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的,返回值在EAX中。因此对于像printf这样可变参数的函数必须用这种约定。
- 编译器在编译的时候对这种调用规则的函数生成修饰名的时候,在输出函数名前加上一个下划线前缀,格式为_function。如函数 int add(int a, int b)的修饰名是_add。
stdcall
__stdcall是Standard Call的缩写,是C++的标准调用方式,当然这是微软定义的标准,__stdcall通常用于Win32 API中(可查看WINAPI的定义)。
- 按从右至左的顺序压参数入栈。
- 由被调用者把参数弹出栈。函数自己在退出时清空堆栈,返回值在EAX中。
- __stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_function@number。如函数int sub(int a, int b)的修饰名是_sub@8。
fastcall
__fastcall调用的主要特点就是快,因为它是通过寄存器来传送参数的。
- 实际上__fastcall用ECX和EDX传送前两个DWORD或更小的参数,剩下的参数仍自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈。
- __fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式为@function@number,如double multi(double a, double b)的修饰名是@multi@16。
- __fastcall和__stdcall很象,**唯一差别就是头两个参数通过寄存器传送。**注意通过寄存器传送的两个参数是从左向右的,即第1个参数进ECX,第2个进EDX,其他参数是从右向左的入栈,返回仍然通过EAX。
thiscall
__thiscall是C++类成员函数缺省的调用约定,但它没有显示的声明形式。因为在C++类中,成员函数调用还有一个this指针参数,因此必须特殊处理,thiscall调用约定的特点:
- 参数入栈:参数从右向左入栈
- this指针入栈:如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压栈后被压入栈。
- 栈恢复:对参数个数不定的,调用者清理栈,否则函数自己清理栈。
总结
几种调用约定的比较
项目 | __cdecl | __stdcall | __fastcall |
---|---|---|---|
参数传递方式 | 右->左 | 右->左 | 左边开始的两个不大于4字节(DWORD)的参数分别放在ECX和EDX寄存器,其余的参数自右向左压栈传送 |
清理栈方 | 调用者清理 | 被调用函数清理 | 被调用函数清理 |
适用场合 | C/C++、MFC的默认方式; 可变参数的时候使用; | Win API | 要求速度快,适用于对性能要求较高的场合。 |
C编译修饰约定 | _functionname | _functionname@number | @functionname@number |
参考资料
https://blog.csdn.net/luoweifu/article/details/52425733
2. dllimport 和 dllexport
__declspec是Microsoft VC中专用的关键字,它配合着一些属性可以对标准C/C++进行扩充。
dllexport
__declspec(dllexport)用于Windows中的动态库中,声明导出函数、类、对象等供外面调用,省略给出.def文件。即将函数、类等声明为导出函数,供其它程序调用,作为动态库的对外接口函数、类等。
.def文件(模块定义文件)是包含一个或多个描述各种DLL属性的Module语句的文本文件。.def文件或__declspec(dllexport)都是将公共符号导入到应用程序或从DLL导出函数。如果不提供__declspec(dllexport)导出DLL函数,则DLL需要提供.def文件。
声明一个导出函数,是说这个函数要从本DLL导出。我要给别人用。一般用于dll中
省掉在DEF文件中手工定义导出哪些函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出类
dllimport
__declspec(dllimport)用于Windows中,从别的动态库中声明导入函数、类、对象等供本动态库或exe文件使用。
声明一个导入函数,是说这个函数是从别的DLL导入。我要用。一般用于使用某个dll的exe中
不使用 __declspec(dllimport) 也能正确编译代码,但使用 __declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于 DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨 DLL 边界的函数调用中。但是,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量。
比较
关键字 | 含义 | |
---|---|---|
dllexport | 将当前动态库的接口导出,供别的程序使用 | |
dllimport | 将别的动态库的接口导入到当前程序,供当前使用 |
用法
#define LIBSCRENDER_API __declspec (dllexport)
LIBSCRENDER_API int __stdcall sum(int a, int b)
{
return a + b;
}
参考资料
https://docs.microsoft.com/zh-cn/cpp/cpp/dllexport-dllimport?view=vs-2019
持续更新……