一、概述
SQLite 能够在运行时加载扩展(包括新 的应用程序定义的 SQL 函数、 整理序列、虚拟表和VFSes)。此功能允许扩展代码与应用程序分开开发和测试,然后根据需要加载。
扩展也可以与应用程序静态链接。下面显示的代码模板将像静态链接扩展一样工作,就像它作为运行时可加载扩展一样工作,除了你应该给入口点函数(“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,但错误的体系结构”,那么您可能需要向 gcc 添加命令行选项“-arch i386”或“arch x86_64”,具体取决于您的应用程序是如何构建的。
要使用 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子目录 中一些扩展的简要说明:
carray.c — carray 表值函数的实现。
compress.c —应用程序定义的 SQL 函数compress() 和 uncompress() 的实现,它们对文本或 blob 内容进行 zLib 压缩。
json1.c — JSON SQL 函数和表值函数的实现。这是一个更大更复杂的扩展。
series.c — generate_series虚拟表和 表值函数的实现。这是一个相对简单的虚拟表实现示例,可以作为编写新虚拟表的模板。
其他和更复杂的扩展可以在ext/下的子文件夹中找到,而不是 ext/misc/。
5.持久可加载扩展
可加载扩展的默认行为是当最初调用 sqlite3_load_extension()的数据库连接关闭时,它会从进程内存中卸载。(换句话说,当数据库连接关闭时, sqlite3_vfs对象的 xDlClose 方法会为所有扩展调用。)但是,如果初始化过程返回 SQLITE_OK_LOAD_PERMANENTLY而不是 SQLITE_OK,则不会卸载扩展(不会调用 xDlClose)扩展将无限期地保留在进程内存中。SQLITE_OK_LOAD_PERMANENTLY 返回值对于想要注册新VFSes的扩展很有用。
澄清一下:初始化函数返回 SQLITE_OK_LOAD_PERMANENTLY 的扩展在数据库连接关闭后继续存在于内存中。但是,该扩展不会自动注册到后续的数据库连接中。这使得加载实现新VFSes的扩展成为可能。要持续加载和注册实现新 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()。
七、实施细节
SQLite 使用sqlite3_vfs对象的 xDlOpen()、xDlError()、xDlSym() 和 xDlClose() 方法实现运行时扩展加载 。这些方法在 unix 上使用 dlopen() 库(这解释了为什么 SQLite 通常需要链接到 unix 系统上的“-ldl”库)并在 Windows 上使用 LoadLibrary() API 来实现。在用于不寻常系统的自定义VFS中,这些方法都可以省略,在这种情况下,运行时扩展加载机制将不起作用(尽管您仍然可以静态链接扩展代码,假设入口指针是唯一命名的) . SQLite 可以使用 SQLITE_OMIT_LOAD_EXTENSION进行编译,以从构建中省略扩展加载代码。