2026/11/4 12:51:01

VC6.0编译SQLite3静态库:版本选择、兼容补丁与排查实战

VC6.0编译SQLite3静态库:版本选择、兼容补丁与排查实战 简介SQLite3 VC6.0 编译版面向仍在使用 Visual C 6.0 维护旧项目、需要轻量级关系型数据库的开发者也适合希望阅读 SQLite3 源码、完成自定义编译与调试的学习者可解决老平台下数据库引擎接入和库文件配套不完整的问题。资源以官方源码为基础包含可在 VC6.0 环境中打开的工程、静态库与动态库以及 API 文档、测试示例和验证代码便于理解编译配置、链接方式与基本功能调用。压缩包为 7z 格式约 5.4MB上游未提供文件总数与主要类型明细包体较小适合下载留存和本地归档。目前已有 228 人学习或下载说明其在旧版本开发场景中具有一定参考价值。读者可据此复现编译过程、排查链接错误并将 SQLite3 接入现有应用或进行二次开发。1. 开场一次失败让我记了十年sqlite3 vc6.0 这套组合看着像是「把两个文件拖进工程、点一下编译」的事。我第一次干的时候也这么想结果 1200 版编译器直接甩了四十多个错误最上面一条是long followed by long is illegal。那是个已经跑了七八年的老系统只能挂在 VC6 上编译加不了别的依赖数据库又必须换成 sqlite3。当时卡了整整两天不是 sqlite3 不好用是这套用法不常见能搜到的东西零散。这篇记录按我后来重装几次的流程写先定版本再编库再补 VC6 缺的那几块最后是验证和救火。手上还有 VC6 老工程、又必须上 SQLite 的人可以照着走。2. 安装前确定版本VC6 能接受的 sqlite3 版本与工程清单这一步容易被跳过但它是后面所有问题的源头。VC6 的编译器认的 C 标准停在 C89 那一带加上一点自己的扩展//注释、__int64、__FUNCTION__。而 sqlite3 的源码越往后越往 C99 上靠。两边的交集比想象中窄。2.1 先用编译器边界把版本范围缩小判断哪个版本的 sqlite3 能在 VC6 下直接使用不用查支持表看源码里三样东西就够了有没有#include stdint.h、有没有裸的long long、有没有__func__。这三样都是 VC6 不认的。我自己的经验区间是3.6.x 到 3.7.x 这一段。这一段的 amalgamation 对老编译器留了退路sqlite3.c里有SQLITE_INT64_TYPE这个宏没定义时它才用long long int定义了就走你给的类型os_win.c里也有1400的 MSVC 版本分支会把snprintf映射到_snprintf。这些退路在 3.8 之后基本被清理掉了stdint.h直接 include__func__也用上了需要你自己补一大堆东西。具体怎么选看两个条件判断条件建议老工程只用到增删改查、事务、索引3.6.x 段源码干净编译错误最少需要 FTS3 全文检索或 RTree3.7.x 段功能全一些但要补的宏也多需要 WAL 模式3.7.0 以后才有注意同时要改sqlite3_open_v2的旗标需要sqlite3_prepare_v23.6.x 后期就有不必为了它上 3.8提示不要拿最新版往下试。你以为是在验证兼容性实际上是在给一个二十多年前的编译器补 C99 运行时工作量翻好几倍还没人替你测过。还有个现实的坑3.8 之后的sqlite3.c单文件超过 6MBVC6 的 IDE 打开它会卡住甚至直接假死。这不是编译器的问题是编辑器的问题。所以哪怕版本选对了也不要指望在 IDE 里改源码。2.2 目录、运行库、字符集三件事先确定下来在敲第一条命令之前把这几样写在一张纸上后面所有报错都跟它们有关。目录骨架。我一般这么摆好处是 Debug 和 Release 两套库物理隔离不会互相覆盖E:\prj\legacy_app\ third_party\ sqlite\ src\ sqlite3.c sqlite3.h sqlite3ext.h include\ sqlite3.h (给工程引用的那份) lib\ sqlite3_r.lib sqlite3_d.lib compat\ stdint.h (VC6 补丁头下一章讲) src\ 你的业务代码 legacy_app.dsw运行库。VC6 的/MD和/MT是分水岭选错了会在链接期给你LNK2005或者运行期堆断言。sqlite3 作为静态库编出来的时候它内部用的malloc/free来自某个 CRT如果它用/MT编、你的 exe 用/MD编那么sqlite3_malloc出来的指针传到你的代码里再由free释放两块堆不是同一个轻则断言重则进程静默退出。规则只有一条exe 用什么sqlite3.lib 就用什么。老工程的现状我见过两种一种是 VC6 建工程时默认的/MD依赖msvcrt.dll系统自带另一种是后来有人为了发布方便改成/MT。先翻工程设置里的 C/C 页看Use run-time library是Multithreaded DLL还是Multithreaded记下来再编库。字符集。VC6 的工程基本都是窄字符MBCS源文件存的是 GBK。而 sqlite3 的sqlite3_open收的是 UTF-8 路径。这两者不一致就是后面「数据库文件死活建不出来、也不报错」的根因。先记住结论中文路径要么走sqlite3_open16传宽字符要么先把 GBK 转 UTF-8 再调sqlite3_open。这一点在第 5 章会展开。线程模型。sqlite3 编的时候有三个值SQLITE_THREADSAFE0单线程最快、1串行化一个连接多线程共享安全、2多线程一个连接不能被多线程同时用但多个连接可以分到不同线程。老工程通常是单线程 UI 加一个后台采集线程稳妥起见直接1性能损失在这个量级上基本看不出来。3. 把 sqlite3 编成 lib命令行 cl 的完整操作步骤这一章是主体。目标是从sqlite3.c出发得到两个能直接挂进 VC6 工程的.lib。3.1 amalgamation 准备文件摆放与 BOM 检查从 sqlite 官网下 amalgamation 包解压出来一般是sqlite3.c、sqlite3.h、sqlite3ext.h、shell.c四个文件。shell.c是命令行工具这次不用留在原地就行不要往工程里加。把前三个拷进third_party\sqlite\src\再拷一份sqlite3.h到include\。为什么要拷两份因为业务代码只该看见include那一份src里的随时可能被替换成别的版本工程里的 Include 路径指向include能避免改版本的时候把整个工程的引用一起动。第一件事是查 BOM。下载的源码如果被编辑器另存成 UTF-8 with BOMVC6 编译器看到开头的EF BB BF会直接报sqlite3.c(1) : error C2018: unknown character 0xef sqlite3.c(1) : error C2018: unknown character 0xbb sqlite3.c(1) : error C2018: unknown character 0xbf这个错误看起来吓人其实很好解。用十六进制编辑器打开看头三个字节是EF BB BF就去掉。或者用一段脚本批量处理。我习惯用 PowerShell 一次性检查整个目录# 找出所有带 UTF-8 BOM 的文件输出路径 Get-ChildItem -Path .\third_party\sqlite\src\*.c, .\third_party\sqlite\src\*.h | ForEach-Object { $b [System.IO.File]::ReadAllBytes($_.FullName) if ($b.Length -ge 3 -and $b[0] -eq 0xEF -and $b[1] -eq 0xBB -and $b[2] -eq 0xBF) { Write-Output (BOM found: $_.FullName) } }ReadAllBytes把文件整个读进内存只看前三字节判断 BOM。这个脚本只做检测不删除确认之后再手工处理避免误伤。如果目录里文件多可以把它挂成编译前的一步。顺带说一个容易被忽略的点sqlite3.c里如果有//风格的注释VC6 的 C 编译器是认的作为扩展不用担心。真正会挂的是inline关键字——VC6 的 C 模式不认inline只认__inline。3.7.x 里对这块做了宏替换3.8 之后就没有了。3.2 一条 cl 命令编出 Release 版 sqlite3.lib先开一个「Visual C 6.0 工具」的命令行窗口或者手工跑一次vcvars32.bat# VC6 安装目录下的环境脚本作用是设好 PATH / LIB / INCLUDE call C:\Program Files\Microsoft Visual Studio\VC98\Bin\VCVARS32.BAT这一步不做的话后面cl和lib都找不到。这个脚本只在当前命令行窗口有效下次开新窗口要重来。然后是编译命令。下面这条是我在third_party\sqlite\src目录下用的 Release 版cl /c /nologo /O1 /MT /W3 ^ /Fo..\lib\sqlite3_r.obj ^ /D SQLITE_INT64_TYPE__int64 ^ /D SQLITE_UINT64_TYPEunsigned __int64 ^ /D snprintf_snprintf ^ /D vsnprintf_vsnprintf ^ /D SQLITE_THREADSAFE1 ^ /D SQLITE_OMIT_LOAD_EXTENSION ^ /D SQLITE_ENABLE_COLUMN_METADATA ^ /D SQLITE_DEFAULT_MEMSTATUS0 ^ sqlite3.c逐段说明/c只编译不链接产出.obj。/O1是「最小体积」优化。这里我刻意不用/O2VC6 的/O2在 sqlite3.c 这种几万行的函数密集文件上编译时间能到十分钟以上而且老版本优化器偶尔会生成错误的代码导致运行期出现难以定位的数据异常。/O1稳一些性能差距在实际业务里基本无法察觉。/MT对应静态 CRT。如果你的老工程是/MD这里改成/MD并把输出文件名改成sqlite3_r_md.obj避免和其他配置混在一起。/D SQLITE_INT64_TYPE__int64和后面那条是这套方案的核心解决long long不认识的问题第 4 章详说。snprintf那两条是把标准库里没有的函数名映射到 VC6 的带下划线版本。有些 3.7.x 的源码自己做了这个映射重复定义会报警告但不影响有的版本没做缺了就直接undeclared identifier。SQLITE_OMIT_LOAD_EXTENSION去掉动态加载扩展的能力。老工程基本用不到sqlite3_load_extension去掉能少一堆对LoadLibrary/GetProcAddress的依赖链接期也更干净。SQLITE_ENABLE_COLUMN_METADATA打开后可以用sqlite3_column_table_name、sqlite3_column_origin_name这类函数。如果你在做通用的数据访问层想根据结果集反查字段来自哪张表这个宏很值不做就别开纯增加体积。SQLITE_DEFAULT_MEMSTATUS0关掉内存统计每次分配少一次计数操作属于白送的性能。编完检查一下有没有.obj落地。如果cl报了一堆错先看第一个错误后面的经常是连锁反应。接着打包成静态库lib /nologo /OUT:..\lib\sqlite3_r.lib ..\lib\sqlite3_r.objlib.exe和cl.exe在同一个目录VCVARS32.BAT已经把路径加进去了。如果/OUT指定的目录不存在lib不会替你建直接报错先把lib目录建好。Debug 版把/O1换成/Od /Zi输出名改sqlite3_d其余参数不动。两套库分开存链接的时候按配置选这是最基本的纪律。3.3 VC6 工程里的三处设置头文件、库、链接顺序库编好了接下来是让工程找到它。VC6 的工程设置分散在几个对话框里容易漏。第一处头文件路径。Project → Settings → C/C → Preprocessor → Additional include directories填.\third_party\sqlite\include注意用相对路径工程换机器拷来拷去也不会断。VC6 的相对路径基准是.dsp文件所在目录不是.c文件所在目录这点和后来的 VS 一样但很多人第一次会搞错。第二处库路径。Project → Settings → Link → Input → Additional library path填.\third_party\sqlite\lib第三处链接库清单。同一个页面的Object/library modules在已有的kernel32.lib user32.lib后面加上sqlite3_r.libRelease 配置加sqlite3_r.libDebug 配置加sqlite3_d.lib。VC6 的 Debug 和 Release 是两套独立设置改完一个记得切到另一个再改一遍这是最常见的漏配。还有一个更省事的办法在业务代码的公共头里加一行/* 放在自己的 stdafx.h 或公共头里省掉每个工程单独配库路径 */ #pragma comment(lib, sqlite3_r.lib)#pragma comment是 VC 的扩展链接器会自动去找配合上面的库路径就能工作。好处是换工程不用重复配坏处是 Debug/Release 切换时文件名字不同得用条件编译包一层#ifdef _DEBUG # pragma comment(lib, sqlite3_d.lib) #else # pragma comment(lib, sqlite3_r.lib) #endif_DEBUG是 VC6 在 Debug 配置下自动定义的宏和/MTd是配套出现的。链接顺序也值得说一句。VC6 的链接器是按命令行顺序解析符号的sqlite3.lib依赖kernel32.lib里的InterlockedCompareExchange一类函数。工程默认的kernel32.lib一般排在前头通常没问题如果你手工调整过顺序把sqlite3.lib放到kernel32.lib后面比较稳。4. 参数与源码补丁VC6 缺的那几个类型和函数怎么补这一章讲的是「为什么那几条/D是必须的」。理解了出问题才知道往哪儿看。4.1 用 __int64 替换 long longsqlite3 内部有个 64 位整数类型sqlite_int64用于行号、时间戳、大整数列。3.7.x 的sqlite3.c里大概长这样/* sqlite3.c 中的原始写法VC6 看不懂 long long */ #if !defined(SQLITE_INT64_TYPE) # define SQLITE_INT64_TYPE long long int # define SQLITE_UINT64_TYPE unsigned long long int #endif typedef SQLITE_INT64_TYPE sqlite_int64; typedef SQLITE_UINT64_TYPE sqlite_uint64;VC6 的 C 编译器没有long long这个关键字遇到它直接报sqlite3.c(12345) : error C2632: long followed by long is illegal但它有自己的 64 位扩展__int64。所以只要在编译期把这两个宏定义掉源码里那段#if就不会走到默认分支/D SQLITE_INT64_TYPE__int64 /D SQLITE_UINT64_TYPEunsigned __int64注意unsigned __int64中间有空格/D后面的值必须整体加引号否则命令行解析会在空格处截断变成定义了SQLITE_UINT64_TYPEunsigned然后又是一个新的错误。定义之后要确认一件事sqlite3.c内部格式化 64 位整数用的是sqlite3_snprintf自己实现的版本不依赖 CRT 的printf所以不存在%lld不认的问题。但你自己业务代码里如果用printf打sqlite3_int64必须用%I64d而不是%lld——VC6 的 CRT 只认%I64d。这是另一个高频坑见第 5 章。4.2 补上 stdint.h 与 snprintf 的缺口如果你选的是 3.8 之后的版本除了long long还会遇到两个新的缺口。这一段是给「不得不用新版」的场景准备的。缺口一stdint.h。VC6 的 CRT 里没有这个头。解决办法是在third_party\sqlite\compat\下自己放一个然后把这个目录加进 Include 路径/* compat\stdint.h —— 仅供 VC6 使用的最小实现 */ #ifndef SQLITE_COMPAT_STDINT_H #define SQLITE_COMPAT_STDINT_H typedef signed char int8_t; typedef unsigned char uint8_t; typedef short int16_t; typedef unsigned short uint16_t; typedef int int32_t; typedef unsigned int uint32_t; typedef __int64 int64_t; /* VC6 的 64 位扩展 */ typedef unsigned __int64 uint64_t; /* VC6 是 32 位编译器指针宽度等于 int */ typedef int intptr_t; typedef unsigned int uintptr_t; #define INT8_MAX 127 #define INT16_MAX 32767 #define INT32_MAX 2147483647 #define UINT32_MAX 4294967295U #define INT64_MAX 9223372036854775807i64 #define INT64_MIN (-INT64_MAX - 1) #endif /* SQLITE_COMPAT_STDINT_H */这里几个类型都得对齐VC6 的short是 16 位、int是 32 位、__int64是 64 位和标准一致。intptr_t用int是因为 32 位编译器下指针就是 32 位。INT64_MAX写成i64后缀是 VC6 认的 64 位字面量后缀写LL会报错。缺口二snprintf。VC6 的 CRT 里有_snprintf和_vsnprintf没有不带下划线的版本。新版源码如果直接调用snprintf编译能过有隐式声明警告链接时会找不到符号。前面那条/D snprintf_snprintf就是干这个的。要留意_snprintf和标准的snprintf行为有个差异缓冲区不够时_snprintf不保证补\0。sqlite3 内部调用点大多自己做了截断处理一般不至于出问题但如果你的业务代码也用这个宏得自己补终止符。顺便说__func__。3.8 之后的源码在断言和日志里用了这个 C99 标识符。VC6 不支持但支持__FUNCTION__扩展加一条就能糊过去/D __func____FUNCTION__这条不是万能药只有在__func__被当作字符串常量用的地方才成立用在别处会出语法错误。先加上试报错再说。4.3 宏开关取舍线程模型、扩展加载与 FTS3/RTree宏不是越多越好每开一个都增加编译时间和链接体积有些还会引入额外依赖。下面这张表是我常用的几组按场景挑宏作用建议SQLITE_THREADSAFE1串行化模式有后台线程就开纯单线程改 0SQLITE_OMIT_LOAD_EXTENSION去掉sqlite3_load_extension老工程基本都建议开SQLITE_ENABLE_COLUMN_METADATA结果集列可反查表名做通用数据层时开SQLITE_ENABLE_FTS3全文检索只在需要全文检索时开代码量不小SQLITE_ENABLE_RTREER 树索引做地理数据时才开SQLITE_OMIT_DEPRECATED去掉废弃接口新代码可以开老代码慎开SQLITE_DEBUG打开内部断言只在排查疑难时临时开SQLITE_OMIT_DEPRECATED这条要单独提醒开了之后sqlite3_prepare、sqlite3_open的某些老入口会被裁掉如果你的老工程还在用这些旧接口编译能过但链接会失败报一串unresolved external symbol。先别开等代码全迁到_v2版本再说。SQLITE_ENABLE_FTS3在 VC6 下有额外风险FTS3 模块的代码里对isalnum之类的字符函数有依赖VC6 的实现和 C99 有细微差异遇到非 ASCII 输入可能行为不一致。如果你的全文检索只针对英文问题不大要处理中文建议别用 FTS3自己在业务层做分词再入普通表。还有一个不属于宏但属于编译选项的不要开/Gz默认__stdcall调用约定。sqlite3 的导出函数都是cdecl全局改成__stdcall会导致链接时函数名对不上报一堆修饰名不匹配的错误。同理/GX异常处理也不需要sqlite3 是纯 C没有异常。5. 避坑与排查VC6 编 sqlite3 的六个典型问题这一章全部是实际踩过的。按「现象 → 原因 → 解决」写遇到对得上的直接照方抓药。5.1 编译期与链接期的三个问题一、LNK2001: unresolved external symbol _sqlite3_open现象编译通过链接时所有 sqlite3 函数都报未解析。原因sqlite3.c被当成 C 编译了。VC6 判断语言看文件扩展名.c按 C 编.cpp按 C 编。C 编译器会对函数名做名字修饰sqlite3_open变成?sqlite3_openYA...而sqlite3.h里声明的还是 C 链接的名字两边对不上。常见于有人图省事把sqlite3.c改名成sqlite3.cpp加进工程。解决改回.c扩展名。如果因为某些原因必须用.cpp就要在头文件外面包一层/* 业务代码里包含 sqlite3.h 时这样写强制 C 链接 */ #ifdef __cplusplus extern C { #endif #include sqlite3.h #ifdef __cplusplus } #endifextern C告诉 C 编译器按 C 的规则生成符号名链接器才能找到。其实sqlite3.h自己已经带了这层保护问题出在编译单元上——只要sqlite3.c本身按 C 编了头文件里的extern C也救不回来因为定义端的符号已经被修饰了。二、LNK2005: _malloc already defined in LIBCMT.lib现象链接期报一堆 CRT 函数重复定义或者运行期HeapValidate断言失败。原因sqlite3.lib用/MT编静态 CRTexe 用/MD编动态 CRT两个 CRT 实例同时被链进来。解决统一运行库选项。查sqlite3.lib是怎么编的看工程设置里的Use run-time library改到一致。改完记得把旧的.obj和.lib删掉重编增量编译有时候不会重新生成。三、error C2018: unknown character 0xef现象编译第一行就报字符错误通常连报三条0xef、0xbb、0xbf。原因sqlite3.c带了 UTF-8 BOM。解决去掉 BOM。用十六进制编辑器删掉头三字节或者用脚本批量处理。如果文件多次流转建议在版本库上加一条提交检查规则有 BOM 直接拒绝。5.2 运行期与编码相关的三个问题四、数据库文件建不出来也不报错现象sqlite3_open(D:\\数据\\app.db, db)返回 0成功但磁盘上找不到文件或者返回 14SQLITE_CANTOPEN错误信息是unable to open database file。原因sqlite3_open收的是 UTF-8 编码的路径。VC6 的源文件是 GBK字符串字面量D:\\数据\\app.db在内存里是 GBK 字节。sqlite3 内部把这段字节当 UTF-8 解析转成宽字符时得到的是乱码路径CreateFileW自然找不到。解决走宽字符入口。sqlite3_open16收 UTF-16 字符串正好对应 VC6 的wchar_tVC6 的wchar_t是 16 位和 Windows 的WCHAR一致#include windows.h #include sqlite3.h /* 用宽字符路径打开彻底避开 GBK/UTF-8 转换问题 */ sqlite3 *open_db_wide(const wchar_t *path) { sqlite3 *db 0; int rc sqlite3_open16(path, db); /* path 必须是 UTF-16 */ if (rc ! SQLITE_OK) { if (db) sqlite3_close(db); return 0; } return db; } /* 调用示例L 前缀让编译器按宽字符存字面量 */ /* sqlite3 *db open_db_wide(LD:\\数据\\app.db); */sqlite3_open16在 3.6.x 就有不需要额外宏。注意路径字面量前面的L不加的话类型对不上VC6 会给你一个警告然后传进去乱码。如果必须用sqlite3_open比如路径是从配置文件读的char*那就得自己做转换/* GBK 路径转 UTF-8再交给 sqlite3_open */ static int gbk_path_to_utf8(const char *gbk, char *out, int out_size) { WCHAR wbuf[MAX_PATH]; /* 先把 GBK 按系统代码页转成宽字符 */ if (MultiByteToWideChar(CP_ACP, 0, gbk, -1, wbuf, MAX_PATH) 0) return -1; /* 再转成 UTF-8 字节流 */ if (WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, out, out_size, NULL, NULL) 0) return -1; return 0; }两步转换少一步都不行。CP_ACP是系统默认代码页在中文系统上就是 GBKCP_UTF8是目标编码。-1表示源字符串以\0结尾函数会连终止符一起转。五、sqlite3_column_int64打出来是负数或乱码现象存进去一个大于 20 亿的数读出来是负数或者用printf打印 64 位值输出一串乱码。原因两个独立问题。一是用sqlite3_column_int读了大数那个函数返回int32 位截断。二是用%lld打印VC6 的 CRT 不认这个格式符。解决读用sqlite3_column_int64打印用%I64dsqlite3_int64 n sqlite3_column_int64(stmt, 0); /* 别用 column_int */ printf(value %I64d\n, n); /* VC6 只认 %I64d */%I64d是 VC 系 CRT 的扩展格式符后来的 VS 才同时支持%lld。老代码迁移时这个替换要全局搜一遍。六、工程能编过换台机器就跑不起来现象开发机上一切正常拷到测试机双击 exe 闪退或者报「无法定位程序输入点」。原因多半是运行库没对上。用/MD编的 exe 依赖msvcrt.dll这个文件虽然系统自带但不同系统版本里的导出函数不完全一样用/MT编的 exe 自带 CRT不会缺这个但体积大。另一个可能是漏了sqlite3.dll——如果你编的是动态库版本。解决确认工程用的是/MT静态然后检查 exe 依赖。VC6 自带dumpbin跑一下# 查看 exe 依赖了哪些 DLL dumpbin /dependents Release\legacy_app.exe输出里如果只有KERNEL32.dll、USER32.dll、ADVAPI32.dll这几个系统 DLL说明 CRT 是静态链接的可以放心拷。如果看到MSVCRT.dll那就要确保目标机器上的版本不比你开发机旧。6. 安装完成后如何验证三层自检确保 sqlite3 跑通库编出来、工程链上了不等于能用。我习惯分三层验编译层、链接层、运行层。三层都过才算真的装好。6.1 三层自检清单层级检查什么通过标准编译层头文件路径对不对、宏有没有生效sqlite3.c无错误编出.obj链接层库路径、名字、运行库一致性工程链接无LNK2001/LNK2005运行层版本号、内存库、磁盘库、中文路径四项全过运行层我一般写一个独立的检查程序不进主工程单独建一个 Win32 Console 工程编出来的 exe 留在tools\下以后每次换环境都跑一遍/* sqlite_check.c —— VC6 控制台工程四步自检 */ #include stdio.h #include windows.h #include sqlite3.h int main(void) { sqlite3 *db 0; sqlite3_stmt *stmt 0; int rc; /* 第 1 步版本号确认链进来的库和头文件是同一版 */ printf([1] libversion %s\n, sqlite3_libversion()); printf( threadsafe %d\n, sqlite3_threadsafe()); /* 第 2 步内存库验证编解码和语句执行链路 */ rc sqlite3_open(:memory:, db); if (rc ! SQLITE_OK) { printf([2] open :memory: FAILED, rc%d\n, rc); return 1; } rc sqlite3_exec(db, CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT); INSERT INTO t(v) VALUES(hello);, 0, 0, 0); if (rc ! SQLITE_OK) { printf([2] exec FAILED: %s\n, sqlite3_errmsg(db)); sqlite3_close(db); return 1; } rc sqlite3_prepare_v2(db, SELECT id, v FROM t, -1, stmt, 0); if (rc SQLITE_OK sqlite3_step(stmt) SQLITE_ROW) { printf([2] row %d / %s\n, sqlite3_column_int(stmt, 0), (const char *)sqlite3_column_text(stmt, 1)); } sqlite3_finalize(stmt); sqlite3_close(db); /* 第 3 步磁盘库 中文路径验证文件系统和编码转换 */ rc sqlite3_open16(L.\\验证目录\\check.db, db); if (rc ! SQLITE_OK) { printf([3] open16 FAILED, rc%d\n, rc); if (db) sqlite3_close(db); return 1; } printf([3] disk db OK, path .\\验证目录\\check.db\n); sqlite3_close(db); /* 第 4 步64 位整数验证 __int64 那个宏真的生效了 */ { sqlite3_int64 big (sqlite3_int64)1 40; /* 约 1.1 万亿 */ printf([4] int64 %I64d\n, big); } printf(ALL PASS\n); return 0; }前三步好理解重点说第 3 步和第 4 步。第 3 步的目录要先手工建好sqlite3_open16不会帮你创建中间目录目录不存在会返回SQLITE_CANTOPEN。这一步同时验证了三件事宽字符路径能过、ANSI 源文件里的中文字面量能正确编译成 UTF-16、磁盘 IO 链路正常。如果你的工程只跑内存库这一步可以跳过只要落盘必须验。第 4 步看着像凑数其实是整篇文章的收尾检查。(sqlite3_int64)1 40这个值超过 32 位如果SQLITE_INT64_TYPE没生效、sqlite3_int64被定义成了 32 位类型结果会溢出成 0如果打印格式符用错了输出会是乱码或者截断。一行代码同时验证类型定义和格式化两件事成本最低。6.2 两条容易漏掉的硬指标除了上面四步还有两条我每次必看。中文路径的往返。第 3 步建出来的check.db用另一个工具打开看里面有没有数据。有些情况下sqlite3_open16能建文件但写不进去原因是文件权限或者路径里有不可见字符。我一般会再写一条CREATE TABLE进去然后关掉重开SELECT一次确认能读回来。写进去再读回来才算真的通。并发压一下。用SQLITE_THREADSAFE1编的库开两个线程同时对同一个连接做查询跑个几百次。如果出现崩溃或者返回SQLITE_MISUSE说明宏没生效回去检查sqlite3_threadsafe()的返回值——编译期设定是多少它就返回多少这个数字是最直接的证据。从那以后我每接一个 VC6 老工程都会先把这三层自检在这个工程上跑一遍再动业务代码。不是因为流程多严谨是因为吃过亏有一次库编好了、工程链接也过了结果上线前才发现中文路径下数据库根本没建出来所有写操作都写进了空气里日志干干净净。希望帮到你。本文还有配套的精品资源点击获取