30

我有一个关于 Windows 共享库 (DLL) 与 Linux 共享库 (SO) 的快速问题。

为什么当您创建 Windows DLL 时,它需要客户端程序也链接到静态库(.lib 文件),但在 Linux 中创建的应用程序不需要链接到此类静态库。

它与代码重定位等有什么关系吗?谢谢。

4

3 回答 3

43

为什么当您创建 Windows DLL 时,它需要客户端程序也链接到静态库(.lib 文件),但在 Linux 中创建的应用程序不需要链接到此类静态库。

这是 Microsoft 做出的历史性设计决定,以便链接器可以将 DLL 引用添加到可执行文件中,而无需在链接时出现特定版本的 DLL。这样做的原因是,总是有不同版本的 Windows 和不同版本的 DLL。同样在那个时候,微软正在与 IBM 在 OS/2 上进行合作,并且计划是,Windows 程序也可以在 OS/2 上执行。好吧,微软决定“背刺” OS/2,推出他们自己的基于 NT 内核的专业级操作系统。但这意味着,对于开发,您希望开发人员能够链接到系统 DLL,而无需提供所有不同的 DLL 变体。取而代之的是动态链接“模板”.lib文件,它们根本不是库,而只是符号和序数表(这是一个鲜为人知的事实,但 PE 二进制符号不仅可以通过字符串标识符加载,还可以通过整数加载,即所谓的序数)。

序数的一个副作用是,它们允许隐藏人类可读的符号,因此只有在知道序数←→函数关系的情况下才能使用 DLL。

在 Unix 中,传统是“你在要运行它的系统上构建它”,或者“你拥有所有目标系统文件”。所以从来没有像图书馆和链接信息那样分离的激励。从技术上讲,同样适用于 DLL。PE 可以导出符号和重定位表,DLL 会这样做,并且链接器可以从中获取它需要的所有信息,这很好。

如果你想用 Unix 共享对象隐藏符号,你通常会使用一个struct包含所有函数指针的单一对象,并且只按名称导出此结构的全局常量实例,其中包含许多未明确命名的指针。不过,您可以对 Windows DLL 做同样的事情。

TL;DR:这不是技术原因,而是营销和分销决策。

于 2013-05-24T17:42:09.720 回答
28

实际上不是代码重定位,这是一个完全不同的问题。这是关于架构上的差异:

  • 在 Windows 中,DLL 就像可执行文件 (EXE)。EXE 和 DLL 的主要区别在于 EXE 有一个入口点(main/WinMain 函数),因此它可以用来启动进程,而 DLL 只能加载到预先存在的进程中。但见 (1)

  • 在 Linux 中,.so 的工作方式类似于静态库 (.a)。主要区别在于.so 文件可以与正在运行的程序链接,而.a 文件只能在编译程序时链接。

这种方法的结果是在 linux 中可以使用相同的文件来构建和运行程序。但在 Windows 中,您需要一个适当的库 (LIB) 来链接程序。实际上,对应于 DLL 的库通常只有函数的名称,以满足链接器的要求,以及执行重定位的存根。但见(2)

(1) 好吧,DLL 也有入口点,但它不用作主函数,只是用作某种初始化/终结钩子。

(2) 一些链接器足够聪明,在一些简单的情况下,能够使用 DLL 本身链接到 DLL,而不需要额外的 LIB 文件。我认为至少 MinGW 链接器可以做到这一点。

于 2013-05-24T14:43:59.077 回答
4

在 Windows 中,客户端程序无需链接静态库即可访问 DLL 中的函数。动态链接可以完全在运行时发生,而客户端程序在编译时甚至不知道 DLL 的存在。

例如,如果您想在名为“bar.dll”的 DLL 中调用函数名称“foo”,则可以编写如下代码:

HINSTANCE hinst = LoadLibrary("bar.dll");
FARPROC foo = GetProcAddress(hinst, "foo");
foo();

并且“foo”和“bar.dll”很容易成为仅在运行时建立的值,例如通过配置文件或其他一些用户输入。

静态库的目的是自动化这个动态加载过程,通过创建就客户端程序而言看起来像是常规函数但在运行时链接到 DLL 的存根。通常,这种链接发生在客户端进程加载时,但也可以生成将按需加载和链接的库,因此 DLL 在实际需要之前不会被带入内存。确定链接何时发生的是静态库。

在大多数情况下,编译器可以自动生成这些库,因此从技术上讲,仅在链接到 DLL 函数时不需要它们。但是,一个例外(我知道)是链接到共享变量时。

在 Windows DLL 中,您可以创建一个带有变量的共享数据段,任何已加载该 DLL 的进程都可以访问这些变量。有关这些变量的大小和类型的信息存储在相关的静态库中,不能仅从 DLL 中确定。为了访问这些变量,客户端程序必须链接到该 DLL 的静态库。

据我所知,Linux 共享库不支持这样的概念。

更新

我还应该提到,在 Windows 上,可以创建一个 DLL,其中函数入口点仅按序号(数字)而不是名称导出。这可以被认为是一种数据隐藏形式,通常在实现者希望某些功能保持私有时使用。

有权访问静态库的人将能够按名称调用这些函数,因为该库将具有将函数名称链接到适当序号的详细信息。任何只有 DLL 的人都必须通过序号手动链接到函数,或者生成自己的具有组成名称的静态库。

于 2013-05-24T17:10:12.237 回答