windows - 为什么动态链接导入(在 Windows 中)总是在应用程序启动时加载?
问题描述
动态链接到库的一个好处是,当调用该库中的函数时,会将其加载到程序的虚拟地址空间中,然后调用该函数。静态链接加载整个可执行文件,占用空间。
在 Windows 上,当加载程序时,甚至在调用程序入口点之前,所有函数导入都已解析。例如:kernel32.dll!VirtualFree
。加载所有需要的库,然后调用程序的入口点。
这与静态链接的可执行文件有什么不同?如果一次加载所有引用的库,它会不会占用相同数量的空间?有什么好处?请帮我理解。
解决方案
事实上,Windows 应用程序所依赖的所有 DLL都没有必要在启动时加载。自 Visual C++ 6.0 (1998) 以来,MS 链接器支持/DELAYLOAD
延迟加载 DLL 直到且除非调用其导出之一的选项。
默认行为是在启动时加载 DLL。我假设您想知道默认行为如何在内存占用和更普遍的方面与链接静态库进行权衡。
假设具有相同实现的相同 API 被构建为静态库libfoo.lib
和动态库libfoo.dll
。
只要libfoo.dll
在给定时间只有一个程序需要,那么此时消耗的内存至少
与同一个程序静态链接时消耗的内存一样多libfoo.lib
。
事实上,加载 DLL 的程序版本很可能比与静态库链接的程序版本消耗更多的内存。这是因为当第一次加载 DLL 时,整个DLL 被加载到内存中。但事实上,当一个程序链接到一个静态库时,并不是整个静态库都必须合并到可执行文件中。静态库只是目标文件的存档,默认情况下,链接器仅从中提取定义程序引用的符号的文件,并将它们链接到程序中,而忽略其余部分。因此,如果程序不需要将所有目标文件归档在libfoo.lib
,那么静态链接所需的内存libfoo.lib
将小于
动态链接所需的内存libfoo.dll
。
但是,一旦有多个运行程序libfoo.dll
同时需要,内存成本就会开始向 DLL 倾斜。这是因为 DLL 由加载程序可以单独加载的不同代码和数据部分组成。每个需要的并发程序都需要libfoo.dll
拥有自己的 DLL数据副本,但它们都可以执行其代码的相同副本。在
libfoo.dll
为第一个需要它的程序加载了代码和数据部分之后,加载器只需为
需要它的任何更多并发程序加载其数据的新副本。
因此,当我们考虑整个运行系统的内存占用时,其中许多并发进程可能需要单个库提供的服务,在 DLL 中而不是静态库中实现这些服务是经济的策略。
DLL 的主要目的是以这样一种方式提供服务,即无论有多少并发程序正在执行它,都只需要加载一个实现代码的副本。
但是它们还有另一个重要的好处,即使对于只可能由一个应用程序使用的库也是如此。当foo
库的新版本发布时,提供错误修复或增强功能,此版本可以部署到已与静态库链接的程序的唯一方法libfoo.lib
是重新链接、重新分发和重新安装所有这些程序。但是只要新版本libfoo
保留了现有的 API,就不需要对与libfoo.dll
. 只需分发和安装 的新版本libfoo.dll
,这些程序将在下次运行时加载它。应用程序架构师可以选择实现特定于应用程序的模块DLL 中的功能,以便可以部署此功能的更新,而无需最终用户重新安装应用程序。
推荐阅读
- scala - 如何使用 spark sql scala 拆分列中的 geojson 数据
- r - 将文件夹和子文件夹中的所有文件列出到表中
- r - stargazer R:显示多个回归,不同的数据并排显示
- c# - LINQ 按多列分组并计数
- javers - 如何使用普通存储库保存终端快照
- string - 将段落拆分为字符串列表,每个字符串不超过给定大小并避免将单词分成两半
- python - 在列表元素的选定部分中查找子字符串
- perl - 我可以在我的 Perl 包中压缩我的导出例程吗?
- java - java Serializable接口没有功能,为什么会影响“writeObject”/“readObject”
- java - 在 Eclipse 中,Junit 不会从 src/test/resources 文件夹中获取文件。