小巧。快速。可靠。
三选二。
运行时加载扩展

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,但体系结构错误”,则可能需要根据应用程序的构建方式向 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. 编程可加载扩展

一个模板可加载扩展包含以下三个元素

  1. 在源代码文件的顶部使用“#include <sqlite3ext.h>”代替“#include <sqlite3.h>".

  2. 在“SQLITE_EXTENSION_INIT1”行的正下方单独一行放置宏“#include <sqlite3ext.h>”行。

  3. 添加一个扩展加载入口点例程,其外观类似于以下内容

    #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. 示例扩展

可以在 SQLite 源代码树的ext/misc子目录中看到许多完整且可运行的可加载扩展示例。该目录中的每个文件都是一个单独的扩展。文档由文件上的标题注释提供。以下是ext/misc子目录中一些扩展的简要说明

其他更复杂的扩展可以在ext/下的除 ext/misc/ 之外的子文件夹中找到。

5. 持久化可加载扩展

可加载扩展的默认行为是在最初调用sqlite3_load_extension()的数据库连接关闭时从进程内存中卸载它。(换句话说,当数据库连接关闭时,将对所有扩展调用sqlite3_vfs对象的 xDlClose 方法。)但是,如果初始化过程返回SQLITE_OK_LOAD_PERMANENTLY而不是 SQLITE_OK,则扩展不会被卸载(不会调用 xDlClose),并且扩展将无限期地保留在进程内存中。SQLITE_OK_LOAD_PERMANENTLY 返回值对于想要注册新的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 编译,以从构建中省略扩展加载代码。

此页面上次修改于 2024-01-31 23:45:50 UTC