zoukankan      html  css  js  c++  java
  • sqlite3使用总结(转并且修改)

    此文来自http://blog.csdn.net/shengfang666/article/details/7937200,我在这儿重新发一下,备份参考。

    前序
    一、       版本
    二、       基本编译
    三、       SQLITE操作入门
    (1)     基本流程
    (2)     SQL语句操作
    (3)     操作二进制
    (4)     事务处理
    四、       给数据库加密
    五、       后记

    前序:
    Sqlite3 的确很好用。小巧、速度快。但是因为非微软的产品,帮助文档总觉得不够。这些天再次研究它,又有一些收获,这里把我对 sqlite3 的研究列出来,以备忘记。
    这里要注明,我是一个跨平台专注者,并不喜欢只用 windows 平台。我以前的工作就是为 unix 平台写代码。下面我所写的东西,虽然没有验证,但是我已尽量不使用任何 windows 的东西,只使用标准 C 或标准C++。但是,我没有尝试过在别的系统、别的编译器下编译,因此下面的叙述如果不正确,则留待以后修改。
    下面我的代码仍然用 VC 编写,因为我觉得VC是一个很不错的IDE,可以加快代码编写速度(例如配合 Vassist )。下面我所说的编译环境,是VC2003。如果读者觉得自己习惯于 unix 下用 vi 编写代码速度较快,可以不用管我的说明,只需要符合自己习惯即可,因为我用的是标准 C 或 C++ 。不会给任何人带来不便。
    一、       版本
    从 www.sqlite.org 网站可下载到最新的 sqlite 代码和编译版本。我写此文章时,最新代码是 3.3.17 版本。
    很久没有去下载 sqlite 新代码,因此也不知道 sqlite 变化这么大。以前很多文件,现在全部合并成一个 sqlite3.c 文件。如果单独用此文件,是挺好的,省去拷贝一堆文件还担心有没有遗漏。但是也带来一个问题:此文件太大,快接近7万行代码,VC开它整个机器都慢下来了。如果不需要改它代码,也就不需要打开 sqlite3.c 文件,机器不会慢。但是,下面我要写通过修改 sqlite 代码完成加密功能,那时候就比较痛苦了。如果个人水平较高,建议用些简单的编辑器来编辑,例如 UltraEdit 或 Notepad 。速度会快很多。
    二、       基本编译
    这个不想多说了,在 VC 里新建 dos 控制台空白工程,把 sqlite3.c 和 sqlite3.h 添加到工程,再新建一个 main.cpp 文件。在里面写:
    extern "C"
    {
    #include "./sqlite3.h"
    };
    int main( int , char** )
    {
    return 0;
    }
    为什么要 extern “C” ?如果问这个问题,我不想说太多,这是C++的基础。要在 C++ 里使用一段 C 的代码,必须要用 extern “C” 括起来。C++跟 C虽然语法上有重叠,但是它们是两个不同的东西,内存里的布局是完全不同的,在C++编译器里不用extern “C”括起C代码,会导致编译器不知道该如何为 C 代码描述内存布局。
    可能在 sqlite3.c 里人家已经把整段代码都 extern “C” 括起来了,但是你遇到一个 .c 文件就自觉的再括一次,也没什么不好。
    基本工程就这样建立起来了。编译,可以通过。但是有一堆的 warning。可以不管它。
    三、       SQLITE操作入门
    sqlite提供的是一些C函数接口,你可以用这些函数操作数据库。通过使用这些接口,传递一些标准 sql 语句(以 char * 类型)给 sqlite 函数,sqlite 就会为你操作数据库。
    sqlite 跟MS的access一样是文件型数据库,就是说,一个数据库就是一个文件,此数据库里可以建立很多的表,可以建立索引、触发器等等,但是,它实际上得到的就是一个文件。备份这个文件就备份了整个数据库。
    sqlite 不需要任何数据库引擎,这意味着如果你需要 sqlite 来保存一些用户数据,甚至都不需要安装数据库(如果你做个小软件还要求人家必须装了sqlserver 才能运行,那也太黑心了)。
    下面开始介绍数据库基本操作。
    (1)   基本流程
    i.1            关键数据结构
    sqlite 里最常用到的是 sqlite3 * 类型。从数据库打开开始,sqlite就要为这个类型准备好内存,直到数据库关闭,整个过程都需要用到这个类型。当数据库打开时开始,这个类型的变量就代表了你要操作的数据库。下面再详细介绍。
    i.2            打开数据库
    int sqlite3_open( 文件名, sqlite3 ** );
    用这个函数开始数据库操作。
    需要传入两个参数,一是数据库文件名,比如:c:\DongChunGuang_Database.db。
    文件名不需要一定存在,如果此文件不存在,sqlite 会自动建立它。如果它存在,就尝试把它当数据库文件来打开。
    sqlite3 ** 参数即前面提到的关键数据结构。这个结构底层细节如何,你不要关它。
    函数返回值表示操作是否正确,如果是 SQLITE_OK 则表示操作正常。相关的返回值sqlite定义了一些宏。具体这些宏的含义可以参考 sqlite3.h 文件。里面有详细定义(顺便说一下,sqlite3 的代码注释率自称是非常高的,实际上也的确很高。只要你会看英文,sqlite 可以让你学到不少东西)。
    下面介绍关闭数据库后,再给一段参考代码。
    i.3            关闭数据库
    int sqlite3_close(sqlite3 *);
    前面如果用 sqlite3_open 开启了一个数据库,结尾时不要忘了用这个函数关闭数据库。
    下面给段简单的代码:
    extern "C"
    {
    #include "./sqlite3.h"
    };

    int main( int , char** )
    {
        sqlite3 * db = NULL; //声明sqlite关键结构指针
        int result;
    //打开数据库
    //需要传入 db 这个指针的指针,因为 sqlite3_open 函数要为这个指针分配内存,还要让db指针指向这个内存区
        result = sqlite3_open( “c:\Dcg_database.db”, &db );
        if( result != SQLITE_OK )
        {
         //数据库打开失败
    return -1;
    }
    //数据库操作代码
    //数据库打开成功
    //关闭数据库
    sqlite3_close( db );
    return 0;
    }
    这就是一次数据库操作过程。
    (2)              SQL语句操作
    本节介绍如何用sqlite 执行标准 sql 语法。
    i.1            执行sql语句
    int sqlite3_exec(sqlite3*, const char *sql, sqlite3_callback, void *, char **errmsg );
    这就是执行一条 sql 语句的函数。
    第1个参数不再说了,是前面open函数得到的指针。说了是关键数据结构。
    第2个参数const char *sql 是一条 sql 语句,以结尾。
    第3个参数sqlite3_callback 是回调,当这条语句执行之后,sqlite3会去调用你提供的这个函数。(什么是回调函数,自己找别的资料学习)
    第4个参数void *                 是你所提供的指针,你可以传递任何一个指针参数到这里,这个参数最终会传到回调函数里面,如果不需要传递指针给回调函数,可以填NULL。等下我们再看回调函数的写法,以及这个参数的使用。
    第5个参数char ** errmsg 是错误信息。注意是指针的指针。sqlite3里面有很多固定的错误信息。执行 sqlite3_exec 之后,执行失败时可以查阅这个指针(直接 printf(“%s ”,errmsg))得到一串字符串信息,这串信息告诉你错在什么地方。sqlite3_exec函数通过修改你传入的指针的指针,把你提供的指针指向错误提示信息,这样sqlite3_exec函数外面就可以通过这个 char*得到具体错误提示。
    说明:通常,sqlite3_callback 和它后面的 void * 这两个位置都可以填 NULL。填NULL表示你不需要回调。比如你做 insert 操作,做 delete 操作,就没有必要使用回调。而当你做 select 时,就要使用回调,因为 sqlite3 把数据查出来,得通过回调告诉你查出了什么数据。
    i.2            exec 的回调
    typedef int (*sqlite3_callback)(void*,int,char**, char**);
    你的回调函数必须定义成上面这个函数的类型。下面给个简单的例子:
    //sqlite3的回调函数        
    // sqlite 每查到一条记录,就调用一次这个回调
    int LoadMyInfo( void * para, int n_column, char ** column_value, char ** column_name )
    {
              //para是你在 sqlite3_exec 里传入的 void * 参数
              //通过para参数,你可以传入一些特殊的指针(比如类指针、结构指针),然后在这里面强制转换成对应的类型(这里面是void*类型,必须强制转换成你的类型才可用)。然后操作这些数据
              //n_column是这一条记录有多少个字段 (即这条记录有多少列)
              // char ** column_value 是个关键值,查出来的数据都保存在这里,它实际上是个1维数组(不要以为是2维数组),每一个元素都是一个 char * 值,是一个字段内容(用字符串来表示,以结尾)
              //char ** column_name 跟 column_value是对应的,表示这个字段的字段名称
              //这里,我不使用 para 参数。忽略它的存在.
              int i;
         printf( “记录包含 %d 个字段 ”, n_column );
         for( i = 0 ; i < n_column; i ++ )
         {
              printf( “字段名:%s ?> 字段值:%s ”, column_name[i], column_value[i] );
         }
         printf( “------------------ “ );        
              return 0;
    }

    int main( int , char ** )
    {
              sqlite3 * db;
              int result;
              char * errmsg = NULL;

              result = sqlite3_open( “c:\Dcg_database.db”, &db );
              if( result != SQLITE_OK )
              {
           //数据库打开失败
         return -1;
         }

    //数据库操作代码
    //创建一个测试表,表名叫 MyTable_1,有2个字段: ID 和 name。其中ID是一个自动增加的类型,以后insert时可以不去指定这个字段,它会自己从0开始增加
    result = sqlite3_exec( db, “create table MyTable_1( ID integer primary key autoincrement, name nvarchar(32) )”, NULL, NULL, errmsg );
    if(result != SQLITE_OK )
    {
    printf( “创建表失败,错误码:%d,错误原因:%s ”, result, errmsg );
    }
    //插入一些记录
    result = sqlite3_exec( db, “insert into MyTable_1( name ) values ( ‘走路’ )”, 0, 0, errmsg );
    if(result != SQLITE_OK )
    {
    printf( “插入记录失败,错误码:%d,错误原因:%s ”, result, errmsg );
    }
    //开始查询数据库
    result = sqlite3_exec( db, “select * from MyTable_1”, LoadMyInfo, NULL, errmsg );
    //关闭数据库
    sqlite3_close( db );
    return 0;

    }

    通过上面的例子,应该可以知道如何打开一个数据库,如何做数据库基本操作。
    有这些知识,基本上可以应付很多数据库操作了。
    i.3            不使用回调查询数据库
    上面介绍的 sqlite3_exec 是使用回调来执行 select 操作。还有一个方法可以直接查询而不需要回调。但是,我个人感觉还是回调好,因为代码可以更加整齐,只不过用回调很麻烦,你得声明一个函数,如果这个函数是类成员函数,你还不得不把它声明成 static 的(要问为什么?这又是C++基础了。C++成员函数实际上隐藏了一个参数:this,C++调用类的成员函数的时候,隐含把类指针当成函数的第一个参数传递进去。结果,这造成跟前面说的 sqlite 回调函数的参数不相符。只有当把成员函数声明成 static 时,它才没有多余的隐含的this参数)。
    虽然回调显得代码整齐,但有时候你还是想要非回调的 select 查询。这可以通过 sqlite3_get_table 函数做到。
    int sqlite3_get_table(sqlite3*, const char *sql, char ***resultp, int *nrow, int *ncolumn, char **errmsg );
    第1个参数不再多说,看前面的例子。
    第2个参数是 sql 语句,跟 sqlite3_exec 里的 sql 是一样的。是一个很普通的以结尾的char *字符串。
    第3个参数是查询结果,它依然一维数组(不要以为是二维数组,更不要以为是三维数组)。它内存布局是:第一行是字段名称,后面是紧接着是每个字段的值。下面用例子来说事。
    第4个参数是查询出多少条记录(即查出多少行)。
    第5个参数是多少个字段(多少列)。
    第6个参数是错误信息,跟前面一样,这里不多说了。
    下面给个简单例子:
    int main( int , char ** )
    {
              sqlite3 * db;
              int result;
              char * errmsg = NULL;

        char **dbResult; //是 char ** 类型,两个*号

              int nRow, nColumn;
              int i , j;
              int index;

              result = sqlite3_open( “c:\Dcg_database.db”, &db );
        if( result != SQLITE_OK )
        {
         //数据库打开失败
    return -1;
    }

    //数据库操作代码
    //假设前面已经创建了 MyTable_1 表
    //开始查询,传入的 dbResult 已经是 char **,这里又加了一个 & 取地址符,传递进去的就成了 char ***
    result = sqlite3_get_table( db, “select * from MyTable_1”, &dbResult, &nRow, &nColumn, &errmsg );
    if( SQLITE_OK == result )
    {
         //查询成功
        index = nColumn; //前面说过 dbResult 前面第一行数据是字段名称,从 nColumn 索引开始才是真正的数据
         printf( “查到%d条记录 ”, nRow );

         for( i = 0; i < nRow ; i++ )
         {
             printf( “第 %d 条记录 ”, i+1 );
             for( j = 0 ; j < nColumn; j++ )
             {
                  printf( “字段名:%s ?> 字段值:%s ”, dbResult[j], dbResult [index] );
                  ++index; // dbResult 的字段值是连续的,从第0索引到第 nColumn - 1索引都是字段名称,从第 nColumn 索引开始,后面都是字段值,它把一个二维的表(传统的行列表示法)用一个扁平的形式来表示
             }
             printf( “------- ” );

         }
    }

    //到这里,不论数据库查询是否成功,都释放 char** 查询结果,使用 sqlite 提供的功能来释放
    sqlite3_free_table( dbResult );
    //关闭数据库
    sqlite3_close( db );

    return 0;
    }

    到这个例子为止,sqlite3 的常用用法都介绍完了。
    用以上的方法,再配上 sql 语句,完全可以应付绝大多数数据库需求。
    但有一种情况,用上面方法是无法实现的:需要insert、select 二进制。当需要处理二进制数据时,上面的方法就没办法做到。下面这一节说明如何插入二进制数据
    (2)              操作二进制
    sqlite 操作二进制数据需要用一个辅助的数据类型:sqlite3_stmt * 。
    这个数据类型记录了一个“sql语句”。为什么我把 “sql语句” 用双引号引起来?因为你可以把 sqlite3_stmt * 所表示的内容看成是 sql语句,但是实际上它不是我们所熟知的sql语句。它是一个已经把sql语句解析了的、用sqlite自己标记记录的内部数据结构。
    正因为这个结构已经被解析了,所以你可以往这个语句里插入二进制数据。当然,把二进制数据插到 sqlite3_stmt 结构里可不能直接 memcpy ,也不能像 std::string 那样用 + 号。必须用 sqlite 提供的函数来插入。
    i.1          写入二进制
    下面说写二进制的步骤。
    要插入二进制,前提是这个表的字段的类型是 blob 类型。我假设有这么一张表:
    create table Tbl_2( ID integer, file_content blob )
    首先声明
    sqlite3_stmt * stat;
    然后,把一个 sql 语句解析到 stat 结构里去:
    sqlite3_prepare( db, “insert into Tbl_2( ID, file_content) values( 10, ? )”, -1, &stat, 0 );
    上面的函数完成 sql 语句的解析。第一个参数跟前面一样,是个 sqlite3 * 类型变量,第二个参数是一个 sql 语句。
    这个 sql 语句特别之处在于 values 里面有个 ? 号。在sqlite3_prepare函数里,?号表示一个未定的值,它的值等下才插入。
    第三个参数我写的是-1,这个参数含义是前面 sql 语句的长度。如果小于0,sqlite会自动计算它的长度(把sql语句当成以结尾的字符串)。
    第四个参数是 sqlite3_stmt 的指针的指针。解析以后的sql语句就放在这个结构里。
    第五个参数我也不知道是干什么的。为0就可以了。
    如果这个函数执行成功(返回值是 SQLITE_OK 且 stat 不为NULL ),那么下面就可以开始插入二进制数据。
    sqlite3_bind_blob( stat, 1, pdata, (int)(length_of_data_in_bytes), NULL ); // pdata为数据缓冲区,length_of_data_in_bytes为数据大小,以字节为单位
    这个函数一共有5个参数。
    第1个参数:是前面prepare得到的 sqlite3_stmt * 类型变量。
    第2个参数:?号的索引。前面prepare的sql语句里有一个?号,假如有多个?号怎么插入?方法就是改变 bind_blob 函数第2个参数。这个参数我写1,表示这里插入的值要替换 stat 的第一个?号(这里的索引从1开始计数,而非从0开始)。如果你有多个?号,就写多个 bind_blob 语句,并改变它们的第2个参数就替换到不同的?号。如果有?号没有替换,sqlite为它取值null。
    第3个参数:二进制数据起始指针。
    第4个参数:二进制数据的长度,以字节为单位。
    第5个参数:是个析够回调函数,告诉sqlite当把数据处理完后调用此函数来析够你的数据。这个参数我还没有使用过,因此理解也不深刻。但是一般都填NULL,需要释放的内存自己用代码来释放。
    bind完了之后,二进制数据就进入了你的“sql语句”里了。你现在可以把它保存到数据库里:
    int result = sqlite3_step( stat );
    通过这个语句,stat 表示的sql语句就被写到了数据库里。
    最后,要把 sqlite3_stmt 结构给释放:
    sqlite3_finalize( stat ); //把刚才分配的内容析构掉
    i.2          读出二进制
    下面说读二进制的步骤。
    跟前面一样,先声明 sqlite3_stmt * 类型变量:
    sqlite3_stmt * stat;
    然后,把一个 sql 语句解析到 stat 结构里去:
    sqlite3_prepare( db, “select * from Tbl_2”, -1, &stat, 0 );
    当 prepare 成功之后(返回值是 SQLITE_OK ),开始查询数据。
    int result = sqlite3_step( stat );
    这一句的返回值是 SQLITE_ROW 时表示成功(不是 SQLITE_OK )。
    你可以循环执行 sqlite3_step 函数,一次 step 查询出一条记录。直到返回值不为 SQLITE_ROW 时表示查询结束。
    然后开始获取第一个字段:ID 的值。ID是个整数,用下面这个语句获取它的值:
    int id = sqlite3_column_int( stat, 0 ); //第2个参数表示获取第几个字段内容,从0开始计算,因为我的表的ID字段是第一个字段,因此这里我填0
    下面开始获取 file_content 的值,因为 file_content 是二进制,因此我需要得到它的指针,还有它的长度:
             const void * pFileContent = sqlite3_column_blob( stat, 1 );
             int len = sqlite3_column_bytes( stat, 1 );
    这样就得到了二进制的值。
    把 pFileContent 的内容保存出来之后,不要忘了释放 sqlite3_stmt 结构:
    sqlite3_finalize( stat ); //把刚才分配的内容析构掉
    i.3          重复使用 sqlite3_stmt 结构
    如果你需要重复使用 sqlite3_prepare 解析好的 sqlite3_stmt 结构,需要用函数: sqlite3_reset。
    result = sqlite3_reset(stat);
    这样, stat 结构又成为 sqlite3_prepare 完成时的状态,你可以重新为它 bind 内容。
    (4)              事务处理
    sqlite 是支持事务处理的。如果你知道你要同步删除很多数据,不仿把它们做成一个统一的事务。
    通常一次 sqlite3_exec 就是一次事务,如果你要删除1万条数据,sqlite就做了1万次:开始新事务->删除一条数据->提交事务->开始新事务->… 的过程。这个操作是很慢的。因为时间都花在了开始事务、提交事务上。
    你可以把这些同类操作做成一个事务,这样如果操作错误,还能够回滚事务。
    事务的操作没有特别的接口函数,它就是一个普通的 sql 语句而已:
    分别如下:
    int result; 
    result = sqlite3_exec( db, "begin transaction", 0, 0, &zErrorMsg ); //开始一个事务
    result = sqlite3_exec( db, "commit transaction", 0, 0, &zErrorMsg ); //提交事务
    result = sqlite3_exec( db, "rollback transaction", 0, 0, &zErrorMsg ); //回滚事务
    四、       给数据库加密
    前面所说的内容网上已经有很多资料,虽然比较零散,但是花点时间也还是可以找到的。现在要说的这个——数据库加密,资料就很难找。也可能是我操作水平不够,找不到对应资料。但不管这样,我还是通过网上能找到的很有限的资料,探索出了给sqlite数据库加密的完整步骤。
    这里要提一下,虽然 sqlite 很好用,速度快、体积小巧。但是它保存的文件却是明文的。若不信可以用 NotePad 打开数据库文件瞧瞧,里面 insert 的内容几乎一览无余。这样赤裸裸的展现自己,可不是我们的初衷。当然,如果你在嵌入式系统、智能手机上使用 sqlite,最好是不加密,因为这些系统运算能力有限,你做为一个新功能提供者,不能把用户有限的运算能力全部花掉。
    Sqlite为了速度而诞生。因此Sqlite本身不对数据库加密,要知道,如果你选择标准AES算法加密,那么一定有接近50%的时间消耗在加解密算法上,甚至更多(性能主要取决于你算法编写水平以及你是否能使用cpu提供的底层运算能力,比如MMX或sse系列指令可以大幅度提升运算速度)。
    Sqlite免费版本是不提供加密功能的,当然你也可以选择他们的收费版本,那你得支付2000块钱,而且是USD(美元)。我这里也不是说支付钱不好,如果只为了数据库加密就去支付2000块,我觉得划不来。因为下面我将要告诉你如何为免费的Sqlite扩展出加密模块——自己动手扩展,这是Sqlite允许,也是它提倡的。
    那么,就让我们一起开始为 sqlite3.c 文件扩展出加密模块。
    i.1          必要的宏
    通过阅读 Sqlite 代码(当然没有全部阅读完,6万多行代码,没有一行是我习惯的风格,我可没那么多眼神去看),我搞清楚了两件事:
    Sqlite是支持加密扩展的;
    需要 #define 一个宏才能使用加密扩展。
    这个宏就是 SQLITE_HAS_CODEC。
    你在代码最前面(也可以在 sqlite3.h 文件第一行)定义:
    #ifndef SQLITE_HAS_CODEC
    #define SQLITE_HAS_CODEC
    #endif
    如果你在代码里定义了此宏,但是还能够正常编译,那么应该是操作没有成功。因为你应该会被编译器提示有一些函数无法链接才对。如果你用的是 VC 2003,你可以在“解决方案”里右键点击你的工程,然后选“属性”,找到“C/C++”,再找到“命令行”,在里面手工添加“/D "SQLITE_HAS_CODEC"”。
    定义了这个宏,一些被 Sqlite 故意屏蔽掉的代码就被使用了。这些代码就是加解密的接口。
    尝试编译,vc会提示你有一些函数无法链接,因为找不到他们的实现。
    如果你也用的是VC2003,那么会得到下面的提示:
    error LNK2019: 无法解析的外部符号 _sqlite3CodecGetKey ,该符号在函数 _attachFunc 中被引用
    error LNK2019: 无法解析的外部符号 _sqlite3CodecAttach ,该符号在函数 _attachFunc 中被引用
    error LNK2019: 无法解析的外部符号 _sqlite3_activate_see ,该符号在函数 _sqlite3Pragma 中被引用
    error LNK2019: 无法解析的外部符号 _sqlite3_key ,该符号在函数 _sqlite3Pragma 中被引用
    fatal error LNK1120: 4 个无法解析的外部命令
    这是正常的,因为Sqlite只留了接口而已,并没有给出实现。
    下面就让我来实现这些接口。 
    i.2          自己实现加解密接口函数
    如果真要我从一份 www.sqlite.org 网上down下来的 sqlite3.c 文件,直接摸索出这些接口的实现,我认为我还没有这个能力。
    好在网上还有一些代码已经实现了这个功能。通过参照他们的代码以及不断编译中vc给出的错误提示,最终我把整个接口整理出来。
    实现这些预留接口不是那么容易,要重头说一次怎么回事很困难。我把代码都写好了,直接把他们按我下面的说明拷贝到 sqlite3.c 文件对应地方即可。我在下面也提供了sqlite3.c 文件,可以直接参考或取下来使用。
    这里要说一点的是,我另外新建了两个文件:crypt.c和crypt.h。
    其中crypt.h如此定义:
    #ifndef DCG_SQLITE_CRYPT_FUNC_
    #define DCG_SQLITE_CRYPT_FUNC_
    /***********
    董淳光写的 SQLITE 加密关键函数库
    ***********/
    /***********
    关键加密函数
    ***********/
    int My_Encrypt_Func( unsigned char * pData, unsigned int data_len, const char * key, unsigned int len_of_key );
    /***********
    关键解密函数
    ***********/
    int My_DeEncrypt_Func( unsigned char * pData, unsigned int data_len, const char * key, unsigned int len_of_key );
    #endif
    其中的 crypt.c 如此定义:
    #include "./crypt.h"
    #include "memory.h"
    /***********
    关键加密函数
    ***********/
    int My_Encrypt_Func( unsigned char * pData, unsigned int data_len, const char * key, unsigned int len_of_key )
    {
    return 0;
    }
    /***********
    关键解密函数
    ***********/
    int My_DeEncrypt_Func( unsigned char * pData, unsigned int data_len, const char * key, unsigned int len_of_key )
    {
    return 0;
    }
    这个文件很容易看,就两函数,一个加密一个解密。传进来的参数分别是待处理的数据、数据长度、密钥、密钥长度。
    处理时直接把结果作用于 pData 指针指向的内容。
    你需要定义自己的加解密过程,就改动这两个函数,其它部分不用动。扩展起来很简单。
    这里有个特点,data_len 一般总是 1024 字节。正因为如此,你可以在你的算法里使用一些特定长度的加密算法,比如AES要求被加密数据一定是128位(16字节)长。这个1024不是碰巧,而是 Sqlite 的页定义是1024字节,在sqlite3.c文件里有定义:
    # define SQLITE_DEFAULT_PAGE_SIZE 1024
    你可以改动这个值,不过还是建议没有必要不要去改它。
    上面写了两个扩展函数,如何把扩展函数跟 Sqlite 挂接起来,这个过程说起来比较麻烦。我直接贴代码。
    分3个步骤。
    首先,在 sqlite3.c 文件顶部,添加下面内容:
    #ifdef SQLITE_HAS_CODEC
    #include "./crypt.h"
    /***********
    用于在 sqlite3 最后关闭时释放一些内存
    ***********/
    void sqlite3pager_free_codecarg(void *pArg);
    #endif
    这个函数之所以要在 sqlite3.c 开头声明,是因为下面在 sqlite3.c 里面某些函数里要插入这个函数调用。所以要提前声明。

    i.3          加密使用方法:
    现在,你代码已经有了加密功能。
    你要把加密功能给用上,除了改 sqlite3.c 文件、给你工程添加 SQLITE_HAS_CODEC 宏,还得修改你的数据库调用函数。
    前面提到过,要开始一个数据库操作,必须先 sqlite3_open 。
    加解密过程就在 sqlite3_open 后面操作。
    假设你已经 sqlite3_open 成功了,紧接着写下面的代码:
          int i;
    //添加、使用密码      
          i = sqlite3_key( db, "dcg", 3 );
          //修改密码
          i = sqlite3_rekey( db, "dcg", 0 );
    用 sqlite3_key 函数来提交密码。
    第1个参数是 sqlite3 * 类型变量,代表着用 sqlite3_open 打开的数据库(或新建数据库)。
    第2个参数是密钥。
    第3个参数是密钥长度。
    用 sqlite3_rekey 来修改密码。参数含义同 sqlite3_key。
    实际上,你可以在sqlite3_open函数之后,到 sqlite3_close 函数之前任意位置调用 sqlite3_key 来设置密码。
    但是如果你没有设置密码,而数据库之前是有密码的,那么你做任何操作都会得到一个返回值:SQLITE_NOTADB,并且得到错误提示:“file is encrypted or is not a database”。
    只有当你用 sqlite3_key 设置了正确的密码,数据库才会正常工作。
    如果你要修改密码,前提是你必须先 sqlite3_open 打开数据库成功,然后 sqlite3_key 设置密钥成功,之后才能用 sqlite3_rekey 来修改密码。
    如果数据库有密码,但你没有用 sqlite3_key 设置密码,那么当你尝试用 sqlite3_rekey 来修改密码时会得到 SQLITE_NOTADB 返回值。
    如果你需要清空密码,可以使用:
    //修改密码
          i = sqlite3_rekey( db, NULL, 0 );
    来完成密码清空功能。

    ==== 之后的原文代码我就不转了,发一下我自己根据原文代码实践的 ====

    (小小:下面的代码,是我自己的实践,适用于sqlite3 v3.8.7):

      1 /************************************************/
      2 /*                                              */
      3 /*                    encryption                */
      4 /*                                              */
      5 /************************************************/
      6 #ifdef SQLITE_HAS_CODEC/*这个宏,开启了加解密的代码*/
      7 #include <crypt_gb.h> /*此头文件是加密库的头文件,其中包含加解密函数*/
      8 #define DB_KEY_LENGTH_BYTE   16   /*密钥长度 unused*/
      9 #define DB_KEY_PADDING       0x33 /*密钥位数不足时补充的字符 unused*/
     10 #define CRYPT_OFFSET 8
     11 #define DATA_TO_PGHDR(D)  ((unsigned long)(D)-sizeof(void *))/*由PgHdr结构中的pData成员找到PgHdr结构指针*/
     12 typedef unsigned char BYTE;
     13 
     14 /***加密结构***/
     15 typedef struct _CryptBlock
     16 {
     17     BYTE*     ReadKey;     // 读数据库和写入事务的密钥
     18     BYTE*     WriteKey;    // 写入数据库的密钥
     19     int       PageSize;    // 页的大小
     20     BYTE*     Data;
     21 } CryptBlock, *LPCryptBlock;
     22 
     23 /********************
     24 THE NEW FUNCTIONS
     25 *********************/
     26 void sqlite3pager_free_codecarg(void *pArg);
     27 int sqlite3_key_interop(sqlite3 *db, const void *pKey, int nKeySize);
     28 int sqlite3_rekey_interop(sqlite3 *db, const void *pKey, int nKeySize);
     29 int sqlite3CodecAttach(sqlite3 *db, int nDb, const void *pKey, int nKeyLen);
     30 int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey);
     31 int sqlite3_key(sqlite3 *db, const void *pKey, int nKey);
     32 void sqlite3pager_set_codec(Pager *pPager,void *(*xCodec)(void*,void*,Pgno,int),void *pCodec);
     33 static LPCryptBlock CreateCryptBlock(unsigned char* hKey, Pager *pager, LPCryptBlock pExisting);
     34 static unsigned char * DeriveKey(const void *pKey, int nKeyLen);
     35 static void * sqlite3pager_get_codecarg(Pager *pPager);
     36 static void DestroyCryptBlock(LPCryptBlock pBlock);
     37 void * sqlite3Codec(void *pArg, unsigned char **data_addr, Pgno nPageNum, int nMode);
     38 void sqlite3_activate_see(const char* right );
     39 void sqlite3CodecGetKey(sqlite3* db, int nDB, void** Key, int* nKey);
     40 
     41 
     42 
     43 /***********
     44 这里有个特点,data_len 一般总是 1024 字节。正因为如此,你可以在你的算法里使用一些特定长度的加密算法,
     45 比如AES要求被加密数据一定是128位(16字节)长。这个1024不是碰巧,而是 Sqlite 的页定义是1024字节,在sqlite3.c文件里有定义:
     46 # define SQLITE_DEFAULT_PAGE_SIZE 1024
     47 你可以改动这个值,不过还是建议没有必要不要去改它。
     48 
     49 关键加密函数
     50 ***********/
     51 
     52 int My_Encrypt_Func( unsigned char * pData, unsigned int data_len, const char * key, unsigned int len_of_key )
     53 {
     54     len_of_key = strlen(key);
     55     logg("&[My_Encrypt_Func]called.key=%s;keylen=%d
    ", key, len_of_key);
     56     /*
     57     int i;
     58     for (i = 0; i < data_len; i ++){
     59         pData[i] += 1;
     60     }
     61     */
     62     int rc = 0;
     63     int ret = 0;
     64     
     65     sms4_context ctx;
     66     memset(&ctx, 0, sizeof(ctx));
     67     INT32U *dest = NULL;//free
     68     INT32U destlen = 0;
     69     
     70     crypt_gb_sms4_start(&ctx, key, len_of_key, 1);//初始化加解密的密钥,保存在ctx结构中
     71     rc = crypt_gb_sms4_encrypt(&ctx, pData, data_len, &dest, &destlen);//data_len=1024;destlen=260(32u)=1040(8u)
     72         
     73     if (rc == 0 && destlen == 260) {//这里的数据长度都是按照data_len=1024设置的
     74         //logg("&[sqlite3:My_Encrypt_Func]crypt_gb_sms4_encrypt SUCCESS.
    ");
     75         memcpy(pData, dest, destlen*4 - 16);
     76     }else {
     77         logg("![sqlite3:My_Encrypt_Func]crypt_gb_sms4_encrypt error.(rc=%d;destlen=%d)
    ", rc, destlen);
     78         ret = -1;
     79         goto out;
     80     }
     81 out:
     82     if (dest) {free(dest); dest = NULL;}
     83     return ret;
     84 }
     85 
     86 /***********
     87 关键解密函数
     88 ***********/
     89 int My_DeEncrypt_Func( unsigned char * pData, unsigned int data_len, const char * key, unsigned int len_of_key )
     90 {
     91     len_of_key = strlen(key);
     92     logg("&[My_DeEncrypt_Func]called.key=%s;keylen=%d
    ", key, len_of_key);
     93     /*
     94     int i;
     95     for (i = 0; i < data_len; i ++){
     96         pData[i] -= 1;
     97     }
     98     */
     99     int rc = 0;
    100     int ret = 0;
    101     
    102     sms4_context ctx;
    103     memset(&ctx, 0, sizeof(ctx));
    104     INT32U src[260];
    105     memset(src, 0, sizeof(src));
    106     INT8U *dest = NULL;
    107     INT32U *dest_tail = NULL;
    108     INT32U destlen = 0;
    109     
    110     unsigned char tail_str[16];
    111     memset(tail_str, 16, sizeof(tail_str));
    112     
    113     crypt_gb_sms4_start(&ctx, key, len_of_key, 1);
    114     rc = crypt_gb_sms4_encrypt(&ctx, tail_str, sizeof(tail_str), &dest_tail, &destlen);//destlen=8(32u)=32(8u)
    115     if (rc != 0){
    116         logg("![sqlite3:My_DeEncrypt_Func]crypt_gb_sms4_encrypt(tail) error.
    ");
    117         ret = -1;
    118         goto out;
    119     }
    120     memcpy(src, pData, data_len);
    121     memcpy(src + data_len/4, dest_tail, 16);
    122     rc = crypt_gb_sms4_decrypt(&ctx, src, sizeof(src), &dest, &destlen);
    123     if (rc != 0){
    124         logg("![sqlite3:My_DeEncrypt_Func]crypt_gb_sms4_decrypt error.
    ");
    125         ret = -1;
    126         goto out;
    127     }
    128     memcpy(pData, dest, data_len);
    129 out:
    130     if (dest) {free(dest); dest = NULL;}
    131     if (dest_tail) {free(dest_tail); dest_tail = NULL;}
    132     return ret;
    133 }
    134 
    135 void sqlite3CodecGetKey(sqlite3* db, int nDB, void** Key, int* nKey)
    136 {
    137     return;
    138 }
    139 void sqlite3_activate_see(const char* right )
    140 {   
    141     return;
    142 }
    143 //sqlite3Codec
    144 //加密/解密函数, 被pager调用, 在sqlite3.c中以CODEC1, CODEC2宏调用,这个宏已根据下面函数的参数进行修改:
    145 void * sqlite3Codec(void *pArg, unsigned char **data_addr/*这里传地址,是为了找到PgHdr结构指针*/, Pgno nPageNum, int nMode)
    146 {
    147     LPCryptBlock pBlock = (LPCryptBlock)pArg;
    148     unsigned char *data;
    149     data = *data_addr;
    150     unsigned int dwPageSize = 0;
    151 
    152     if (!pBlock) return data;
    153     //确保pager的页长度和加密块的页长度相等.如果改变,就需要调整.
    154     if (nMode != 2)
    155     {
    156         PgHdr *pageHeader;
    157         pageHeader = DATA_TO_PGHDR(data_addr);
    158         if (pageHeader->pPager->pageSize != pBlock->PageSize)
    159         {
    160             CreateCryptBlock(0, pageHeader->pPager, pBlock);
    161         }
    162     }
    163 
    164     switch(nMode)
    165     {
    166         case 0: // Undo a "case 7" journal file encryption
    167         case 2: //重载一个页
    168         case 3: //载入一个页
    169             if (!pBlock->ReadKey) break;
    170             dwPageSize = pBlock->PageSize;
    171             if (My_DeEncrypt_Func(data, dwPageSize, pBlock->ReadKey, DB_KEY_LENGTH_BYTE ) != 0) /*调用我的解密函数*/
    172                 exit(1);
    173             break;
    174         case 6: //加密一个主数据库文件的页
    175             if (!pBlock->WriteKey) break;
    176             memcpy(pBlock->Data + CRYPT_OFFSET, data, pBlock->PageSize);
    177             data = pBlock->Data + CRYPT_OFFSET;
    178             dwPageSize = pBlock->PageSize;
    179             if (My_Encrypt_Func(data , dwPageSize, pBlock->WriteKey, DB_KEY_LENGTH_BYTE ) != 0)/*调用我的加密函数*/
    180                 exit(1);
    181             break;
    182         case 7: //加密事务文件的页
    183             /*在正常环境下, 读密钥和写密钥相同. 当数据库是被重新加密的,读密钥和写密钥未必相同.
    184             回滚事务必要用数据库文件的原始密钥写入.因此,当一次回滚被写入,总是用数据库的读密钥,
    185             这是为了保证与读取原始数据的密钥相同.
    186             */
    187             if (!pBlock->ReadKey) break;
    188             memcpy(pBlock->Data + CRYPT_OFFSET, data, pBlock->PageSize);
    189             data = pBlock->Data + CRYPT_OFFSET;
    190             dwPageSize = pBlock->PageSize;
    191             if (My_Encrypt_Func( data, dwPageSize, pBlock->ReadKey, DB_KEY_LENGTH_BYTE ) != 0) /*调用我的加密函数*/
    192                 exit(1);
    193             break;
    194     }
    195     return data;
    196 }
    197 
    198 //DestroyCryptBlock
    199 //销毁一个加密块及相关的缓冲区,密钥.
    200 static void DestroyCryptBlock(LPCryptBlock pBlock)
    201 {
    202     //销毁读密钥.
    203     if (pBlock->ReadKey){
    204          sqlite3_free(pBlock->ReadKey);
    205     }
    206     //如果写密钥存在并且不等于读密钥,也销毁.
    207     if (pBlock->WriteKey && pBlock->WriteKey != pBlock->ReadKey){
    208          sqlite3_free(pBlock->WriteKey);
    209     }
    210 
    211     if(pBlock->Data){
    212          sqlite3_free(pBlock->Data);
    213     }
    214     //释放加密块.
    215     sqlite3_free(pBlock);
    216 }
    217 
    218 //support function
    219 static void * sqlite3pager_get_codecarg(Pager *pPager)
    220 {
    221     return (pPager->xCodec) ? pPager->pCodec: NULL;
    222 }
    223 
    224 //DeriveKey
    225 // 从用户提供的缓冲区中得到一个加密密钥
    226 static unsigned char * DeriveKey(const void *pKey, int nKeyLen)
    227 {
    228     return pKey;/*原文中的该函数写的可能有问题,我这里什么都不做,直接返回*/
    229     unsigned char * hKey = NULL;
    230     int j;
    231 
    232     if( pKey == NULL || nKeyLen == 0 )
    233     {
    234          return NULL;
    235     }
    236 
    237     hKey = sqlite3_malloc( DB_KEY_LENGTH_BYTE + 1 );
    238     if( hKey == NULL )
    239     {
    240          return NULL;
    241     }
    242 
    243     hKey[ DB_KEY_LENGTH_BYTE ] = 0;
    244     if( nKeyLen < DB_KEY_LENGTH_BYTE )
    245     {
    246          memcpy( hKey, pKey, nKeyLen ); //先拷贝得到密钥前面的部分
    247          j = DB_KEY_LENGTH_BYTE - nKeyLen;
    248          //补充密钥后面的部分
    249          memset( hKey + nKeyLen, DB_KEY_PADDING, j );
    250     }
    251     else
    252     { //密钥位数已经足够,直接把密钥取过来
    253          memcpy( hKey, pKey, DB_KEY_LENGTH_BYTE );
    254     }
    255     return hKey;
    256 }
    257 
    258 //创建或更新一个页的加密算法索引.此函数会申请缓冲区.
    259 static LPCryptBlock CreateCryptBlock(unsigned char* hKey, Pager *pager, LPCryptBlock pExisting)
    260 {
    261     LPCryptBlock pBlock;
    262 
    263     if (!pExisting) //创建新加密块
    264     {
    265          pBlock = sqlite3_malloc(sizeof(CryptBlock));
    266          memset(pBlock, 0, sizeof(CryptBlock));
    267          pBlock->ReadKey = hKey;
    268          pBlock->WriteKey = hKey;
    269          pBlock->PageSize = pager->pageSize;
    270          pBlock->Data = (unsigned char*)sqlite3_malloc(pBlock->PageSize + CRYPT_OFFSET);
    271     }
    272     else //更新存在的加密块
    273     {
    274          pBlock = pExisting;
    275          if ( pBlock->PageSize != pager->pageSize && !pBlock->Data){
    276               sqlite3_free(pBlock->Data);
    277               pBlock->PageSize = pager->pageSize;
    278               pBlock->Data = (unsigned char*)sqlite3_malloc(pBlock->PageSize + CRYPT_OFFSET);
    279          }
    280 
    281     }
    282     memset(pBlock->Data, 0, pBlock->PageSize + CRYPT_OFFSET);
    283 
    284     return pBlock;
    285 }
    286 
    287 /*
    288 ** Set the codec for this pager
    289 */
    290 void sqlite3pager_set_codec(Pager *pPager,
    291                             void *(*xCodec)(void*,void*,Pgno,int),
    292                             void *pCodec)
    293 {
    294     pPager->xCodec = xCodec;
    295     pPager->pCodec = pCodec;
    296 }
    297 
    298 //main crypt API functions
    299 int sqlite3_key(sqlite3 *db, const void *pKey, int nKey)
    300 {
    301     logg("&[sqlite3_key]called...(key=%s;nkey=%d)
    ", pKey, nKey);/*忽略这个函数吧,我打印日志用的*/
    302     return sqlite3_key_interop(db, pKey, nKey);
    303 }
    304 int sqlite3_key_v2(sqlite3 *db, const char *zDbName, const void *pKey, int nKey)
    305 {
    306     logg("&[sqlite3_key_v2]called...(key=%s;nkey=%d)
    ", pKey, nKey);
    307     return sqlite3_key_interop(db, pKey, nKey);
    308 }
    309 
    310 int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey)
    311 {
    312     logg("&[sqlite3_rekey]called...(key=%s;nkey=%d)
    ", pKey, nKey);
    313     return sqlite3_rekey_interop(db, pKey, nKey);
    314 }
    315 int sqlite3_rekey_v2(sqlite3 *db, const char *zDbName, const void *pKey, int nKey)
    316 {
    317     logg("&[sqlite3_rekey_v2]called...(key=%s;nkey=%d)
    ", pKey, nKey);
    318     return sqlite3_rekey_interop(db, pKey, nKey);
    319 }
    320 
    321 /*被sqlite 和 sqlite3_key_interop 调用, 附加密钥到数据库.*/
    322 int sqlite3CodecAttach(sqlite3 *db, int nDb, const void *pKey, int nKeyLen)
    323 {
    324     int rc = SQLITE_ERROR;
    325     unsigned char* hKey = 0;
    326     
    327     //如果没有指定密匙,可能标识用了主数据库的加密或没加密.
    328     if (!pKey || !nKeyLen)
    329     {
    330         if (!nDb)
    331         {
    332             return SQLITE_OK; //主数据库, 没有指定密钥所以没有加密.
    333         }
    334         else //附加数据库,使用主数据库的密钥.
    335         {
    336             //获取主数据库的加密块并复制密钥给附加数据库使用
    337             //LPCryptBlock pBlock = (LPCryptBlock)sqlite3pager_get_codecarg(sqlite3BtreePager(db->aDb[0].pBt));
    338             LPCryptBlock pBlock = (LPCryptBlock)sqlite3PagerGetCodec(sqlite3BtreePager(db->aDb[0].pBt));
    339             if (!pBlock) return SQLITE_OK; //主数据库没有加密
    340             if (!pBlock->ReadKey) return SQLITE_OK; //没有加密
    341             memcpy(pBlock->ReadKey, &hKey, 16);
    342         }
    343     }
    344     else //用户提供了密码,从中创建密钥.
    345     {
    346         hKey = DeriveKey(pKey, nKeyLen);
    347     }
    348     //创建一个新的加密块,并将解码器指向新的附加数据库.
    349     if (hKey)
    350     {
    351         LPCryptBlock pBlock = CreateCryptBlock(hKey, sqlite3BtreePager(db->aDb[nDb].pBt), NULL);
    352         //sqlite3pager_set_codec(sqlite3BtreePager(db->aDb[nDb].pBt), sqlite3Codec, pBlock);
    353         sqlite3PagerSetCodec(sqlite3BtreePager(db->aDb[nDb].pBt), sqlite3Codec, NULL, sqlite3pager_free_codecarg, pBlock);
    354         rc = SQLITE_OK;
    355     }
    356     return rc;
    357 }
    358 
    359 // Changes the encryption key for an existing database.
    360 int sqlite3_rekey_interop(sqlite3 *db, const void *pKey, int nKeySize)
    361 {
    362     printf("[sqlite3_rekey_interop]called...
    ");
    363     Btree *pbt = db->aDb[0].pBt;
    364     Pager *p = sqlite3BtreePager(pbt);
    365     //LPCryptBlock pBlock = (LPCryptBlock)sqlite3pager_get_codecarg(p);
    366     LPCryptBlock pBlock = (LPCryptBlock)sqlite3PagerGetCodec(p);
    367     unsigned char * hKey = DeriveKey(pKey, nKeySize);
    368     int rc = SQLITE_ERROR;
    369     if (!pBlock && !hKey) return SQLITE_OK;
    370 
    371     //重新加密一个数据库,改变pager的写密钥, 读密钥依旧保留.
    372     if (!pBlock) //加密一个未加密的数据库
    373     {
    374         pBlock = CreateCryptBlock(hKey, p, NULL);
    375         pBlock->ReadKey = 0; //原始数据库未加密
    376         //sqlite3pager_set_codec(sqlite3BtreePager(pbt), sqlite3Codec, pBlock);
    377         sqlite3PagerSetCodec(sqlite3BtreePager(pbt), sqlite3Codec, NULL, sqlite3pager_free_codecarg, pBlock);
    378     }
    379     else //改变已加密数据库的写密钥
    380     {
    381         pBlock->WriteKey = hKey;
    382     }
    383 
    384     sqlite3_mutex_enter(pbt->db->mutex);
    385     //开始一个事务
    386     rc = sqlite3BtreeBeginTrans(pbt, 1);
    387     if (!rc)
    388     {
    389         //用新密钥重写所有的页到数据库。
    390         Pgno nPage = 0;
    391         sqlite3PagerPagecount(p, &nPage);
    392         Pgno nSkip = PAGER_MJ_PGNO(p);
    393         void *pPage;
    394         Pgno n;
    395 
    396         for(n = 1; rc == SQLITE_OK && n <= nPage; n ++)
    397         {
    398             if (n == nSkip) continue;
    399             rc = sqlite3PagerGet(p, n, &pPage);
    400             if(!rc)
    401             {
    402                 rc = sqlite3PagerWrite(pPage);
    403                 sqlite3PagerUnref(pPage);
    404             }
    405         }
    406     }
    407     //如果成功,提交事务。
    408     if (!rc)
    409     {
    410         rc = sqlite3BtreeCommit(pbt);
    411     }
    412 
    413     //如果失败,回滚。
    414     if (rc)
    415     {
    416         sqlite3BtreeRollback(pbt, SQLITE_OK);
    417     }
    418 
    419     //如果成功,销毁先前的读密钥。并使读密钥等于当前的写密钥。
    420     if (!rc)
    421     {
    422         if (pBlock->ReadKey)
    423         {
    424             sqlite3_free(pBlock->ReadKey);
    425         }
    426         pBlock->ReadKey = pBlock->WriteKey;
    427     }
    428     else// 如果失败,销毁当前的写密钥,并恢复为当前的读密钥。
    429     {
    430         if (pBlock->WriteKey)
    431         {
    432             sqlite3_free(pBlock->WriteKey);
    433         }
    434         pBlock->WriteKey = pBlock->ReadKey;
    435     }
    436 
    437     //如果读密钥和写密钥皆为空,就不需要再对页进行编解码。
    438     //销毁加密块并移除页的编解码器
    439     if (!pBlock->ReadKey && !pBlock->WriteKey)
    440     {
    441         //sqlite3pager_set_codec(p, NULL, NULL);
    442         sqlite3PagerSetCodec(p, NULL, NULL, sqlite3pager_free_codecarg, NULL);
    443         DestroyCryptBlock(pBlock);
    444     }
    445     sqlite3_mutex_leave(pbt->db->mutex);
    446     return rc;
    447 
    448 }
    449 
    450 
    451 /***
    452 下面是加密函数的主体
    453 ***/
    454 int sqlite3_key_interop(sqlite3 *db, const void *pKey, int nKeySize)
    455 {
    456     printf("[sqlite3_key_interop]called...
    ");
    457     return sqlite3CodecAttach(db, 0, pKey, nKeySize);
    458 }
    459 
    460 // 释放与一个页相关的加密块
    461 void sqlite3pager_free_codecarg(void *pArg)
    462 {
    463     if (pArg)
    464         DestroyCryptBlock((LPCryptBlock)pArg);
    465 }
    466 
    467 #endif //#ifdef SQLITE_HAS_CODEC

    (上述代码适用于3.8.7)
    五、       后记
    写此教程,可不是一个累字能解释。
    但是我还是觉得欣慰的,因为我很久以前就想写 sqlite 的教程,一来自己备忘,二而已造福大众,大家不用再走弯路。
    本人第一次写教程,不足的地方请大家指出。

    本文可随意转载、修改、引用。但无论是转载、修改、引用,都请附带我的名字:董淳光。以示对我劳动的肯定。

  • 相关阅读:
    AX ERROR: Could not find my mock parent, most likely I am stale 不及格的程序员
    利用Segue在视图控制器间传值的问题 不及格的程序员
    Creating a Singleton Instance 不及格的程序员
    iPad 通知 UIKeyboardWillShowNotification 不会在keyBoard处在Undock状态下接到通知 不及格的程序员
    Why RootViewController's view is rotated Automatically by System when the app first loaded? 不及格的程序员
    如何弹出UIDatePicker最好 不及格的程序员
    jQuery开始做恶了 不及格的程序员
    what is the SEL,id and IMP,Class ,Method? 不及格的程序员
    Objectivec 字符串比较的陷井 不及格的程序员
    Unable to create any keyboard shortcuts after the iOS 6.1.3 update on iPad. 不及格的程序员
  • 原文地址:https://www.cnblogs.com/qxxnxxFight/p/4096074.html
Copyright © 2011-2022 走看看