本文简要讲述一下windows平台下sqlite3的编译及简单使用。当前我们的编译环境如下:
操作系统: 64位win7
编译器: vs2013
1. sqlite3的编译
我们到sqlite3官网 下载最新版本(v3.29.0
)的如下4个压缩包:
sqlite-amalgamation-3290000.zip: sqlite3源码包Amalgamation版本(混合版)。sqlite3源代码库总共大概有100多个C文件,所谓Amalgamation版本,即是把这100多个C文件合并到了一个sqlite3.c文件中了。
sqlite-doc-3290000.zip: sqlite3的相关文档
sqlite-dll-win32-x86-3290000.zip: 预编译好的sqlite3动态链接库(32位版)
sqlite-dll-win64-x64-3290000.zip: 预编译好的sqlite3动态链接库(64位版)
sqlite-src-3290000.zip: sqlite3源码包(原始版)
这里我们可以有如下两种方式来编译sqlite3,生成我们想要的sqlite3.lib以及sqlite3.dll:
基于预编译好的sqlite3动态链接库生成sqlite3.lib,以及sqlite3.h头文件
基于Amalgamation源代码编译sqlite3.lib以及sqlite3.dll
基于sqlite3源码编译sqlite3.lib以及sqlite3.dll
说明: 如果没有特殊要求,可直接下载sqlite-dll-xxx.zip内的dll,如果不放心就自己编译。正常情况下建议直接下载sqlite-dll-xxx.zip来使用
下面我们分别介绍。
1.1 基于预编译好的动态链接库来编译
这里我们使用sqlite-dll-win64-x64-3290000.zip来编译出sqlite3.lib。解压该压缩文件,可以看到有如下:
# dir
2019/07/11 02:44 6,010 sqlite3.def
2019/07/11 02:44 1,902,080 sqlite3.dll
接着,我们以管理员身份运行 “VS2013 开发人员命令提示”,然后切换到上面的解压目录,执行如下命令:
# cd D:\workspace\sqlite-dll-win64-x64-3290000
# lib /def:sqlite3.def //采用缺省的方式生成lib库(32位版)
# lib /def:sqlite3.def /machine:x86 //明确指定生成x86版本的lib库(32位版)
# lib /def:sqlite3.def /machine:x64 //明确指定生成x64版本的lib库 (我们这里用此命令)
Microsoft (R) Library Manager Version 12.00.40629.0
Copyright (C) Microsoft Corporation. All rights reserved.
正在创建库 sqlite3.lib 和对象 sqlite3.exp
# dir
D:\workspace\sqlite-dll-win64-x64-3290000 的目录
2019/07/11 02:44 6,010 sqlite3.def
2019/07/11 02:44 1,902,080 sqlite3.dll
2019/09/10 12:35 37,700 sqlite3.exp
2019/09/10 12:35 62,596 sqlite3.lib
注: 这里要以管理员身份运行,否则可能出现各种怪异的问题
之后,我们从sqlite-amalgamation-3290000.zip 拷贝出sqlite3.h头文件即可。
1.2 基于sqlite3 Amalgamation版源代码编译
这里我们可以根据自身需要选择编译sqlite3动态库或者sqlite3静态库。下面分别介绍:
1.2.1 sqlite3动态库编译
进行如下步骤:
1) 使用vs2013创建win32工程sqlite3-dynamic
,然后选择DLL和空项目 (正常情况下,vc6-vs2015其实都应该支持)
2) 把sqlite3.c、sqlite3.h、sqlite3ext.h、sqlite3.def拷贝到工程源文件目录,前3个文件位于sqlite-amalgamation-3290000.zip 压缩包中,后一个文件位于sqlite-dll-win64-x64-3290000.zip 压缩包中。(这里采用64位版)
3) 通过工程的资源管理器把上述4个文件添加到工程中: 将sqlite3.c添加到源文件项中,将sqlite3.h、sqlite3ext.h添加到头文件项中
4) 修改工程配置,在配置属性–>c/c++–>预处理器–>预处理器定义,加入
SQLITE_ENABLE_RTREE
SQLITE_ENABLE_COLUMN_METADATA
SQLITE_ENABLE_DESERIALIZE
SQLITE_ENABLE_FTS5
SQLITE_ENABLE_FTS3
注: 此步骤在编译Debug版本时,可以不进行设置,但是编译Release版本时需要进行设置
5) 修改工程配置,在配置属性–>链接器–>输入–>模块定义文件加入sqlite3.def
6) 编译时选择好平台为x64
,并在配置属性–>常规–>目标文件名 出将生成的目标文件名改为sqlite3
1.2.2 sqlite3静态库编译
进行如下步骤:
1) 使用vs2013创建win32工程sqlite3-static
,然后选择静态库,去掉预编译头(正常情况下,vc6-vs2015其实都应该支持)
2) 把sqlite3.c、sqlite3.h、sqlite3ext.h、sqlite3.def拷贝到工程源文件目录,前3个文件位于sqlite-amalgamation-3290000.zip 压缩包中,后一个文件位于sqlite-dll-win64-x64-3290000.zip 压缩包中。(这里采用64位版)
3) 通过工程的资源管理器把上述4个文件添加到工程中: 将sqlite3.c添加到源文件项中,将sqlite3.h、sqlite3ext.h添加到头文件项中
4) 修改工程配置,在配置属性–>库管理器–>常规–>模块定义文件加入sqlite3.def
5) 编译时选择好平台为x64
,并在配置属性–>常规–>目标文件名 出将生成的目标文件名改为sqlite3
1.3 基于sqlite3源码编译
1.3.1 安装tcl
虽然sqlite3本身并不依赖tcl,但是编译时会用到tcl工具,这里我们可以到tcl官网 去下载安装。这里我们安装ActiveTcl 8.5
版本。安装步骤较为简单,这里不做介绍。
1.3.2 编译sqlite3源码
这里我们首先解压sqlite-src-3290000.zip ,然后可能需要对解压出来的文件夹名称进行重命名为sqlite
,这是因为我们通过脚本编译时,里面脚本默认的工程路径名为sqlite
。
之后,我们以管理员身份运行 “VS2013 开发人员命令提示”,然后切换到上面的解压目录,执行如下命令:
#cd D:\workspace\sqlite-src-3290000\sqlite
# nmake /f Makefile.msc TOP=..\sqlite
# nmake /f Makefile.msc clean TOP=..\sqlite
2. sqlite3的使用
我们可以使用sqlite3.exe来打开sqlite数据库,其基本命令如下:
sqlite> .help
.archive ... Manage SQL archives
.auth ON|OFF Show authorizer callbacks
.backup ?DB? FILE Backup DB (default "main") to FILE
.bail on|off Stop after hitting an error. Default OFF
.binary on|off Turn binary output on or off. Default OFF
.cd DIRECTORY Change the working directory to DIRECTORY
.changes on|off Show number of rows changed by SQL
.check GLOB Fail if output since .testcase does not match
.clone NEWDB Clone data into NEWDB from the existing database
.databases List names and files of attached databases
.dbconfig ?op? ?val? List or change sqlite3_db_config() options
.dbinfo ?DB? Show status information about the database
.dump ?TABLE? ... Render all database content as SQL
.echo on|off Turn command echo on or off
.eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN
.excel Display the output of next command in a spreadsheet
.exit ?CODE? Exit this program with return-code CODE
.expert EXPERIMENTAL. Suggest indexes for specified queries
.fullschema ?--indent? Show schema and the content of sqlite_stat tables
.headers on|off Turn display of headers on or off
.help ?-all? ?PATTERN? Show help text for PATTERN
.import FILE TABLE Import data from FILE into TABLE
.imposter INDEX TABLE Create imposter table TABLE on index INDEX
.indexes ?TABLE? Show names of indexes
.iotrace FILE Enable I/O diagnostic logging to FILE
.limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT
.lint OPTIONS Report potential schema issues.
.load FILE ?ENTRY? Load an extension library
.log FILE|off Turn logging on or off. FILE can be stderr/stdout
.mode MODE ?TABLE? Set output mode
.nullvalue STRING Use STRING in place of NULL values
.once (-e|-x|FILE) Output for the next SQL command only to FILE
.open ?OPTIONS? ?FILE? Close existing database and reopen FILE
.output ?FILE? Send output to FILE or stdout if FILE is omitted
.parameter CMD ... Manage SQL parameter bindings
.print STRING... Print literal STRING
.progress N Invoke progress handler after every N opcodes
.prompt MAIN CONTINUE Replace the standard prompts
.quit Exit this program
.read FILE Read input from FILE
.restore ?DB? FILE Restore content of DB (default "main") from FILE
.save FILE Write in-memory database into FILE
.scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off
.schema ?PATTERN? Show the CREATE statements matching PATTERN
.selftest ?OPTIONS? Run tests defined in the SELFTEST table
.separator COL ?ROW? Change the column and row separators
.session ?NAME? CMD ... Create or control sessions
.sha3sum ... Compute a SHA3 hash of database content
.shell CMD ARGS... Run CMD ARGS... in a system shell
.show Show the current values for various settings
.stats ?on|off? Show stats or turn stats on or off
.system CMD ARGS... Run CMD ARGS... in a system shell
.tables ?TABLE? List names of tables matching LIKE pattern TABLE
.testcase NAME Begin redirecting output to 'testcase-out.txt'
.timeout MS Try opening locked tables for MS milliseconds
.timer on|off Turn SQL timer on or off
.trace ?OPTIONS? Output each SQL statement as it is run
.vfsinfo ?AUX? Information about the top-level VFS
.vfslist List all available VFSes
.vfsname ?AUX? Print the name of the VFS stack
.width NUM1 NUM2 ... Set column widths for "column" mode
sqlite>
但相关命令使用不便,建议使用SQLite Expert Professional
这一图形界面工具来操作sqlite3。
3. 通过sqlite3 API操作数据库
3.1 程序示例
参看如下示例:
// sqlite3_sample.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "sqlite3.h"
#include <string.h>
#include <windows.h>
#include <wchar.h>
#include <string>
#include <vector>
using namespace std;
#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif
int print_record(void *params, int n_column, char **column_value, char **column_name);
//UTF-8转Unicode
std::wstring Utf82Unicode(const std::string& utf8string)
{
int widesize = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, NULL, 0);
if (widesize == ERROR_NO_UNICODE_TRANSLATION)
{
throw std::exception("Invalid UTF-8 sequence.");
}
if (widesize == 0)
{
throw std::exception("Error in conversion.");
}
std::vector<wchar_t> resultstring(widesize);
int convresult = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, &resultstring[0], widesize);
if (convresult != widesize)
{
throw std::exception("La falla!");
}
return std::wstring(&resultstring[0]);
}
//unicode 转为 ascii
string WideByte2Acsi(wstring& wstrcode)
{
int asciisize = ::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, NULL, 0, NULL, NULL);
if (asciisize == ERROR_NO_UNICODE_TRANSLATION)
{
throw std::exception("Invalid UTF-8 sequence.");
}
if (asciisize == 0)
{
throw std::exception("Error in conversion.");
}
std::vector<char> resultstring(asciisize);
int convresult = ::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, &resultstring[0], asciisize, NULL, NULL);
if (convresult != asciisize)
{
throw std::exception("La falla!");
}
return std::string(&resultstring[0]);
}
//utf-8 转 ascii
string UTF_82ASCII(string& strUtf8Code)
{
string strRet("");
//先把 utf8 转为 unicode
wstring wstr = Utf82Unicode(strUtf8Code);
//最后把 unicode 转为 ascii
strRet = WideByte2Acsi(wstr);
return strRet;
}
//ascii 转 Unicode
wstring Acsi2WideByte(string& strascii)
{
int widesize = MultiByteToWideChar(CP_ACP, 0, (char*)strascii.c_str(), -1, NULL, 0);
if (widesize == ERROR_NO_UNICODE_TRANSLATION)
{
throw std::exception("Invalid UTF-8 sequence.");
}
if (widesize == 0)
{
throw std::exception("Error in conversion.");
}
std::vector<wchar_t> resultstring(widesize);
int convresult = MultiByteToWideChar(CP_ACP, 0, (char*)strascii.c_str(), -1, &resultstring[0], widesize);
if (convresult != widesize)
{
throw std::exception("La falla!");
}
return std::wstring(&resultstring[0]);
}
//Unicode 转 Utf8
std::string Unicode2Utf8(const std::wstring& widestring)
{
int utf8size = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, NULL, 0, NULL, NULL);
if (utf8size == 0)
{
throw std::exception("Error in conversion.");
}
std::vector<char> resultstring(utf8size);
int convresult = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, &resultstring[0], utf8size, NULL, NULL);
if (convresult != utf8size)
{
throw std::exception("La falla!");
}
return std::string(&resultstring[0]);
}
//ascii 转 Utf8
string ASCII2UTF_8(string& strAsciiCode)
{
string strRet("");
//先把 ascii 转为 unicode
wstring wstr = Acsi2WideByte(strAsciiCode);
//最后把 unicode 转为 utf8
strRet = Unicode2Utf8(wstr);
return strRet;
}
// 注释:多字节包括GBK和UTF-8
int GBK2UTF8(char *szGbk, char *szUtf8, int Len)
{
// 先将多字节GBK(CP_ACP或ANSI)转换成宽字符UTF-16
// 得到转换后,所需要的内存字符数
int n = MultiByteToWideChar(CP_ACP, 0, szGbk, -1, NULL, 0);
// 字符数乘以 sizeof(WCHAR) 得到字节数
WCHAR *str1 = new WCHAR[sizeof(WCHAR) * n];
// 转换
MultiByteToWideChar(CP_ACP, // MultiByte的代码页Code Page
0, //附加标志,与音标有关
szGbk, // 输入的GBK字符串
-1, // 输入字符串长度,-1表示由函数内部计算
str1, // 输出
n // 输出所需分配的内存
);
// 再将宽字符(UTF-16)转换多字节(UTF-8)
n = WideCharToMultiByte(CP_UTF8, 0, str1, -1, NULL, 0, NULL, NULL);
if (n > Len)
{
delete[]str1;
return -1;
}
WideCharToMultiByte(CP_UTF8, 0, str1, -1, szUtf8, n, NULL, NULL);
delete[]str1;
str1 = NULL;
return 0;
}
//UTF-8 GBK
int UTF82GBK(char *szUtf8, char *szGbk, int Len)
{
int n = MultiByteToWideChar(CP_UTF8, 0, szUtf8, -1, NULL, 0);
WCHAR * wszGBK = new WCHAR[sizeof(WCHAR) * n];
memset(wszGBK, 0, sizeof(WCHAR) * n);
MultiByteToWideChar(CP_UTF8, 0, szUtf8, -1, wszGBK, n);
n = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
if (n > Len)
{
delete[]wszGBK;
return -1;
}
WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGbk, n, NULL, NULL);
delete[]wszGBK;
wszGBK = NULL;
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
sqlite3 *db = NULL;
sqlite3_stmt *stmt = NULL;
int ret;
char **dbresult;
int nrow, ncolumn, index;
char *errmsg = NULL;
const char *database_name = "./sqlite3-demo.db";
const char *sql_drop_table = "drop table if exists student";
const char *sql_create_table = "create table student(id int primary key,name varchar(128))";
// create or open database
ret = sqlite3_open(database_name, &db);
if (ret != SQLITE_OK){
fprintf(stderr, "Cannot open database '%s'\n", sqlite3_errmsg(db));
return -1;
}
printf("Open database '%s' success\n", database_name);
// create or drop table
ret = sqlite3_exec(db, sql_drop_table, NULL, NULL, &errmsg);
if (ret != SQLITE_OK){
fprintf(stderr, "drop table '%s' failure: %s\n", "student", errmsg);
}
ret = sqlite3_exec(db, sql_create_table, NULL, NULL, &errmsg);
if (ret != SQLITE_OK){
fprintf(stderr, "create table '%s' failure: %s\n", "student", errmsg);
return -1;
}
//insert data
char *sql_str1 = "INSERT INTO student(id, name) VALUES(1001, 'Ady Liu');";
char *sql_str2 = "INSERT INTO student(id, name) VALUES(1002, 'ivan');";
char *sql_str3 = "INSERT INTO student(id, name) VALUES(1003, '刘德华');";
ret = sqlite3_exec(db, sql_str1, NULL, NULL, &errmsg);
printf("Insert a record %s\n", ret == SQLITE_OK ? "OK" : "FAIL");
ret = sqlite3_exec(db, sql_str2, NULL, NULL, &errmsg);
printf("Insert a record %s\n", ret == SQLITE_OK ? "OK" : "FAIL");
ret = sqlite3_exec(db, sql_str3, NULL, NULL, &errmsg);
printf("Insert a record %s\n", ret == SQLITE_OK ? "OK" : "FAIL");
//delete data
char *sql_str4 = "DELETE FROM student WHERE id = 1002;";
ret = sqlite3_exec(db, sql_str4, NULL, NULL, &errmsg);
printf("Delete a record %s\n", ret == SQLITE_OK ? "OK" : "FAIL");
//prepare statement(根据文档说明,优先使用sqlite3_prepare_v2())
char *sql_str5 = "INSERT INTO student(id, name) VALUES(?,?);";
char buf[256];
sqlite3_prepare_v2(db, sql_str5, -1, &stmt, 0);
for (int i = 10; i<20; i++){
sprintf_s(buf, "HELLO#%i", i);
sqlite3_bind_int(stmt, 1, i);
sqlite3_bind_text(stmt, 2, buf, int(strlen(buf)), SQLITE_STATIC);
sqlite3_step(stmt);
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
//select data
char *sql_str6 = "SELECT * FROM student LIMIT 5;";
ret = sqlite3_exec(db, sql_str6, print_record, NULL, &errmsg);
if (ret != SQLITE_OK){
fprintf(stderr, "query SQL error: %s\n", errmsg);
}
//update data
char *sql_str7 = "UPDATE student set name='MESSAGE#10' WHERE id=10;";
ret = sqlite3_exec(db, sql_str7, NULL, NULL, &errmsg);
if (ret != SQLITE_OK){
fprintf(stderr, "update failure: %s\n", errmsg);
}
//select table
char *sql_str8 = "SELECT * FROM student;";
ret = sqlite3_get_table(db, sql_str8, &dbresult, &nrow, &ncolumn, &errmsg);
if (ret == SQLITE_OK){
printf("query %i records.\n", nrow);
index = ncolumn;
for (int i = 0; i<nrow; i++){
printf("[%2i]", i);
for (int j = 0; j<ncolumn; j++){
//printf(" %s", dbresult[index]);
UTF82GBK(dbresult[index], buf, 256);
printf(" %s", buf);
index++;
}
printf("\n");
}
}
else{
fprintf(stderr, "query with get_table error: %s\n", errmsg);
}
sqlite3_free_table(dbresult);
//delete table
char *sql_str9 = "DELETE FROM student;";
ret = sqlite3_exec(db, sql_str9, NULL, NULL, &errmsg);
if (ret == SQLITE_OK){
printf("delete records: %i\n", sqlite3_changes(db));
}
sqlite3_free(errmsg);
//close database
sqlite3_close(db);
printf("Close database\n");
return 0;
}
int print_record(void *params, int n_column, char **column_value, char **column_name){
int i;
char buf[256];
for (i = 0; i<n_column; i++){
//printf("\t%s", column_value[i]);
UTF82GBK(column_value[i], buf, 256);
printf("\t%s", buf);
}
printf("\n");
return 0;
}
程序执行情况如下:
Open database './sqlite3-demo.db' success
Insert a record OK
Insert a record OK
Insert a record OK
Delete a record OK
1001 Ady Liu
1003 刘德华
10 HELLO#10
11 HELLO#11
12 HELLO#12
query 12 records.
[ 0] 1001 Ady Liu
[ 1] 1003 刘德华
[ 2] 10 MESSAGE#10
[ 3] 11 HELLO#11
[ 4] 12 HELLO#12
[ 5] 13 HELLO#13
[ 6] 14 HELLO#14
[ 7] 15 HELLO#15
[ 8] 16 HELLO#16
[ 9] 17 HELLO#17
[10] 18 HELLO#18
[11] 19 HELLO#19
delete records: 12
Close database
请按任意键继续. . .
3.2 sqlite乱码处理
通过sqlite.dll接口对sqlite数据库进行操作,包括打开数据库,插入、查询数据库等,如果操作接口输入参数包含中文字符,会导致操作异常。例如调用sqlite3_open()打开数据库文件,如果文件路径出现中文,就会导致打开失败。sqlite3_exec()执行sql语句,如果包含中文对应字符就会变成乱码。
这是由于sqlite数据库使用的是UTF-8编码方式,而传入的字符串时ASCII编码或者Unicode编码,导致字符串格式错误。解决的方案是在调用sqlite接口之前,先将字符串转换成UTF-8编码。
1) 发送数据
这里我们向sqite3发送数据时,通过如下方式来自动完成UTF-8编码的装换:
#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif
2) 查询数据
查询数据时,sqlite3返回给我们的数据格式也是utf-8编码。但是我们的命令行控制台的编码格式为GBK:
因此我们在这里读取出数据的时候,需要调用UTF82GBK()函数来进行转换。
3.3 关于VC平台字符编码的补充
关于vc平台上字符编码问题,有如下:
1) 一个wchar_t字符串,例如L”这是一个abc字符串”,是否是UTF-16格式?
答: 不是。是UNICODE。
2) 而char字符串,例如”这是一个abc字符串”,是否是GBK格式?
答: 是。也是ASCII
3) 在VS工程的设置里面,“使用UNICODE字符集”或者“使用多字节字符集”也并没有直接说明到底是哪种编码
答: Vs中UNICODE字符集就是UNICODE编码,多字符集就是GBK编码(或ASCII)
4) 听说Windows一般情况下并不默认使用UTF-8,那么以多字节字符集为例,是不是如果我用简体中文版Windows就是GBK,用日文版Windows就是JIS?
答: 是
5) 有没有办法完全设置为UTF-8编码的代码文本,UTF-8编码的原生字符串?
答: 暂时我没有办法
6) 若C++DLL导出函数的一个参数是const char*,C#对应参数是string,那么C++被传入的似乎是一个UTF-8编码的char字符串。若是把这字符串与VC原生的字符串混用就很尴尬。而且,若是我要给C#回传字符串,似乎也得是UTF8才行。那么,VC有什么好办法处理UTF8字符串与原生字符串的相互转换呢?
答: 使用编码转换函数,例如MultiByteToWideChar
[参看]:
Windows下编译sqlite3
windows下sqlite3静态库和动态库的编译
C++ VS2013环境编译使用sqlite数据库全过程
SQLite 教程
sqlite3API函数
简单有效的内存池实现
VC++ UTF-8与GBK格式转换
sqlite中文乱码问题原因分析及解决(utf8和ascii相互转换)
VC2017中,字符串的默认编码格式是什么?
c++日志
Sqlite3数据库API手册