__declspec(dllimport)加和不加的区别

总的来说对于DLL总导入的函数调用,加和 不加__declspec(dllimport)的效果都是一样的,
区别在于,不加__declspec(dllimport)则二进制代码多了一个JMP指令,仅此而已。

对于C++中的函数调用编译器函数调用大多产生类似 call 0xxxxxxxxx 形式的指令代码,如果不加
__declspec(dllimport) 则编译器分辨不出普通的函数调用和从别的DLL导入的函数调用,为了弥补编译器
利用了dll产生的 输入库 lib文件,当不加__declspec(dllimport)时,对于从其他dll导入的函数call的地 址是
链接器填入的在这个dll中 相对这个导入函数在lib文件中的代码,类似 jmp DWORD PTR [0Xxxxxxxxx]形式,
这个JMP指令跳转地址是实际的导入函数在exe中的导入表 IAT中的位置,这个位置处存的就是导入函数
被加载到进程中的实际的地址。如果加了 __declspec(dllimport)的话,编译器知道这个函数是从其他的dll
导入的函数,则对这个函数的调用形式为 CALL dword ptr [0xxxxxxxxx],相比于不加 __declspec(dllimport)
少了一个跳转指令的执行,直接从IAT找到导入函数在进程中的地址去执行。

不加 __declspec(dllimport),调用导入函数先call到lib中对应的导入函数的 stub代码,stub代码实际
跳转到导入函数在IAT中记录的实际地址处执行,加上 __declspec(dllimport)则是直接CALL调用IAT中
导入函数的实际地址执行。
每个DLL产生的lib文件则用来安放stub代码,每个lib文件必须编译到每个exe中去,exe可能是先根据
lib产生导入表,然后再根据导入表修改lib中stub跳转代码跳转到的存导入函数地址的IAT地址了吧!这样的话
exe文件只要完成了编译则这些代码就都是固定的了!



要点:PE文件中,当CALL另一模块中的函数,编译器产生的CALL指令并不会把控制权直接传给DLL中的函数,而是传给一个JMPDWORD PTR[xxxxxxxx]指令。

CALL外部的DLLs,其实并不是直接CALL DLL本身,而是跳到一块存放有JMPDWORD PTR [XXXXXXXX]指令的存储区域去(可能放在.text或是.icode)。不过若在VC++中使用__declspec(dllimport)进行函数CALL,编译器不会在模块的其他地方产生JMP指令,而是直接就会产生CALL DWORD PTR[XXXXXXXX]。不论哪种情况,JMPCALL指令中的位址都存放在.idata节的一个DWORD值中(这个DWORD内含该函数的真正位址,即函数入口点)。JMPCALL指令会把控制权转给该位址。.

一个PE文件呼叫imported function的图示( User32.dll中的GetMessage函数)

上述图示中的偏移地址BFC0847D才是真正指向User32.dll模块中的GetMessage()函数。

   为什么DLL的呼叫需要以此方式实现?

原因是把对同一个DLL函数的所有CALL都集中到一处,加载器就不再需要修补每一个CALL DLL的指令。PE加载器要做的,只是把DLL函数的真实位址放到.idata的那个DWORD之中,根本就没有程序码需要修改。(不象NE文件的每一个节段内含一串待修正记录fixup records)。PE文件这种处理方式的缺点:不能够以DLL函数的真正位址初始化一个变量,如:FARPROC pfnGetMessage=GetMessage ;


其他关于此点的可以参考的文章:

http://blog.sina.com.cn/s/blog_6e6798460101j2t5.html


猜你喜欢

转载自blog.csdn.net/xiaohua_de/article/details/78243025