Unix 和 Windows 对于代码的运行时加载使用了完全不同的范式。 在你尝试构建可动态加载的模块之前,要先了解你所用系统是如何工作的。
在 Unix 中,一个共享对象 (.so) 文件中包含将由程序来使用的代码,也包含在程序中可被找到的函数名称和数据。 当文件被合并到程序中时,对在文件代码中这些函数和数据的全部引用都会被改为指向程序中函数和数据在内存中所放置的实际位置。 这基本上是一个链接操作。
在 Windows 中,一个动态链接库 (.dll) 文件中没有悬挂的引用。 而是通过一个查找表执行对函数或数据的访问。 因此在运行时 DLL 代码不必在运行时进行修改;相反地,代码已经使用了 DLL 的查找表,并且在运行时查找表会被修改以指向特定的函数和数据。
在 Unix 中,只存在一种库文件 (.a),它包含来自多个对象文件 (.o) 的代码。 在创建共享对象文件 (.so) 的链接阶段,链接器可能会发现它不知道某个标识符是在哪里定义的。 链接器将在各个库的对象文件中查找它;如果找到了它,链接器将会包括来自该对象文件的所有代码。
在 Windows 中,存在两种库类型,静态库和导入库 (扩展名都是 .lib)。 静态库类似于 Unix 的 .a 文件;它包含在必要时可被包括的代码。 导入库基本上仅用于让链接器能确保特定标识符是合法的,并且将在 DLL 被加载时出现于程序中。 这样链接器可使用来自导入库的信息构建查找表以便使用未包括在 DLL 中的标识符。 当一个应用程序或 DLL 被链接时,可能会生成一个导入库,它将需要被用于应用程序或 DLL 中未来所有依赖于这些符号的 DLL。
假设你正在编译两个动态加载模块 B 和 C,它们应当共享另一个代码块 A。 在 Unix 上,你 不应 将 A.a 传给链接器作为 B.so 和 C.so;那会导致它被包括两次,这样 B 和 C 将分别拥有它们自己的副本。 在 Windows 上,编译 A.dll 将同时编译 A.lib。 你 应当 将 A.lib 传给链接器用于 B 和 C。 A.lib 并不包含代码;它只包含将在运行时被用于访问 A 的代码的信息。
在 Windows 上,使用导入库有点像是使用 import spam;它让你可以访问 spam 中的名称,但并不会创建一个单独副本。 在 Unix 上,链接到一个库更像是 from spam import *;它会创建一个单独副本。 |