动态链接-Linux与。视窗


10

在Windows下,当我在MSVC的DLL项目中编译C / C ++代码时,我得到2个文件:

  1. MyDll.dll
  2. MyDll.lib

据我了解,MyDll.lib其中包含某种指示符表,用于指示dll中的函数位置。使用此dll时(例如在exe文件中),MyDll.lib在链接期间会嵌入到exe文件中,因此在运行时它“知道”函数的位置MyDll.dll并可以使用它们。

但是,如果我在Linux下编译相同的代码,我只会得到一个MySo.so不带 文件的文件MySo.a(相当于libLinux中的文件),那么MySo.so如果在链接过程中未嵌入任何内容,Linux下的可执行文件如何知道函数的位置?

Answers:


1

在Linux上,链接器(不是动态链接器)搜索链接时指定的共享库,并在可执行文件中创建对它们的引用。当动态链接程序加载这些可执行文件时,它将所需的共享库加载到内存中并解析符号,从而可以运行二进制文件。

MySo.a,如果创建的话,实际上将包括直接链接到二进制文件的符号,而不是Windows上使用的“符号查找表”。

rustyx的答案比我更全面地解释了Windows上的过程。自从我使用Windows已经很长时间了。


1
“ Windows采用了不同的方法...在操作系统中准确指定符号在DLL中的位置”-这与Wiki相反,后者表示即使在您启动时或在首次调用库函数时,函数名仍会解析。使用序号(除非使用直接地址绑定,否则没人会这样做,因为只要库发生更改,它就会强制库用户重新编译和重新部署其代码)。
yugr,

@yugr删除了那部分,无论如何我还是在抓住稻草。
SS安妮

4

MSVC链接器可以将对象文件(.obj)和对象库(.lib)链接在一起,以生成.EXE或.DLL。

为了与DLL链接,MSVC中的过程是使用所谓的导入库(.LIB),它充当C函数名称与DLL的导出表之间的胶合剂(在DLL中,可以按名称或按顺序排列 -后者通常用于未记录的API)。

但是,在大多数情况下,DLL导出表具有所有函数名称,因此导入库(.LIB)包含大量冗余信息(“ 导入函数ABC->导出函数ABC ”等)。
甚至有可能从现有的.DLL 生成 .LIB。

其他平台上的链接器没有此“功能”,可以直接与动态库链接。


“其他平台上的链接器不具有此功能” –尽管很容易实现(例如Implib.so在Linux上也是如此)以实现延迟加载和其他好处。
yugr,

@yugr:这就是为什么“功能”用引号引起来-它不是您通常想要做的,而且是Windows上必须要做的额外工作。
克里斯·多德

1

您看到的区别更多是实现细节-在Linux和Windows的工作原理下类似-您的代码调用了一个存根函数,该存根函数静态链接到您的可执行文件中,然后该存根将在必要时加载DLL / shlib(以防延迟)加载,否则在程序启动时加载库),并且(在首次调用时)通过GetProcAddress/ 解析符号dlsym

唯一的区别是,在Linux上,当您将应用程序与动态库(库包含足够的信息来生成它们)链接时,这些存根函数(称为PLT存根)是动态生成的,而在Linux上,它们是在DLL本身为创建在单独的.lib文件中。

这两种方法是如此相似,以至于实际上可以在Linux上模拟Windows导入库(请参见Implib.so项目)。


0

在Linux上,您传递MySo.so给链接器,它只能提取链接阶段所需的内容,并MySo.so在运行时放入所需的引用。


-3

.dll或是.so共享库(在运行时链接),而.a.lib是静态库(在编译时链接)。Windows和Linux之间没有区别。

不同之处在于它们的处理方式。注意:区别仅在于海关,如何使用。使Linux以Windows方式构建并不难,反之亦然,除非实际上没有人这样做。

如果我们使用dll,或者甚至从我们自己的二进制文件中调用函数,则有一种简单明了的方法。例如,在C中,我们看到:

int example(int x) {
  ...do_something...
}

int ret = example(42);

但是,在asm级别上可能会有很多差异。例如,在x86上,call执行一个操作码,并42在堆栈上给出。或在某些寄存器中。或任何地方。没有人知道在编写dll之前将如何使用它。或项目将如何使用它,可能是用现在还不存在的编译器(或用一种语言!)编写的(或者对于dll的开发人员来说是未知的)。

例如,默认情况下,C和Pascal都从堆栈中放入参数(并获取返回值),但是它们的执行顺序不同。您还可以通过某些编译器相关的优化在寄存器中的函数之间交换参数。

如您所见,Windows习惯是构建一个dll,我们还使用它创建了一个minimal .a/ .lib。这个最小的静态库只是一个包装,该dll的符号(函数)通过​​它到达。这将进行所需的asm级调用转换。

它的优点是兼容性。它的缺点是,如果只有.dll,则可能很难弄清楚如何调用其函数。如果dll的开发人员没有向您提供.a这将使dll的使用成为黑客行为。因此,它主要用于封闭性目的,例如,这样可以更轻松地为SDK获得额外的现金。

它的另一个缺点是,即使使用动态库,也需要静态编译此小包装程序。

在Linux中,dll的二进制接口是标准的,并且遵循C约定。因此,不需.a要这样做,并且共享库之间存在二进制兼容性,但作为交换,我们没有Microsoft自定义的优点。


1
请提供一个证明链接,以便存根函数可以更改参数顺序。考虑到巨大的性能开销,我以前从未听说过,而且很难相信。
yugr

@yugr简单的寄存器/堆栈重新排序不会增加性能。如果从msvc编译的二进制文件中使用msvc编译的dll,那么显然不会发生太多,但是可能会发生。
彼得-恢复莫妮卡

1
我们可以对此进行争论,但是如果您是对的,则应该很容易提供证明链接,证明存根函数能够对参数进行非平凡的处理(并且不仅仅是虚假的蹦床)。
yugr

@yugr存根可以访问dll的功能签名,从而使非平凡的处理变得微不足道。
peterh-恢复莫妮卡

1
我只建议您使用有关导入库功能的证明链接来完成您的答案(因为某些声明值得怀疑)。
yugr,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.