调用约定和函数导出名
来源:互联网 发布:首都医科大学网络教育 编辑:程序博客网 时间:2024/05/16 14:30
本文主要介绍C/C++代码编译中的“调用约定”选择和不同的编译器产生的不同规则的“函数导出名”的问题。主要涉及:cdecl、stdcall、thiscall、VC++编译器、mingw编译器、gcc编译器、g++编译器。
一、调用约定
调用约定指的是函数在被调用的时候,会按照不同的规则,翻译成不同的汇编代码。为了解释这个概念,首先要了解一下调用堆栈的概念。当一个函数被调用时,首先压入函数的各个参数,然后压入函数的返回地址。当函数退出时会以相反的顺序依次退出堆栈。因此,函数在被调用前和调用后的堆栈保持平衡。
编译中常见的cdecl、stdcall、thiscall等调用约定,主要约定两件事:
1)参数入栈的顺序;
2)函数调用结束后,谁(调用者or被调用者)来恢复栈。
1,C语言调用约定,__cdecl
VS新建win32工程,查看VS工程“属性-> c/c++ -> 高级”,可知VS Win32工程默认为cdecl调用约定,它是C语言的调用约定,它的约定如下:
1)函数调用时,参从右到左入栈。
2)函数调用结束后,调用者负责恢复栈。
示例代码(新建win32控制台程序):
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">int Foo(int a, int b){ return a + b;}int main(){ int x = Foo(2,3); return 0;}</span></span></span>
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">int main(){00A52290 push ebp 00A52291 mov ebp,esp 00A52293 sub esp,0CCh 00A52299 push ebx 00A5229A push esi 00A5229B push edi 00A5229C lea edi,[ebp-0CCh] 00A522A2 mov ecx,33h 00A522A7 mov eax,0CCCCCCCCh 00A522AC rep stos dword ptr es:[edi] <span style="white-space:pre"></span>int x = Foo(2,3);00A522AE push 3 00A522B0 push 2 00A522B2 call Foo (0A51366h) 00A522B7 add esp,8 00A522BA mov dword ptr [x],eax return 0;00A522BD xor eax,eax }00A522BF pop edi 00A522C0 pop esi 00A522C1 pop ebx 00A522C2 add esp,0CCh 00A522C8 cmp ebp,esp 00A522CA call __RTC_CheckEsp (0A51339h) 00A522CF mov esp,ebp 00A522D1 pop ebp 00A522D2 ret</span></span></span>
1)先“push 3";
2)再压入左边的参数”2“;
3)压入”Foo函数的入口地址“,call表示跳转到该地址;
4)”add esp,8“,即将”栈顶指针“加8,表示函数调用完成后,堆栈回退8个字节,刚好是两个参数(2* 4byte)所占的内存大小。
注:
1) 在内存地址空间中,栈是由高地址向低地址增长,即“栈是向下生长的”。故,堆栈回退,栈顶指针进行”+“运算。
2)参数是按4字节对齐,不管是int型还是char型。
注:
1,MinGW编译器默认也是采用的__cdecl调用约定。
2,对于动态链接库,采用动态加载方式(即“resolve a symbol in the library at runtime”),由于不需要将DLL关联的“.h文件”编译进调用者工程,调用者编译器将采用默认的的调用约定,一般为“__cdecl”。
如果DLL采用的是__stdcall,而调用者采用动态加载,这个时候怎么办?
办法是有的。大致原理是:调用者实际上是先“resolve a symbol in the library at runtime”,拿到被调函数的地址,并保存到一个函数指针中,然后通过函数指针来调用该函数。因此,我们可以为该函数指针指定调用约定。形式如下:
<span style="font-size:14px;"><span style="font-size:14px;">long (__stdcall *pFunctionPtr) (void) = (long(__stdcall*)(void)) m_MyLibrary.resolve("Foo");</span></span>
此外,也可通过typedef先行指定函数指针的形式。
此处参考:DLL的调用约定 和 C++成员指针 this指针 调用约定,因为C++成员函数采用的是“__thiscall”,而VS和MinGW等默认为__cdecl,在类外通过类成员函数指针调用时,就需要注意到这些差异引起的问题。
3,Qt - MinGW版 采用QLibrary类来管理动态加载。可以阅读Qt的文档,摘要如下:
The QLibrary class loads shared libraries at runtime.
An instance of a QLibrary object operates on a single shared object file (which we call a "library", but is also known as a "DLL"). A QLibrary provides access to the functionality in the library in a platform independent way. You can either pass a file name in the constructor, or set it explicitly with setFileName(). When loading the library, QLibrary searches in all the system-specific library locations (e.g. LD_LIBRARY_PATH on Unix), unless the file name has an absolute path.
The most important functions are load() to dynamically load the library file, isLoaded() to check whether loading was successful, and resolve() to resolve a symbol in the library. The resolve() function implicitly tries to load the library if it has not been loaded yet. Multiple instances of QLibrary can be used to access the same physical library. Once loaded, libraries remain in memory until the application terminates. You can attempt to unload a library using unload(), but if other instances of QLibrary are using the same library, the call will fail, and unloading will only happen when every instance has called unload().
A typical use of QLibrary is to resolve an exported symbol in a library, and to call the C function that this symbol represents. This is called "explicit linking" in contrast to "implicit linking", which is done by the link step in the build process when linking an executable against a library.
The following code snippet loads a library, resolves the symbol "mysymbol", and calls the function if everything succeeded. If something goes wrong, e.g. the library file does not exist or the symbol is not defined, the function pointer will be 0 and won't be called.
<span style="font-size:14px;"><span style="font-size:14px;">QLibrary myLib("mylib");typedef void (*MyPrototype)();MyPrototype myFunction = (MyPrototype) myLib.resolve("mysymbol");if (myFunction) myFunction();</span></span>
The symbol must be exported as a C function from the library for resolve() to work. This means that the function must be wrapped in an extern "C" block if the library is compiled with a C++ compiler. On Windows, this also requires the use of a dllexport macro; see resolve() for the details of how this is done. For convenience, there is a static resolve() function which you can use if you just want to call a function in a library without explicitly loading the library first:
<span style="font-size:14px;"><span style="font-size:14px;">typedef void (*MyPrototype)();MyPrototype myFunction = (MyPrototype) QLibrary::resolve("mylib", "mysymbol");if (myFunction) myFunction();</span></span>
值得注意的是,它只加载C接口。
三、标准调用约定,__stdcall
VS默认的win32工程采用的是__cdecl。但是,如果用VS2015+WDK10新建KMDF驱动工程,默认的调用约定为”__stdcal“。事实上,Windows API采用的都是stdcall,如关键字WINAPI 、CALLBACK。但__stdcall不能用于可变参数函数调用。
__stdcall调用会在目标文件中产生一个符号来代表这个函数(实际是用符号替换地址),此符号形式为”下划线+函数名+X“。其中,X代表清理堆栈时需要的数字,函数以”ret X“的形式返回。它的约定如下:
1)函数调用时,参从右到左入栈。
2)函数调用结束后,被调用函数返回前,自己负责恢复栈。
将上述示例中的Foo函数改为如下形式:
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">int __stdcall Foo(int a, int b)</span></span></span>
反汇编后,如下:
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">int main(){00D92290 push ebp 00D92291 mov ebp,esp 00D92293 sub esp,0CCh 00D92299 push ebx 00D9229A push esi 00D9229B push edi 00D9229C lea edi,[ebp-0CCh] 00D922A2 mov ecx,33h 00D922A7 mov eax,0CCCCCCCCh 00D922AC rep stos dword ptr es:[edi] <span style="white-space:pre"></span>int x = Foo(2,3);00D922AE push 3 00D922B0 push 2 00D922B2 call Foo (0D9136Bh) 00D922B7 mov dword ptr [x],eax return 0;00D922BA xor eax,eax }00D922BC pop edi 00D922BD pop esi 00D922BE pop ebx 00D922BF add esp,0CCh 00D922C5 cmp ebp,esp 00D922C7 call __RTC_CheckEsp (0D91339h) 00D922CC mov esp,ebp 00D922CE pop ebp 00D922CF ret</span></span></span>
注:
1,调用者调用完成后,未移动栈顶指针。
2,被调函数,最后"ret 8",自行清理堆栈。
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">int __stdcall Foo(int a, int b){00D91A70 push ebp 00D91A71 mov ebp,esp 00D91A73 sub esp,0C0h 00D91A79 push ebx 00D91A7A push esi 00D91A7B push edi 00D91A7C lea edi,[ebp-0C0h] 00D91A82 mov ecx,30h 00D91A87 mov eax,0CCCCCCCCh 00D91A8C rep stos dword ptr es:[edi] <span style="white-space:pre"></span>return a + b;00D91A8E mov eax,dword ptr [a] 00D91A91 add eax,dword ptr [b] }00D91A94 pop edi 00D91A95 pop esi 00D91A96 pop ebx 00D91A97 mov esp,ebp 00D91A99 pop ebp 00D91A9A ret 8</span></span></span>
二、函数符号导出名
同样一个函数,用在C语言编译器和C++编译器编译出的符号导出名是不同的。而在链接的时候,链接器不知道源程序的函数名,它只会去目标(Object)文件中寻找相应的函数符号表。VC或DDK提供的编译器cl.exe,既可以编译C语言,又可以编译C++语言。默认情况下,编译器会根据源文件的扩展名,来判断使用哪种方式编译。当文件扩展名是.cpp时,编译器会使用C++方式编译,当文件扩展名是.c的时候,则使用C语言方式编译。例如,同样使用stdcall调用约定编译的函数:
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">int _declspec(dllexport) __stdcall Foo(int a, int b){return a + b;}</span></span></span>在C++编译方式下,导出符号如下:
而在C编译方式中,导出符号为:“_Foo@8”。C++复杂的函数导出符号是为了支持函数重载而设计的,不同的C++编译器的函数导出符号各不相同。故,在跨编译器调用动态链接库的函数时,最好将导出函数用“extern C”修饰,变为C语言方式导出函数符号。如下:
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">extern "C" int _declspec(dllexport) __stdcall Foo(int a, int b){return a + b;}</span></span></span>
注:
Windows驱动程序的入口函数规定为“_DriverEntry@8”,因此,用C++编译器编译时,需要添加“extern c”修饰,否则会导致链接错误。
具体参考博客:调用约定、mingw和mvc转换、msvc mingw dll
- 调用约定和函数导出名
- 函数调用约定和函数名修饰规则
- (转)函数调用约定和函数名修饰规则
- (转)函数调用约定和函数名修饰规则
- DLL 导出函数 _stdcall 和 _cdecl调用约定
- 函数调用约定及函数名修饰
- 名字修饰约定和函数调用约定
- C++调用约定及函数名修饰约定规则
- C++编译时函数名修饰约定规则和DLL中导出函数的方法
- C++编译时函数名修饰约定规则和DLL中导出函数的方法
- C++编译时函数名修饰约定规则和DLL中导出函数的方法
- 函数调用约定和堆栈
- 函数调用约定和堆栈
- 函数调用约定和堆栈
- 函数调用约定和堆栈
- 函数调用约定和堆栈
- 函数调用约定和堆栈
- 函数调用约定和堆栈
- Timer
- 求职在长沙
- 读《程序员的数学》有感
- 整理了下viewpagerTransformers框架的效果图
- IOS学习笔记——object-C基础 (一)
- 调用约定和函数导出名
- 在Java中使用WebSocket实现网页聊天室
- 凸包问题的五种解法
- 深扒“微信乞丐” 教你如何不乞讨就赚大钱!
- 报数三退出 c语言(指针+链表)
- threadlocal原理及常用应用场景
- Python实现Mysql数据库连接池
- nyoj44 子串和
- C++ Primer 学习笔记_87_用于大型程序的工具 -错误处理