使用 __declspec(dllimport) 和 __declspec(dllexport)

使用 __declspec(dllimport) 和 __declspec(dllexport)

32 位版本的 Visual C++ 使用__declspec(dllimport)和__declspec(dllexport)替换以前在 16 位版本的 Visual C++ 中使用的__export关键字。

您不需要使用__declspec(dllimport)来让您的代码正确编译,但这样做可以让编译器生成更好的代码。编译器能够生成更好的代码,因为它可以确定某个函数是否存在于 DLL 中,因此编译器可以生成跳过通常存在于跨越 DLL 边界的函数调用中的间接层的代码。但是,您必须使用__declspec(dllimport)才能导入 DLL 中使用的变量。

使用正确的 .DEF 文件 EXPORTS 部分,不需要__declspec(dllexport)。添加了__declspec(dllexport)以提供一种无需使用 .DEF 文件即可从 .EXE 或 .DLL 导出函数的简便方法。

Win32 Portable Executable 格式旨在最大限度地减少必须修改导入的页面数量。为此,它将任何程序的所有导入地址放在一个称为导入地址表的地方。这允许加载器在访问这些导入时只修改一两个页面。

使用 _declspec(dllimport) 进行函数调用
在下面的代码示例中,假设func1是一个驻留在与包含主函数的 .EXE 文件分开的 DLL 中的函数。

没有__declspec(dllimport),给定以下代码:

void main(void)
{
func1();
}
编译器生成如下所示的代码:

call func1

链接器将调用转换为如下内容:

call 0x4000000         ; The address of 'func1'.

如果func1存在于另一个 DLL 中,则链接器无法直接解析它,因为它无法知道其地址func1是什么。在 16 位环境中,链接器将此代码地址添加到 .EXE 中的列表中,加载程序将在运行时使用正确的地址修补该列表。在 32 位环境中,链接器生成一个它知道地址的 thunk。thunk 看起来像:

0x40000000: jmp DWORD PTR __imp_func1
这__imp_func1是func1.EXE 文件的导入地址表中 的插槽的地址。因此,链接器知道所有地址。加载器只需在加载时更新 .EXE 文件的导入地址表,一切才能正常工作。

因此,使用__declspec(dllimport)更好,因为如果不需要,链接器不会生成 thunk。Thunk 使代码更大(在 RISC 系统上,它可以是多条指令)并且会降低您的缓存性能。如果您告诉编译器该函数在 DLL 中,它可以为您生成一个间接调用。

所以现在这个代码:

__declspec(dllimport) void func1(void);
void main(void)
{
func1();
}
生成此指令:

call DWORD PTR __imp_func1
没有thunk,也没有jmp指令,所以代码更小更快。

另一方面,对于 DLL 内的函数调用,您不希望必须使用间接调用。您已经知道函数的地址。在间接调用之前加载和存储函数的地址需要时间和空间,因此直接调用总是更快更小。当从 DLL 本身的外部调用 DLL 函数时,您只想使用__declspec(dllimport)。构建 DLL 时,不要在 DLL 内的函数上使用__declspec(dllimport)。

使用 _declspec(dllexport)
Microsoft在 Visual C++ 的 16 位编译器版本中引入了__export,以允许编译器自动生成导出名称并将它们放置在 .LIB 文件中。然后可以像使用静态 .LIB 一样使用此 .LIB 文件来链接 DLL。

Microsoft 添加了__declspec(dllexport)以继续提供这种便利。其目的是将导出指令添加到目标文件中,这样您就不需要 .DEF 文件了。

这种便利在尝试导出修饰的 C++ 函数名称时最为明显。名称修饰没有标准规范,因此导出函数的名称可能会因编译器版本而异。如果您使用__declspec(dllexport),则仅需要重新编译 DLL 和相关的 .EXE 文件以解决任何命名约定更改。

许多导出指令,例如序号、NONAME 和 PRIVATE,只能在 .DEF 文件中创建,并且没有 .DEF 文件就无法指定这些属性。但是,除了使用 .DEF 文件之外,还使用__declspec(dllexport)不会导致构建错误。

作为参考,搜索 Win32 WINBASE.H 头文件。它包含__declspec(dllimport)用法的示例。

在数据上使用 __declspec(dllexport) 和 __declspec(dllimport)
在数据的情况下,使用__declspec(dllimport)是一个方便的项目,它删除了一个间接层。当你从一个 DLL 中导入数据时,你仍然需要通过导入地址表。在__declspec(dllimport)之前的 Win32 天,这意味着您必须记住在访问从 DLL 导出的数据时执行额外的间接级别:

// project.h
#ifdef _DLL // If accessing the data from inside //the DLL
ULONG ulDataInDll;

#else // If accessing the data from //outside the DLL
ULONG *ulDataInDll;
#endif
然后,您将导出 .DEF 文件中的数据:

// project.def
LIBRARY project
EXPORTS
ulDataInDll CONSTANT
并在 DLL 之外访问它:

if (*ulDataInDll == 0L)
{
// Do stuff here
}
当您将数据标记为__declspec(dllimport) 时,编译器会自动为您生成间接代码。您不再需要担心上述步骤。如前所述,在构建 DLL 时不要对数据使用__declspec(dllimport)声明。DLL 内的函数不会使用导入地址表来访问数据对象;因此,您不会有额外的间接级别。

要从 DLL 中自动导出数据,请使用以下声明:

__declspec(dllexport) ULONG ulDataInDLL;
使用 .DEF 文件
如果您选择将__declspec(dllimport)与 .DEF 文件一起使用,您应该更改 .DEF 文件以使用 DATA 代替 CONSTANT 以减少错误编码导致问题的可能性:

// project.def
LIBRARY project
EXPORTS
ulDataInDll DATA
下表显示了原因:

使用 .DEF 文件
关键词 在导入库中发出 出口
CONSTANT _imp_ulDataInDll
_ulDataInDll _ulDataInDll
DATA _imp_ulDataInDll _ulDataInDll
使用__declspec(dllimport)和 CONSTANT 列出__imp_了 .LIB DLL 导入库中的版本和未修饰的名称,该库是为了允许显式链接而创建的。使用__declspec(dllimport)和 DATA 仅列出__imp_名称的版本。

如果您使用 CONSTANT,则可以使用以下任一代码构造来访问ulDataInDll:

__declspec(dllimport) ULONG ulDataInDll; /prototype/
if (ulDataInDll == 0L) /sample code fragment/
-或者-

ULONG *ulDataInDll; /prototype/
if (*ulDataInDll == 0L) /sample code fragment/
但是,如果您在 .DEF 文件中使用 DATA,则只有使用以下定义编译的代码才能访问变量ulDataInDll:

__declspec(dllimport) ULONG ulDataInDll;

if (ulDataInDll == 0L) /sample code fragment/
使用 CONSTANT 的风险更大,因为如果您忘记使用额外的间接级别,您可能会访问导入地址表的变量指针,而不是变量本身。这种类型的问题通常表现为访问冲突,因为导入地址表当前由 Microsoft 编译器和链接器设置为只读。

如果当前 Visual C++ 链接器在 .DEF 文件中看到 CONSTANT 来说明这种情况,则它会发出警告。使用 CONSTANT 的唯一真正原因是,如果您无法重新编译某些头文件未在原型上列出__declspec(dllimport) 的目标文件。

转自:https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-6.0/aa271769(v=vs.60)?redirectedfrom=MSDN

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值