一、简介
在Windows中,动态链接库(DLL)是作为函数和资源的共享库的一种可执行文件。动态链接是操作系统功能。它可使执行文件调用函数或使用存储在单独文件中的资源。可从使用这些函数和资源的可执行文件中对其分别进行部署。
DLL不是独立的可执行文件。DLL在调用它们的应用程序的上下文中运行。操作系统将DLL加载到应用程序的内存空间中。此操作要么在加载应用程序时(隐式链接)完成,要么在运行时按需(显示链接)完成。DLL还可以在可执行文件之间轻松共享函数和资源。多个应用程序可同时访问内存中单个DLL副本的内容。
DLL文件的布局于exe文件非常相似,但有一个重要区别:DLL文件包含导出表。导出表包含DLL导出到其他科执行文件的每个函数名称。这些函数时进入DLL中的入口点;只有导出表中的函数才能被其他可执行文件访问。DLL中的任何其他函数都是DLL的私有函数。
二、__declspec(dllexport)
我们可以使用__declspec(dllexport)关键字从DLL中导出数据、函数、类或类成员函数。
若要导出函数,__declspec(dllexport)关键字必须出现在调用约定关键字的左侧(如果指定了关键字的话)。例如:
__declspec(dllexport) void Function1(void);
若要导出类中的所有公共数据成员和成员函数,该关键字必须出现在类名的左侧,例如:
class __declspec(dllexport) CExampleExport : public CObject
{
... class definition ...
};
下面的例子展示了导出函数的用法:
#ifndef __EXPORT_H_
#define __EXPORT_H_
__declspec(dllexport) void ExportHello();
#endif // __EXPORT_H_
#include "export.h"
#include <iostream>
void ExportHello()
{
std::cout << "Hello World!" << std::endl;
}
我们选择动态库:
最终我们将生成相应的.dll(动态链接库)和.lib(导入库)文件。
注:.lib(导入库)是编译器生成dll时自动生成的lib文件(只包含了函数实现的声明索引信息,记录了dll文件中的函数的入库和地址信息)。
导入库的作用:导入库方便程序静态载入动态链接库,否则你可能需要在程序中调用LoadLibary调入dll,然后在调用GetProcAddress来获取对应的函数。而有了导入库则只需要在头文件中使用__declspec(dllimport)关键字声明调用函数即可。
三、__declspec(dllimport)
实际上__declspec(dllimport)是可选的。下面这个例子不使用__declspec(dllimport)关键字。
#ifndef __EXPORT_H_
#define __EXPORT_H_
void ExportHello();
#endif // __EXPORT_H_
#include "export.h"
int main(void)
{
ExportHello();
return 0;
}
当然__declspec(dllimport)也有自己的用途。
编译器会生成更高效的代码
未使用__declspec(dllimport)
我们之间使用上述的例子,反汇编查看生成的代码:
int main(void)
{
00007FF747711760 push rbp
00007FF747711762 push rdi
00007FF747711763 sub rsp,0E8h
00007FF74771176A lea rbp,[rsp+20h]
00007FF74771176F lea rcx,[__2CB3C88E_main@cpp (07FF747721001h)]
00007FF747711776 call __CheckForDebuggerJustMyCode (07FF747711348h)
ExportHello();
00007FF74771177B call ExportHello (07FF7477110C3h)
return 0;
00007FF747711780 xor eax,eax
}
我们单步调试调用动态库导出函数的过程:
00007FF7477110C3 jmp ExportHello (07FF747711798h)
00007FF747711798 jmp qword ptr [__imp_ExportHello (07FF747720000h)]
00007FFED06510C8 jmp ExportHello (07FFED06520F0h)
void ExportHello()
{
00007FFED06520F0 push rbp
00007FFED06520F2 push rdi
00007FFED06520F3 sub rsp,0E8h
00007FFED06520FA lea rbp,[rsp+20h]
00007FFED06520FF lea rcx,[__01D3C14F_export@cpp (07FFED0663067h)]
00007FFED0652106 call __CheckForDebuggerJustMyCode (07FFED065136Bh)
std::cout << "Hello World!" << std::endl;
00007FFED065210B lea rdx,[string "Hello World!" (07FFED065A8F8h)]
00007FFED0652112 mov rcx,qword ptr [__imp_std::cout (07FFED06611A0h)]
00007FFED0652119 call std::operator<<<std::char_traits<char> > (07FFED065107Dh)
00007FFED065211E lea rdx,[std::endl<char,std::char_traits<char> > (07FFED0651037h)]
00007FFED0652125 mov rcx,rax
00007FFED0652128 call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FFED0661190h)]
}
使用__declspec(dllimport)
通用使用相同的例子,但是这次加上__declspec(dllimport)关键字,然后反汇编查看:
int main(void)
{
00007FF609CC1760 push rbp
00007FF609CC1762 push rdi
00007FF609CC1763 sub rsp,0E8h
00007FF609CC176A lea rbp,[rsp+20h]
00007FF609CC176F lea rcx,[__2CB3C88E_main@cpp (07FF609CD1001h)]
00007FF609CC1776 call __CheckForDebuggerJustMyCode (07FF609CC1348h)
ExportHello();
00007FF609CC177B call qword ptr [__imp_ExportHello (07FF609CD0000h)]
return 0;
00007FF609CC1781 xor eax,eax
}
我们单步调试调用过程:
00007FFE6D3C10C8 jmp ExportHello (07FFE6D3C20F0h)
void ExportHello()
{
00007FFE6D3C20F0 push rbp
00007FFE6D3C20F2 push rdi
00007FFE6D3C20F3 sub rsp,0E8h
00007FFE6D3C20FA lea rbp,[rsp+20h]
00007FFE6D3C20FF lea rcx,[__01D3C14F_export@cpp (07FFE6D3D3067h)]
00007FFE6D3C2106 call __CheckForDebuggerJustMyCode (07FFE6D3C136Bh)
std::cout << "Hello World!" << std::endl;
00007FFE6D3C210B lea rdx,[string "Hello World!" (07FFE6D3CA8F8h)]
00007FFE6D3C2112 mov rcx,qword ptr [__imp_std::cout (07FFE6D3D11A0h)]
00007FFE6D3C2119 call std::operator<<<std::char_traits<char> > (07FFE6D3C107Dh)
00007FFE6D3C211E lea rdx,[std::endl<char,std::char_traits<char> > (07FFE6D3C1037h)]
00007FFE6D3C2125 mov rcx,rax
00007FFE6D3C2128 call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FFE6D3D1190h)]
}
总结:
使用__declspec(dllimport)时不会生成 thunk 。 Thunk 使代码变大,并可能降低缓存性能。