__declspec(dllimport) 对【函数调用】编译结果的影响
来源:互联网 发布:果核源码 编辑:程序博客网 时间:2024/05/21 19:34
环境:vs2005 + xpsp3
作者:magictong
时间:2010-09-08
注:例子演示里面都是以debug模式下的汇编来讲,在release下因为经过一些优化,过程会有一些区别,但是最终的结论是一样的。
__declspec本身就是microsoft对c++的扩展,因此后面的讨论都是指在VS2005下编译的结果,与__declspec(dllimport)相对的一个组合用法是__declspec(dllexport),__declspec(dllexport)是作用于PE文件导出函数、类、变量等等(如果你不用def文件导出函数,就必须使用__declspec(dllexport)来进行导出)。__declspec(dllimport)的具体作用在msdn上面讲得比较模糊,如下Importing into an Application Using __declspec(dllimport):
英文:
Using __declspec(dllimport) is optional on function declarations, but the compiler produces more efficient code if you use this keyword. However, you must use __declspec(dllimport) for the importing executable to access the DLL's public data symbols and objects. Note that the users of your DLL still need to link with an import library.
中文:
在函数声明上使用 __declspec(dllimport) 是可选操作,但如果使用此关键字,编译器将生成更有效的代码。但是,为使导入的可执行文件能够访问 DLL 的公共数据符号和对象,必须使用 __declspec(dllimport)。请注意,DLL 的用户仍然需要与导入库链接。
所谓可选操作,就是可要可不要,但是如果使用了,编译器会生成更高效的代码。嗯,编译器就是这么一个意思。后来我测试了四种情况,来看看到底是什么情况。
情况一:EXE内部调用自己的swap函数,swap函数没有使用__declspec(dllimport)修饰
对swap的调用生成的汇编代码为:
swap(m, n);
00411423 lea eax,[n]
00411426 push eax
00411427 lea ecx,[m]
0041142A push ecx
0041142B call swap (411113h)
00411430 add esp,8
看一下411113h 这个地址是一条jmp指令,跳转到swap的真正地址:
0x00411113 e9 98 04 00 00
00411113 jmp swap (4115B0h)
int swap(int& a, int& b)
{
004115B0 push ebp
004115B1 mov ebp,esp
004115B3 sub esp,0C0h
因此,这种情况仅仅是正常的的函数调用。
情况二:EXE内部调用自己的swap函数,swap函数使用__declspec(dllimport)修饰
这种情况跟情况一生成的代码基本一样,唯一有点点区别是在编译的时候在swap的实现处会出现下面的警告(大概意思就是声明有点不一致,与编译器的预期有点不一样,因为编译器预期会是一个dll的导出函数,嗯,大概是这么个情况,不用深究):
warning C4273: 'swap' : inconsistent dll linkage
情况三:EXE内部调用dll的add函数,add函数不使用__declspec(dllimport)修饰
对add的调用生成的汇编代码为:
add(m, n);
004113FC mov eax,dword ptr [n]
004113FF push eax
00411400 mov ecx,dword ptr [m]
00411403 push ecx
00411404 call add (411127h)
看下411127h这个地址也是一条jmp指令,跳转到411620h
0x00411127 e9 f4 04 00 00
00411127 jmp add (411620h)
跳转到411620h之后发现居然还不是add的地址,而是
00411620 jmp dword ptr [__imp_add (4181E0h)]
依然是一个jmp指令,跳转到4181E0h指向的一个位置,看下内存,应该是跳转到0x100110f0去执行: 0x004181E0 f0 10 01 10
跟过去,居然还是一条跳转指令,这次是跳转到0x10011340:
100110F0 jmp add (10011340h)
再跟过去,这次终于对了:
int __stdcall add(int a, int b)
{
10011340 push ebp
10011341 mov ebp,esp
10011343 sub esp,0C0h
10011349 push ebx
这次很纠结,中间多了几个jmp指令,这个我们先不讲,我们先看看情况四之后再讨论。
情况四:EXE内部调用dll的add函数,add函数使用__declspec(dllimport)修饰
对add的调用生成的汇编代码为:
add(m, n);
004113FC mov esi,esp
004113FE mov eax,dword ptr [n]
00411401 push eax
00411402 mov ecx,dword ptr [m]
00411405 push ecx
00411406 call dword ptr [__imp_add (4181E0h)]
这次情况好像有点不一样,直接call了4181E0h指向的一个位置,看下内存:
0x004181E0 f0 10 01 10
也就是说跳转到0x100110f0去执行,跟过去:
100110F0 jmp add (10011340h)
直接一个jmp指令到10011340h,再跟过去就是add的真正地址了:
int __stdcall add(int a, int b)
{
10011340 push ebp
10011341 mov ebp,esp
10011343 sub esp,0C0h
这种情况下,对比情况三,情况四少了2条jmp指令……
结论:
看来msdn说的没错o(∩_∩)o ,确实使用了__declspec(dllimport)之后,生成的代码更加高效。在对release代码的查看中发现,最终会少一条jmp指令。
其实整个来讲还是比较好理解的,对于编译器来讲,你没有__declspec(dllimport)这个来修饰函数声明,它开始就会把函数当成一个本地函数来生成调用代码(如:call add (411127h)),但是最后链接的时候发现这个函数是个动态链接库里面的函数,真正地址会存在输入表里面,因此中间就多了至少一条jmp指令来中转。如果使用了__declspec(dllimport)来修饰,那么编译器知道,哦,是个外部的函数,函数的地址在输入表里面,虽然现在不知道函数真正地址,但是装载之后该函数地址在输入表中的位置是固定的,因此编译器就直接生成调用输入表中某个地方的函数地址的代码了(如:call dword ptr [__imp_add (4181E0h)])。
【END】
作者:magictong
时间:2010-09-08
注:例子演示里面都是以debug模式下的汇编来讲,在release下因为经过一些优化,过程会有一些区别,但是最终的结论是一样的。
__declspec本身就是microsoft对c++的扩展,因此后面的讨论都是指在VS2005下编译的结果,与__declspec(dllimport)相对的一个组合用法是__declspec(dllexport),__declspec(dllexport)是作用于PE文件导出函数、类、变量等等(如果你不用def文件导出函数,就必须使用__declspec(dllexport)来进行导出)。__declspec(dllimport)的具体作用在msdn上面讲得比较模糊,如下Importing into an Application Using __declspec(dllimport):
英文:
Using __declspec(dllimport) is optional on function declarations, but the compiler produces more efficient code if you use this keyword. However, you must use __declspec(dllimport) for the importing executable to access the DLL's public data symbols and objects. Note that the users of your DLL still need to link with an import library.
中文:
在函数声明上使用 __declspec(dllimport) 是可选操作,但如果使用此关键字,编译器将生成更有效的代码。但是,为使导入的可执行文件能够访问 DLL 的公共数据符号和对象,必须使用 __declspec(dllimport)。请注意,DLL 的用户仍然需要与导入库链接。
所谓可选操作,就是可要可不要,但是如果使用了,编译器会生成更高效的代码。嗯,编译器就是这么一个意思。后来我测试了四种情况,来看看到底是什么情况。
情况一:EXE内部调用自己的swap函数,swap函数没有使用__declspec(dllimport)修饰
int swap(int& a, int& b);int swap(int& a, int& b){ a = a ^ b; b = a ^ b; a = a ^ b; return 0;}
对swap的调用生成的汇编代码为:
swap(m, n);
00411423 lea eax,[n]
00411426 push eax
00411427 lea ecx,[m]
0041142A push ecx
0041142B call swap (411113h)
00411430 add esp,8
看一下411113h 这个地址是一条jmp指令,跳转到swap的真正地址:
0x00411113 e9 98 04 00 00
00411113 jmp swap (4115B0h)
int swap(int& a, int& b)
{
004115B0 push ebp
004115B1 mov ebp,esp
004115B3 sub esp,0C0h
因此,这种情况仅仅是正常的的函数调用。
情况二:EXE内部调用自己的swap函数,swap函数使用__declspec(dllimport)修饰
这种情况跟情况一生成的代码基本一样,唯一有点点区别是在编译的时候在swap的实现处会出现下面的警告(大概意思就是声明有点不一致,与编译器的预期有点不一样,因为编译器预期会是一个dll的导出函数,嗯,大概是这么个情况,不用深究):
warning C4273: 'swap' : inconsistent dll linkage
情况三:EXE内部调用dll的add函数,add函数不使用__declspec(dllimport)修饰
#if IMPORTDLLDLL_EXPORTS#define API_DECLSPEC __declspec(dllexport)#else #define API_DECLSPEC#endif // ------------------------------------------------------------------------- API_DECLSPEC int __stdcall add(int a, int b); int __stdcall add(int a, int b){ return a + b;}
对add的调用生成的汇编代码为:
add(m, n);
004113FC mov eax,dword ptr [n]
004113FF push eax
00411400 mov ecx,dword ptr [m]
00411403 push ecx
00411404 call add (411127h)
看下411127h这个地址也是一条jmp指令,跳转到411620h
0x00411127 e9 f4 04 00 00
00411127 jmp add (411620h)
跳转到411620h之后发现居然还不是add的地址,而是
00411620 jmp dword ptr [__imp_add (4181E0h)]
依然是一个jmp指令,跳转到4181E0h指向的一个位置,看下内存,应该是跳转到0x100110f0去执行: 0x004181E0 f0 10 01 10
跟过去,居然还是一条跳转指令,这次是跳转到0x10011340:
100110F0 jmp add (10011340h)
再跟过去,这次终于对了:
int __stdcall add(int a, int b)
{
10011340 push ebp
10011341 mov ebp,esp
10011343 sub esp,0C0h
10011349 push ebx
这次很纠结,中间多了几个jmp指令,这个我们先不讲,我们先看看情况四之后再讨论。
情况四:EXE内部调用dll的add函数,add函数使用__declspec(dllimport)修饰
#if IMPORTDLLDLL_EXPORTS#define API_DECLSPEC __declspec(dllexport)#else #define API_DECLSPEC __declspec(dllimport)#endif // ------------------------------------------------------------------------- API_DECLSPEC int __stdcall add(int a, int b); int __stdcall add(int a, int b){ return a + b;}
对add的调用生成的汇编代码为:
add(m, n);
004113FC mov esi,esp
004113FE mov eax,dword ptr [n]
00411401 push eax
00411402 mov ecx,dword ptr [m]
00411405 push ecx
00411406 call dword ptr [__imp_add (4181E0h)]
这次情况好像有点不一样,直接call了4181E0h指向的一个位置,看下内存:
0x004181E0 f0 10 01 10
也就是说跳转到0x100110f0去执行,跟过去:
100110F0 jmp add (10011340h)
直接一个jmp指令到10011340h,再跟过去就是add的真正地址了:
int __stdcall add(int a, int b)
{
10011340 push ebp
10011341 mov ebp,esp
10011343 sub esp,0C0h
这种情况下,对比情况三,情况四少了2条jmp指令……
结论:
看来msdn说的没错o(∩_∩)o ,确实使用了__declspec(dllimport)之后,生成的代码更加高效。在对release代码的查看中发现,最终会少一条jmp指令。
其实整个来讲还是比较好理解的,对于编译器来讲,你没有__declspec(dllimport)这个来修饰函数声明,它开始就会把函数当成一个本地函数来生成调用代码(如:call add (411127h)),但是最后链接的时候发现这个函数是个动态链接库里面的函数,真正地址会存在输入表里面,因此中间就多了至少一条jmp指令来中转。如果使用了__declspec(dllimport)来修饰,那么编译器知道,哦,是个外部的函数,函数的地址在输入表里面,虽然现在不知道函数真正地址,但是装载之后该函数地址在输入表中的位置是固定的,因此编译器就直接生成调用输入表中某个地方的函数地址的代码了(如:call dword ptr [__imp_add (4181E0h)])。
【END】
- __declspec(dllimport) 对【函数调用】编译结果的影响
- __declspec(dllimport) 对【函数调用】编译结果的影响
- 使用 __declspec(dllimport) 能够优化对DLL导出函数的调用.
- 使用 __declspec(dllimport) 导入函数调用
- 编写DLL函数时的 __declspec(dllexport) 与 __declspec(dllimport)
- __declspec(dllimport) 的作用
- __declspec(dllimport)的作用
- __declspec(dllimport)的作用
- __declspec(dllimport)的作用
- __declspec(dllimport)的作用
- __declspec(dllimport)的作用
- __declspec(dllimport)的作用
- __declspec(dllimport)的作用
- __declspec(dllimport)的作用
- __declspec(dllimport)的作用
- __declspec(dllimport)的作用
- __declspec(dllimport)的作用 .
- __declspec(dllimport)的作用
- 【STL中的erase()方法 】
- 线程的交互
- POJ 2676 Sudoku dfs
- poj1503Integer Inquiry
- 《MacOSX及iOS应用程序开发工具集》(Apple Xcode)v4.0.2 and iOS SDK v4.3[光盘镜像]
- __declspec(dllimport) 对【函数调用】编译结果的影响
- 使用动态代理进行GZIP数据压缩过滤
- SQL中各种日期的算法
- 使用包装模式进行GZIP数据压缩过滤
- android 通话涉及的类分析
- poj3006Dirichlet's Theorem on Arithmetic Progressions
- cocos2d中启用多点触摸的方法
- Android权限大全
- S3C2440裸机实验