Home Resources

共享库

Table of Contents

复用

复用其他人写好的功能基本来说有两种,一种直接拷贝相关代码到项目内,一种是依赖对应功能所在的项目或者对应项目的生成物。

宽泛的来说,第二种使用方式我们可以说依赖了这个项目“库”;可能是直接使用其源码,或者链接编译出的静态/动态库,抑或是利用特定工具管理、引入语言特定或工具特定的包。

静态库 & 动态库

当说到静态库(static library)和(dynamic library)1,一般指的是操作系统特定的库。

静态库在编译期使用,其中包含机器码以及要引用代码的各类偏移,依赖它们的程序在编译时,将这些机器码拷贝到最终的可执行文件中,并通过相关偏移引用需要的代码。

动态库在程序的加载时(load time2)或者运行时(runtime)被加载,主流操作系统中,动态库的格式和可执行文件的格式一致,它们共用程序加载器2;当可执行文件中包含符号表时,可被当作动态库使用。

动态库的查找方式和系统相关,对于 Windows

  1. 对于实现了 COM 的 DLL: 通过注册表寻找其位置
  2. 对于其他类型的 DLL,按以下顺序查找对应的 DLL
    • 程序的加载目录(private DLL)
    • 通过 SetDllDirectory 设定的那些目录
    • System32, System 和 Windows 目录
    • 当前工作目录(Current Working Directory)
    • PATH 环境变量设定的那些目录
    • 另外,对于 .NET Framework (2002 之后)
      • Global Assembly Cache

对于类 Unix 系统

  • 加载器依赖搜索路径(search path)查找静态库,搜索路径可能
    • 通过配置文件进行配置
    • 被硬编码进了动态库加载器实现中
  • 另外,可执行文件本身可能允许指定额外的搜索路径
    • 这种情况下,可执行文件一般也允许通过环境变量指定这些额外的搜索路径

Java

Java 是编译型语言,其所有源码都需要被编译成包含 Java 字节码的 class 文件。对于第三方的依赖一般依赖的是它们的编译产物,也即 class 文件,我们需要一种方式找到依赖的类所在的 class 文件的路径,从而从中加载我们的需要的类。

这种方式即在编译和运行时指定依赖的 class 文件所在的路径列表,也称为 classpath。在 Java 世界中,编译出的 class 文件一般通过 jar 文件(使用 .jar 后缀伪装的 zip 文件,可以直接尝试使用 zip 解压查看)分发,因此 classpath 除了允许指定文件夹,也可以直接指定 jar 文件。

简单来说,执行 java -classpath ./:/path1 Hello 时,首先会依次在 .//path1 中查找类 Hello, 执行其中的静态 main 方法,执行过程 import 了其它的类,会再依次从 classpath 中查找。这是一个很简单的线性查找过程,容易理解。可惜,因为 classLoader 的存在,现实情况比这复杂一点3

手工维护项目的依赖文件,以及相关的 classpath 不可接受,项目构建工具会帮我们处理这种事情:

Python

对于 Python 来说,将你要引入的包放入 sys.path 中,即可直接 import 相关内容使用.

一般来说,并不会手工去维护所有要引入包的路径,Python 解释器启动时,其内置 site 包会初始化第三方包(称为 site-package 4)的路径到 sys.path, 你可以将要使用的包拷贝到这些位置,当然,更合适的方式是使用 Python 的包安装器 pip 进行包的安装。

不过,我们并不希望所有项目的依赖都安装到全局(一般在你 Python 的安装目录下)的 site-package 中,类似 virtualenv 5的工具可以帮你维护一些环境变量,让你可以拥有项目级别的虚拟环境,每个虚拟环境包含一个独立的 site-package,依赖会默认安装到这里.

virtualenv 是为某一个 Python 版本管理多个虚拟环境,若希望在系统中安装多个版本的 Python,可以使用系统级别的包管理器(如 macOS 的 HomeBrew)安装多个版本的 Python,更方便的是使用 pyenv 安装、管理多个 Python 版本。

另外,目前有一些工具集成了 Python 版本管理、虚拟环境管理、包管理、依赖管理功能,提供完整的 Python 开发工作流:

Footnotes:

2

Loader 时操作系统的一部分,用于加载程序和库,加载过程主要包括:

  • 将可执行文件中程序指令部分加载进内存,可能通过
    • memory mapping
    • 直接拷贝进内存
  • 做好程序执行前必要的准备工作

Author: lotuc, Published at: 2023-05-23 Tue 00:00, Modified At: 2023-06-13 Tue 20:00 (orgmode - Publishing)