Small. Fast. Reliable.
Choose any three.
运行时可加载扩展

1.概述

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启用扩展加载 。

命令行壳,扩展可以使用“.load”点命令被装载。例如:

.load ./您的代码

请注意,命令行外壳程序已经为您启用了扩展加载功能(通过在安装过程中调用sqlite3_enable_load_extension() 接口作为安装程序的一部分),因此上述命令可以正常运行,而无需任何特殊的开关,设置或其他复杂操作。

带一个参数的“ .load”命令调用zProc参数设置为NULL的sqlite3_load_extension(),从而使SQLite首先查找名为“ sqlite3_extension_init”的入口点,然后查找“ sqlite3_X_init”,其中“ X”是从文件名派生的。如果扩展名的入口点具有不同的名称,只需提供该名称作为第二个参数即可。例如:

.load ./YourCode nonstandard_entry_point

3.编译可加载的扩展

可加载的扩展名是C代码。要在大多数类unix的操作系统上编译它们,通常的命令是这样的:

gcc -g -fPIC-共享的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”,具体取决于应用程序的构建方式。

要在Windows上使用MSVC进行编译,通常会使用类似于以下命令:

cl YourCode.c -link -dll -out:YourCode.dll

要使用MinGW为Windows进行编译,除了将输出文件后缀更改为“ .dll”并省略-fPIC参数外,命令行与unix相同。

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)
    #万一
    int sqlite3_extension_init(/ * <==更改此名称,也许* /
      sqlite3 * db, 
      char ** pzErrMsg, 
      const sqlite3_api_routines * pApi
    ){
      int rc = SQLITE_OK;
      SQLITE_EXTENSION_INIT2(pApi);
      / *在此处插入代码以初始化扩展名* /
      返回rc;
    }
    

    您最好自定义入口点的名称,使其与要生成的共享库的名称相对应,而不要使用通用的“ sqlite3_extension_init”名称。如果您以后决定使用静态链接而不是运行时链接,则为扩展名提供一个自定义的入口点名称将使您能够将两个或多个扩展名静态链接到同一程序中,而不会发生链接器冲突。如果共享库最终被命名为“ YourCode.so”或“ YourCode.dll”或“ YourCode.dylib”,如上面的编译器示例所示,则正确的入口点名称应为“ sqlite3_yourcode_init”。

这是一个完整的模板扩展,您可以复制/粘贴以开始使用:

/ *在此处添加标题注释* /
#include <sqlite3ext.h> / *不要使用<sqlite3.h>!* /
SQLITE_EXTENSION_INIT1

/ *在此处插入您的扩展代码* /

#ifdef _WIN32
__declspec(dllexport)
#万一
/ * TODO:更改入口点名称,以便将“扩展名”替换为
**来自共享库文件名的文本,如下所示:复制每个
**从文件名的最后一个“ /”到最后的ASCII字母字符
**下一个“。”之后,将每个字符转换为小写,并且
**如果前三个字符是“ lib”,则将其丢弃。
* /
int sqlite3_extension_init(
  sqlite3 * db, 
  char ** pzErrMsg, 
  const sqlite3_api_routines * pApi
){
  int rc = SQLITE_OK;
  SQLITE_EXTENSION_INIT2(pApi);
  / *在此处插入对
  ** sqlite3_create_function_v2(),
  ** sqlite3_create_collat​​ion_v2(),
  ** sqlite3_create_module_v2(),和/或
  ** sqlite3_vfs_register()
  **注册扩展程序添加的新功能。
  * /
  返回rc;
}

4.1。示例扩展

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

其他和更复杂的扩展名可以在ext /下的子文件夹中找到,而不是ext / misc /。

5.持久的可加载扩展

可加载扩展的默认行为是,当最初调用sqlite3_load_extension()的数据库连接关闭时,将从进程内存中将其卸载 。(换句话说,关闭数据库连接时,将为所有扩展调用sqlite3_vfs对象的xDlUnload方法。)但是,如果初始化过程返回 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_OMIT_LOAD_EXTENSION编译SQLite, 以从构建中省略扩展加载代码。