__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)修饰
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】
原创粉丝点击