首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C++模块与动态链接的期望关系是什么?

C++模块与动态链接的期望关系是什么?
EN

Stack Overflow用户
提问于 2018-09-12 03:17:40
回答 1查看 1.5K关注 0票数 9

C++模块TS为消除预处理器、提高编译时间以及通常支持C++中更健壮、模块化的代码开发提供了极好的便利,至少对非模板代码是如此。

底层机器对普通程序中符号的进出口提供控制。

然而,为两种动态加载开发库存在一个主要问题:启动时加载和运行时加载。这个问题涉及从库导出符号,这通常是从可见性的角度来讨论的。

通常,并不是所有用于构建动态链接库的翻译单元的外部符号都应向用户显示。此外,在运行时加载(特别是插件概念)时,必须从许多并发加载的库中导出相同的符号。

关于Windows语言扩展的使用

代码语言:javascript
复制
 __declspec(dllexport)
 __declspec(dllimport)

作为符号的属性附加在源代码中,最近在unix平台上使用gcc和clang系统。

代码语言:javascript
复制
__attribute__((visibility("default")))
__attribute__((visibility("hidden"))) 

旨在支持提供和使用拟由图书馆公开的符号。使用这些是复杂和混乱的:在Windows上,必须在编译库时使用宏来导出符号,但在使用时必须导入它们。在unix平台上,可见性必须设置为默认为导出和导入符号,编译器根据是否找到定义来决定自己:编译器必须使用

代码语言:javascript
复制
-fvisibility=hidden 

切换。静态链接不需要导出/导入属性,可能应该将宏输出到空字符串。编写代码和修改构建系统,使所有这些都能工作,特别是考虑到在编译库翻译单元时#include必须具有正确的符号可见性设置是非常困难的,存储库中所需的文件结构混乱不堪,源代码中到处都是宏,而且一般情况下。整件事就是一场灾难。几乎所有开源存储库都无法正确导出用于动态链接的符号,而且大多数程序员都不知道动态库代码结构(使用两个级别的名称空间)与静态链接有很大不同。

在这里可以看到一个如何(希望是正确的)这样做的例子:

t

这个存储库过去有2个头和2个实现文件,构建库的用户将看到2个头文件。现在有7个头文件和2个实现文件,构建库的用户将看到5个头文件(3个扩展名为include,以表明它们不被直接包括在内)。

因此,在冗长的解释之后,问题是:最终的C++模块规范是否有助于解决动态链接符号的导出和导入问题?我们能否期望能够为共享库进行开发,而不会用特定于供应商的扩展和宏来污染我们的代码呢?

EN

回答 1

Stack Overflow用户

发布于 2022-11-15 11:32:26

模块不能帮助您跨DLL边界实现符号可见性。我们可以用一个快速的实验来验证这一点。

代码语言:javascript
复制
// A.ixx
export module A;

export int f() { return 1; }

这里有一个简单的模块接口文件,导出模块A的模块接口中的一个符号A(碰巧与文件基名称共享相同的名称,但这是不必要的)。让我们这样编译如下:

代码语言:javascript
复制
cl /c /std:c++20 /interface /TP A.ixx

/c标志避免调用链接器(默认情况下自动发生),模块语法需要c++20或更高版本才能工作,/interface标志让编译器知道我们正在编译模块接口单元。/TP arg说“将源输入视为C++输入”,并且在指定/interface时是必需的。最后,我们有我们的输入文件。

运行上述操作将生成一个接口文件A.ifc和一个对象文件A.obj。请注意,在编译DLL时,不存在导入库文件或exp文件。

接下来,让我们编写一个消耗此文件的文件。

代码语言:javascript
复制
// main.cpp
import A;

int main() { return f(); }

要将其编译成可执行文件,我们可以使用以下命令:

代码语言:javascript
复制
cl /std:c++20 main.cpp A.obj

A.obj输入的存在不是可选的。没有它,我们得到一个经典的链接错误f是一个未解决的符号。如果我们运行这个程序,我们将得到一个main.exe,它静态地链接A.obj中的代码。

如果我们尝试将A.ixx编译成DLL,会发生什么情况?也就是说,如果我们尝试从A.obj生成一个带有链接器的DLL怎么办?得到的答案是DLL,但没有导入库或exp。如果您尝试运行link /noentry /dll A.obj /out:A.dll,您将得到一个带有预期/disasm部分(通过转储桶可见)的A.dll,但没有导出表。

代码语言:javascript
复制
Dump of file A.dll

File Type: DLL

  0000000180001000: B8 01 00 00 00     mov         eax,1
  0000000180001005: C3                 ret

  Summary

        1000 .rdata
        1000 .text

这是我们所期望的A.dll中的反汇编,但是使用dumpbin /export A.dll检查导出部分没有发现任何结果。原因当然是,我们没有出口这个符号!

如果我们将A.ixx的源代码更改为:

代码语言:javascript
复制
// A.ixx
export module A;

export __declspec(export) int f() { return 1; }

..。我们可以重复这些步骤(编译A.obj,链接A.dll),以发现这一次,链接器会像我们预期的那样生成一个导入库和exp文件。在生成的导入库上调用dumpbin /exports A.lib应该会显示?f@@YAHXZ符号。

现在,我们可以通过main.cpp重新链接A.lib (而不是A.obj)来生成有效的可执行文件,这一次,代码依赖于A.dll,而不是静态嵌入f

我们可以检查这种情况在WinDbg中是否确实发生了。

注意,在左下角模块窗格中存在A.dll。还请注意,在中心的反汇编视图中,我们将调用main!f。哦,不太好。虽然这样做可以正确地解析到!A模块,但它可以通过导入地址表中的一个额外的间接方向这样做,如下所示:

当您忘记用__declspec(dllimport)指令装饰函数或符号时,这就是典型的问题。当编译器遇到没有它识别的dllimport指令的符号时,它就会发出一个重定位条目,这个条目在链接时就会被解析。除了该条目外,它还会发出一个jmp和一个未解决的地址。这是一个经典的问题,我不会在这里讨论,但是结果是,我们有一个额外的不必要的间接方向,因为从模块A导出的符号被认为是静态链接的。

事实证明,我们很难解决这个问题。如果我们尝试将f的另一个声明添加到main.cpp或其他翻译单元,链接器会抱怨它看到f具有“不一致的dll链接”。解决这一问题的唯一方法是用A装饰编译第二个版本的dllimport模块接口(很像标头通常具有扩展到dllexportdllimport的宏的方式,这取决于使用标头的TU )。

故事的寓意是,DLL链接和模块链接虽然不是完全矛盾,但也不是特别兼容。模块export不包括导出表中的导出符号,这是跨DLL边界解析符号所需的。此外,在导出表中放置这些符号,在通过导入地址表完成隐式动态链接之后,仍然会给您带来额外间接的麻烦。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52286991

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档