返回:SQLite—系列文章目录
上一篇:SQLite轻量级会话扩展(三十四)
下一篇:SQLite的DBSTAT 虚拟表(三十六)
1. 概述
SQLite 能够在运行时加载扩展(包括新的应用程序定义的 SQL 函数、整理序列、虚拟表和 VFS)。 此功能允许开发扩展代码和 与应用程序分开测试,然后加载 根据需要。
扩展也可以与应用程序静态链接。 下面显示的代码模板将像静态一样工作 链接扩展,因为它作为运行时可加载扩展,除了 您应该提供入口点函数 (“sqlite3_extension_init”) 如果应用程序包含以下内容,则使用其他名称以避免名称冲突 两个或多个扩展。
2. 加载扩展
SQLite 扩展是共享库或 DLL。要加载它,您需要 需要向 SQLite 提供包含 共享库或 DLL 以及用于初始化扩展的入口点。 在 C 代码中,此信息是使用 sqlite3_load_extension() API 提供的。请参阅有关此内容的文档 例程以获取更多信息。
请注意,不同的操作系统使用不同的文件名 其共享库的后缀。Windows 使用“.dll”,Mac 使用 “.dylib”,除 Mac 外的大多数 Unix 都使用“.so”。如果你想 使你的代码可移植,你可以从共享中省略后缀 将自动添加库文件名和相应的后缀 通过 sqlite3_load_extension() 接口。
还有一个可用于加载扩展的 SQL 函数:load_extension(X,Y)。它的工作方式与 sqlite3_load_extension() C 接口类似。
加载扩展的两种方法都允许您指定 扩展的入口点的名称。 您可以将此参数留空 - 传入 sqlite3_load_extension() C 语言接口的 NULL 指针 或省略 load_extension() SQL 接口的第二个参数 - 扩展加载器逻辑将尝试找出入口点 靠它自己。它将首先尝试通用扩展名 “sqlite3_extension_init”。如果这不起作用,它会构造一个 使用模板“sqlite3_X_init”的入口点,其中 X 被替换 与文件名中每个 ASCII 字符的小写等效值 在最后一个“/”之后和第一个“.”之前省略 前三个字符(如果它们恰好是“lib”)。因此,例如, 如果文件名为“/usr/lib/libmathfunc-4.8.so”,则为入口点名称 将是“sqlite3_mathfunc_init”。或者,如果文件名是 “./SpellFixExt.dll”,则将调用入口点 “sqlite3_spellfixext_init”。
出于安全原因,扩展加载默认处于关闭状态。 为了使用 C 语言或 SQL 扩展加载函数, 必须首先使用
sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,1,NULL)
启用扩展加载 应用程序中的 C 语言 API。
在命令行 shell 中,可以使用 “.load” 点命令。例如:
.load ./YourCode
请注意,命令行 shell 程序已启用 扩展加载(通过调用 sqlite3_enable_load_extension() 接口作为其设置的一部分),因此上面的命令无需 任何特殊开关、设置或其他复杂情况。
带有一个参数的“.load”命令调用 sqlite3_load_extension() 将 zProc 参数设置为 NULL,导致 SQLite 首先查找 名为“sqlite3_extension_init”,然后名为“sqlite3_X_init”的入口点 其中“X”派生自文件名。如果扩展有条目 点与不同的名称,只需提供该名称作为第二个 论点。例如:
.load ./YourCode nonstandard_entry_point
3. 编译可加载扩展
可加载的扩展是 C 代码。要编译它们,请执行以下操作 最像 UNIX 的操作 系统,通常的命令是这样的:
gcc -g -fPIC -shared YourCode.c -o YourCode.so
Mac 是类似 unix 的,但它们不遵循通常的共享库 约定。要在 Mac 上编译共享库,请使用类似 这:
gcc -g -fPIC -dynamiclib YourCode.c -o YourCode.dylib
如果在尝试加载库时收到错误消息 这说“Mach-O,但架构错误”,那么您可能需要添加 命令行选项“-arch i386”或“arch x86_64”到 gcc,具体取决于 关于如何构建应用程序。
若要使用 MSVC 在 Windows 上编译,请使用类似于以下内容的命令 通常可以:
cl YourCode.c -link -dll -out:YourCode.dll
要使用 MinGW 为 Windows 编译,命令行就像它一样 适用于 UNIX,但输出文件后缀更改为“.dll”,并且 省略了 -fPIC 参数:
gcc -g -shared YourCode.c -o YourCode.dll
4. 对可加载扩展进行编程
模板可加载扩展包含以下三个元素:
-
使用源顶部的“#include < sqlite3ext.h>” 代码文件,而不是“#include < sqlite3.h>”。
-
将宏“SQLITE_EXTENSION_INIT1”单独放在一行上 紧接在“#include < sqlite3ext.h>”行之后。
-
添加一个扩展加载入口点例程,如下所示 内容如下:
#ifdef _WIN32 __declspec(dllexport) #endif int sqlite3_extension_init( /* <== Change this name, maybe */sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){int rc = SQLITE_OK;SQLITE_EXTENSION_INIT2(pApi);/* insert code to initialize your extension here */return rc; }
您可以很好地将入口点的名称自定义为 对应于您将要生成的共享库的名称, 而不是使用通用的“sqlite3_extension_init”名称。给 您的扩展自定义入口点名称将使您能够静态地 在没有链接器的情况下将两个或多个扩展链接到同一程序中 冲突(如果您以后决定使用静态链接而不是运行时) 连接。 如果您的共享库最终被命名为“YourCode.so”或 编译器示例中显示的“YourCode.dll”或“YourCode.dylib” ,那么正确的入口点名称将是 “sqlite3_yourcode_init”。
这是一个完整的模板扩展,您可以复制/粘贴 要开始使用,请执行以下操作:
/* Add your header comment here */
#include <sqlite3ext.h> /* Do not use <sqlite3.h>! */
SQLITE_EXTENSION_INIT1/* Insert your extension code here */#ifdef _WIN32
__declspec(dllexport)
#endif
/* TODO: Change the entry point name so that "extension" is replaced by
** text derived from the shared library filename as follows: Copy every
** ASCII alphabetic character from the filename after the last "/" through
** the next following ".", converting each character to lowercase, and
** discarding the first three characters if they are "lib".
*/
int sqlite3_extension_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi
){int rc = SQLITE_OK;SQLITE_EXTENSION_INIT2(pApi);/* Insert here calls to** sqlite3_create_function_v2(),** sqlite3_create_collation_v2(),** sqlite3_create_module_v2(), and/or** sqlite3_vfs_register()** to register the new features that your extension adds.*/return rc;
}
4.1. 示例扩展
完整且可加载扩展的许多示例可以是 在 ext/misc 子目录的 SQLite 源代码树中看到。 该目录中的每个文件都是一个单独的扩展名。文档 由文件上的标题注释提供。 以下是对 ext/misc 子目录:
-
卡雷.c — carray 表值函数的实现。
-
压缩.c — 实现应用程序定义的 SQL 函数 compress()和 uncompress()对文本或 blob 内容进行 zLib 压缩。
-
json1.c — JSON SQL 函数和表值函数的实现。 这是一个更大、更复杂的扩展。
-
memvfs.c — 实现将所有内容存储在内存中的新 VFS。
-
rot13.c — rot13() SQL 函数的实现。这是一个非常简单的扩展函数示例 并可用作创建新扩展的模板。
-
系列.c — 实现generate_series虚拟表和表值函数。这是一个相对简单的示例 虚拟表实现,可以作为编写模板 新的虚拟表。
其他更复杂的扩展可以在子文件夹中找到 在 ext/ 下,除 ext/misc/ 外。
5. 持久可加载扩展
可加载扩展的默认行为是卸载它 当最初调用 sqlite3_load_extension() 的数据库连接关闭时,从进程内存中。(换言之,xDlClose 方法 的 sqlite3_vfs 对象在数据库时为所有扩展调用 连接关闭。但是,如果初始化过程返回 SQLITE_OK_LOAD_PERMANENTLY 而不是 SQLITE_OK,则扩展将 未卸载(不会调用 xDlClose),并且扩展将保留 无限期地在进程内存中。SQLITE_OK_LOAD_PERMANENTLY回归 value 对于想要注册新 VFS 的扩展非常有用。
澄清一下:初始化函数返回的扩展 SQLITE_OK_LOAD_PERMANENTLY在数据库之后继续存在于内存中 连接关闭。但是,扩展不会自动进行 注册到后续数据库连接。这使它成为可能 加载实现新 VFS 的扩展。 持久加载和注册实现新 SQL 的扩展 函数、整理序列和/或虚拟表,使得那些 添加的功能可用于所有后续数据库连接, 然后,初始化例程还应该在将注册这些服务的子函数上调用 sqlite3_auto_extension()。
vfsstat.c 扩展 显示一个可加载扩展的示例,该扩展永久注册两者 新的 VFS 和新的虚拟表。该扩展中的 sqlite3_vfsstat_init() 初始化例程仅调用一次,当 首先加载扩展。它注册了新的“vfslog”VFS 一次,它返回SQLITE_OK_LOAD_PERMANENTLY,以便使用的代码 要实现“vfslog”,VFS 将保留在内存中。初始化例程 还在指向“vstatRegister()”的指针上调用 sqlite3_auto_extension(函数,以便所有后续数据库连接都将调用 “vstatRegister()”函数,因此注册 “vfsstat”虚拟表。
6. 静态链接运行时可加载扩展
完全相同的源代码可用于运行时可加载 共享库或 DLL,并作为与 应用。这提供了灵活性,并允许你重复使用相同的内容 以不同的方式编写代码。
要静态链接扩展,只需添加 -DSQLITE_CORE 编译时选项。SQLITE_CORE宏会导致SQLITE_EXTENSION_INIT1 并将宏SQLITE_EXTENSION_INIT2为无操作。然后修改你的 应用程序直接调用入口点,传入 NULL 指针 作为第三个“pApi”参数。
使用入口点名称尤为重要 基于扩展名文件名,而不是通用 “sqlite3_extension_init”入口点名称,如果将静态 链接两个或多个扩展。如果使用通用名称,则有 将是同一符号的多个定义,并且链接将失败。
如果要在应用程序中打开多个数据库连接, 而不是调用每个数据库的扩展入口点 连接 另外,您可能需要考虑使用 sqlite3_auto_extension() 接口来注册您的扩展和 使它们在每个数据库连接时自动启动 已打开。您只需注册每个扩展一次,就可以 在 main() 例程的开头附近执行此操作。使用 sqlite3_auto_extension() 接口注册扩展使 您的扩展就像它们内置在核心 SQLite 中一样 - 它们 每当打开新的数据库连接时,自动存在 无需初始化。只要确保完成任何 您需要先使用 sqlite3_config() 完成的配置 注册扩展,因为 sqlite3_auto_extension() 接口隐式调用 sqlite3_initialize()。
7. 实施细节
SQLite 使用 sqlite3_vfs 对象的 xDlOpen()、xDlError()、xDlSym()和 xDlClose()方法。这些方法是使用 unix 上的 dlopen()库(这解释了为什么 SQLite 通常 需要链接到 Unix 系统上的“-ldl”库) 并在 Windows 上使用 LoadLibrary()API。在自定义 VFS 中 不寻常的系统,这些方法都可以省略,在这种情况下 运行时扩展加载机制将不起作用(尽管 您仍然可以静态链接扩展代码,假设 入口指针是唯一命名的)。 SQLite可以用SQLITE_OMIT_LOAD_EXTENSION进行编译,以省略扩展加载代码 从构建。