将 SpatiaLite 动态加载作为扩展模块
理论基础
从技术上讲,SpatiaLite 只是一个扩展模块,为 SQLite 添加了空间处理功能。
因此,一些适当的机制允许加载二进制库(libsqlite3 和 libspatialite),然后在 SQLite3 中正确注册 SpatiaLite 实现的许多扩展空间 SQL 函数是一项严格要求的任务。
在适当的债务中应采取进一步的复杂化措施;libspatialite 不是一个简单的独立独立库,它依赖于几个其他库(libgeos、libproj、libxml2 等)。
有几种可能的替代途径可以通向这个最终目标:
- 使用静态链接:一个简单的解决方案,避免了与安装和运行时配置相关的许多后续麻烦。
按照这种方法,将在构建时创建一个内部包含所有必需二进制代码的单体可执行文件;因此,这个整体式可执行文件将完全没有外部依赖关系,从而从根本上简化了任何后续安装任务。
这种策略在独立的独立应用程序(例如,spatialite.exe 和 spatialite_gui.exe Windows 二进制文件通常都是以这种方式构建的)的情况下非常有效。
不幸的是,这个基本的简单解决方案并不适合 Linux 和类 Unix 环境;甚至最糟糕的是,经常与许多语言连接器或其他复杂框架(例如QGIS)支持的内部架构直接冲突。 - 使用共享库(在 Windows 上又名 DLLs)。
第二种方法小心翼翼地避免一劳永逸地链接整体可执行文件;相反,每个代码组件将被打包为一个自主的单个成员,只有在运行时,所有组件才会最终绑定在一起。
好消息是:按照这种方法,每个库/组件将(希望)在目标系统上只安装一次,并且依赖于该库的每个可执行文件都将加载完全相同的二进制代码,从而提供强大的整体一致性。
坏处:如果单个必需的组件无法在运行时加载(由于任何可能的原因),则整个可执行文件将无法运行。
更糟糕的是,这种模式在 Linux、Unix 和 Mac OS X 上运行良好;但在 Windows 上,它受到许多严重问题的困扰(臭名昭著的 DLL 地狱)。 - 使用动态加载,又名后期绑定(不必与简单地使用共享库混淆)。
按照第三种方法,可以有选择地加载扩展模块,也可以不加载扩展模块,并且实际操作将仅在运行时激活,具体取决于某些请求事件。 如果由于任何原因,主可执行文件无法加载最终的扩展模块,它仍然会继续工作,只需忽略需要失败模块的每个可选功能即可。 作为直接结果,以这种方式构建的可执行文件理论上可以以高度动态的方式增长到无限;这对应于所谓的插件程序架构。
令人高兴的是,SQLite3 完全支持这种更高级的机制;许多语言连接器(Python、Java/JDBC、PHP、C# .NET 等)广泛支持此选项。
愚蠢的怪事(要小心避免):
- 没有什么禁止创建混合配置,例如采用静态和动态联动的混合。
- 这样的解决方案很容易吸引幼稚的打包者,因为它显然可以简化安装和配置任务。
- 无论如何,这通常是一个危险的陷阱,只会导致潜在的不稳定和自冲突的配置:这就是所谓的眼镜蛇效应(即当试图解决问题实际上使问题变得更糟时)。
- 黄金法则:永远不要尝试在广泛基于共享库的配置中引入静态链接库。这不是一个聪明的解决方案,只会引入许多意想不到的复杂情况。
动态加载:工作原理
libsqlite3 具有动态加载扩展模块的功能。此功能可作为 C 语言 API 使用,但也可作为特殊的 SQL 函数使用:此 SQL 函数使加载任何可能的扩展的基本问题变得简单(并且绝对独立于语言);您只需要执行一个相当微不足道的 SQL 语句,以便仅在实际需要时加载外部扩展。
可加载扩展解剖结构:
SELECT load_extension('mod_spatialite');
- 任何SQLite的扩展模块都应该声明一个众所周知的常规接口。 除此之外,任何SQLite自己的扩展都只是一个普通的共享库(或DLL),就像任何其他扩展一样。
- 真正的扩展模块永远不会显式依赖于 SQLite3,因为 SQLite3 在加载时会自动执行以下操作:
- 扩展定义/支持的所有 SQL 函数都将添加到标准列表中;最终,扩展可能会重载 SQLite3 本身直接定义的任何预定义 SQL 函数。
- 这还不是全部;SQLite3 将隐式地向扩展传递其自身 API 的镜像副本:这将确保完全相同的代码将同时支持 SQLite 自己的核心和扩展,并有效避免它们之间任何可能的冲突。
- 从 SQLite 版本 3.7.17(发布于 2013-05-20)开始,可加载扩展机制得到了显著增强:
- 现在,任何扩展模块都应声明自己的与扩展名称直接相关的单个入口点;这使得同时加载更多模块变得更加简单。
- 仅指定模块的基本名称;任何最终的后缀(例如.so或.dll)都将由SQLite本身根据特定的目标系统期望自动添加:这允许更容易的跨平台可移植性。
- 即使使用一些过时的版本(< 3.7.17),任何采用最新约定的扩展模块最终都可以加载,但在这种情况下,需要相同的特殊操作。 有关更多详细信息,请查阅 SQLite 自己的文档。
常见故障原因:
- 如果正在加载的共享库没有正确声明预期的常规接口,那么 SQLite3 将简单地假设它不是一个有效的扩展模块。
因此将拒绝加载模块而不会产生任何其他后果(除非可能打印一些适当的错误消息)。 - 更明显的失败原因可能是尝试加载不存在的模块(或者可能,无法通过应用与系统相关的标准搜索规则来解析实际地址的模块)。
- 最后,该模块可以有效地存在,并且可以正确地声明一个有效的常规接口。无论如何,如果相应的共享库有进一步的依赖关系,一些断开的链接可能会阻止成功加载。
- 最后一个注定失败的组合:SQLite3 是一个强可配置的组件。如果由于任何非常有问题的选择,您的系统打包程序决定禁用所有扩展模块机制,那么您将永远无法加载任何扩展。结局。(无论如何,你可能会切换到一些更合理/不那么偏执的发行版)。
- 有用的提示:许多语言连接器都支持非常简约且相当无用的诊断。如果您在成功将 SpatiaLite 加载为扩展时遇到一些麻烦,那么任何直接调试代码的尝试都可能是一种令人沮丧且不确定的体验。
无论如何,您始终可以应用替代方法:- 启动 sqlite3 命令行前端。
- 然后执行有问题的 SELECT load_extension 语句。
- 如果某些故障禁止成功加载扩展模块,那么 SQLite3 将始终打印许多有用的错误消息,清楚地解释违规原因在哪里。在合理的配置中,sqlite3 工具和语言连接器都应调用 libsqlite3 的相同副本,因此您将有效地测试语言连接器采用的相同运行时配置,但从更丰富的诊断消息中获利(不幸的是,并非所有配置都像您预期的那样始终是净和干净的…此规则有许多例外,尤其是在 Windows 上)。
mod_spatialite 中的新增功能
您可能已经习惯于将 SpatiaLite 加载为动态扩展;但是 4.2 之前的任何先前版本都没有真正区分可加载模块和通用共享库。
正如我们从直接的现场经验中痛苦地了解到的那样,这种看似简单的配置带来了很多麻烦:不稳定、突然崩溃等等。更清楚地区分通用共享库和纯可加载模块似乎是最终的解决方案。
正是从4.2版开始的激进创新介绍;现在 SpatiaLite 以两种替代风格分发:
- libspatialite(.so,.dll,.dylib等):一个真正的经典共享库。
它将始终依赖于一些外部 libsqlite3,并且仅供独立应用程序(例如 spatialite 或 spatialite_gui)使用。
你永远无法通过 SELECT load_extension 机制成功地将这个 libspatialite 共享库加载为动态扩展,因为它没有声明所需的常规接口。 - mod_spatialite(.so、.dll、.dylib 等):这只是作为纯粹的可加载模块,没有任何显式的 SQLite3 依赖项。
你永远无法按照经典的方式直接链接这个mod_spatialite共享库,因为它没有声明任何外部链接符号,除了一个符号:即传统接口。 加载和激活此模块的唯一可能方法是调用 SELECT load_extension SQL 语句。
原文地址:【Dynamically loading SpatiaLite as an extension module】https://www.gaia-gis.it/fossil/libspatialite/wiki?name=mod_spatialite