简介:这份资源面向使用 .NET Framework 4.0 进行本地数据存储开发的程序员,提供连接 SQLite 所需的完整引用库,重点解决 32 位与 64 位目标架构下版本选择与兼容问题。压缩包共 50 个文件,约 4.26MB,包含 8 个 dll 核心程序集、8 个 exe 示例程序、8 个 config 配置、8 个 xml 文档、16 个 pdb 调试符号及 2 个 db 示例数据库,分别对应 Win32 与 x64 两套二进制包,便于按项目平台直接取用。库中 System.Data.SQLite 实现了 ADO.NET 接口,可用 SQLiteConnection、SQLiteCommand、SQLiteDataReader 等对象完成建库、查询、事务与异步操作,并附带 EF6、Linq 支持及设计器组件。已有 443 人学习下载,适合需要快速引入 SQLite 依赖、对照示例验证连接与读写流程的开发者参考。
1. 从 sqlite-netFx40-2010.rar 说起:.NET 连 SQLite 为什么绕不开位数匹配
在 .NET 项目里接 SQLite,很多人第一次翻车不是 SQL 写错,而是运行时直接抛BadImageFormatException或者Unable to load DLL 'SQLite.Interop.dll'。你明明装了 System.Data.SQLite,NuGet 也还原成功,代码里SQLiteConnection也能 new 出来,可一执行Open()就崩。这类问题的根子,八成在位数上——你的主程序是 AnyCPU 或 x64,而引用的 SQLite 原生库只有 32 位,或者反过来。
sqlite-netFx40-2010.rar这个包名本身就说明了它的定位:面向 .NET Framework 4.0、Visual Studio 2010 时代的 System.Data.SQLite 发行包,里面同时塞了 32 位和 64 位的原生 interop 库。它解决的核心诉求很具体——让一个 .NET 程序在 32 位和 64 位 Windows 上都能连 SQLite,不用为两个平台各维护一份引用。适合谁?还在维护 .NET Framework 老项目、需要单套代码兼容 x86/x64、或者被SQLite.Interop.dll加载失败折磨过的同学。下面按「选型 → 落地 → 排错 → 进阶」把这条路走通。
2. 拆开 sqlite-netFx40-2010.rar:托管程序集和原生 interop 到底谁管谁
2.1 System.Data.SQLite 的双层结构
System.Data.SQLite 不是一个单纯的托管 DLL,它是「托管壳 + 原生内核」的组合。托管部分是System.Data.SQLite.dll,里面是 ADO.NET 那套SQLiteConnection、SQLiteCommand、SQLiteDataReader;真正干活的 SQLite 引擎被编译成原生库SQLite.Interop.dll,分 x86 和 x64 两个版本。托管壳通过 P/Invoke 去调原生库,所以位数必须和进程位数一致。
这就解释了一个常见困惑:为什么System.Data.SQLite.dll只有一个,而SQLite.Interop.dll要分两个目录。托管程序集可以编译成 AnyCPU,由 CLR 决定以 32 位还是 64 位加载;但原生库没法 AnyCPU,它必须是确定的机器码。sqlite-netFx40-2010.rar这类混合包的做法,就是把两个位数的 interop 都放进去,运行时按当前进程位数挑一个加载。
提示:判断当前进程位数,最直接的是看
Environment.Is64BitProcess,别靠IntPtr.Size猜,虽然结果一样,但前者语义更清楚。
2.2 混合包目录长什么样
解压这类包,典型结构是托管 DLL 在根目录或bin下,原生库按平台分目录:
| 路径 | 内容 | 作用 |
|---|---|---|
System.Data.SQLite.dll | 托管程序集 | 提供 ADO.NET 接口 |
x86/SQLite.Interop.dll | 32 位原生库 | 供 32 位进程加载 |
x64/SQLite.Interop.dll | 64 位原生库 | 供 64 位进程加载 |
System.Data.SQLite.Linq.dll | LINQ 扩展 | 可选,用 LINQ to SQLite 才需要 |
关键点在于:托管 DLL 加载原生库时,默认会去x86或x64子目录找SQLite.Interop.dll。如果你把 interop 平铺在输出目录根下,或者只放了一个位数的版本,运行时就会找不到或加载错位数。这是后面避坑章节要重点说的。
2.3 选包时先确认三件事
拿到一个 SQLite 的 .NET 包,别急着引用,先确认:目标框架是 .NET Framework 还是 .NET Core/.NET 5+(后者用Microsoft.Data.Sqlite更顺);目标平台是 x86、x64 还是 AnyCPU;包里有没有对应位数的 interop。sqlite-netFx40-2010.rar明确是 .NET Framework 4.0 时代产物,用在 .NET Core 项目上不合适,但用在老 WinForm/WPF/ASP.NET 上正合适。选型错了,后面全是白费功夫。
3. 在 .NET Framework 项目里落地 32/64 位兼容引用
3.1 引用托管程序集并设置平台目标
第一步是把System.Data.SQLite.dll加进引用。推荐用「浏览」方式指向解压出来的 DLL,而不是随手丢到 GAC,GAC 里版本混乱时更难排查。加完后,右键项目属性,看「生成」页的「目标平台」:
- 想单套代码跑两种系统,选
AnyCPU,并取消勾选「首选 32 位」(VS2010 里叫 Prefer 32-bit,老项目默认可能勾着)。 - 想锁定 32 位,选
x86;锁定 64 位选x64。
AnyCPU 加不勾首选 32 位,意味着在 64 位系统上进程就是 64 位,会去加载 x64 的 interop;在 32 位系统上进程是 32 位,加载 x86 的 interop。这就是「一套代码兼容两平台」的实现基础。
3.2 用 MSBuild 把两个位数的 interop 都复制到输出目录
光引用托管 DLL 不够,原生库得跟着进输出目录,而且要保持x86/、x64/的子目录结构。在.csproj里加一段复制任务,比手动拷贝可靠:
<ItemGroup> <!-- 把两个位数的原生库按子目录结构复制到输出目录 --> <None Include="libs\x86\SQLite.Interop.dll"> <Link>x86\SQLite.Interop.dll</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> <None Include="libs\x64\SQLite.Interop.dll"> <Link>x64\SQLite.Interop.dll</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup>Link决定复制到输出目录后的相对路径,这里刻意保留x86\和x64\前缀,因为托管壳就是按这个约定去找的。CopyToOutputDirectory用PreserveNewest,避免每次全量覆盖拖慢生成。如果你用的是 VS2010 老式 csproj,这段要放在第一个<Import>之前,否则可能不生效。
3.3 连接字符串和最小可运行代码
引用和复制都到位后,写一段最小代码验证:
using System.Data.SQLite; class Program { static void Main() { // 连接字符串:Data Source 指向 db 文件,不存在会自动建 string connStr = "Data Source=test.db;Version=3;"; using (var conn = new SQLiteConnection(connStr)) { conn.Open(); // 这一步会触发原生库加载,位数不对就在这里炸 using (var cmd = new SQLiteCommand("CREATE TABLE IF NOT EXISTS t(id INTEGER PRIMARY KEY, name TEXT)", conn)) { cmd.ExecuteNonQuery(); } using (var cmd = new SQLiteCommand("INSERT INTO t(name) VALUES(@n)", conn)) { cmd.Parameters.AddWithValue("@n", "hello"); cmd.ExecuteNonQuery(); } Console.WriteLine("Is64BitProcess = " + Environment.Is64BitProcess); } } }conn.Open()是真正的分水岭,前面 new 对象都不会碰原生库。Version=3是 SQLite 文件格式版本,不是库版本,写 3 即可。参数化用AddWithValue能防注入,别用字符串拼接。跑通后打印Is64BitProcess,你就知道当前加载的是哪个位数的 interop。
3.4 用配置文件强制指定 interop 路径
有些场景输出目录结构被别的构建流程打乱,托管壳找不到 interop。这时可以在App.config里显式指定:
<configuration> <startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/> </startup> <runtime> <!-- 告诉 System.Data.SQLite 去哪里找原生库 --> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="x86;x64"/> </assemblyBinding> </runtime> </configuration>probing的privatePath是相对应用程序基目录的子目录列表,分号分隔。这样即使 interop 不在默认位置,CLR 也会去这些目录找。注意privatePath只对托管程序集探测有效,原生 DLL 的加载还受SQLite.Interop.dll自身搜索逻辑影响,所以最稳的还是保持标准目录结构。
4. 避坑与排查:SQLite.Interop.dll 加载失败的 5 种典型现场
4.1 现象:BadImageFormatException,提示不是有效的 Win32 应用
原因:进程位数和 interop 位数不匹配。32 位进程加载了 x64 的 interop,或反过来。常见于 AnyCPU 项目在 64 位系统上跑,但输出目录只有 x86 的 interop。
解决:确认Environment.Is64BitProcess,再检查输出目录x86/、x64/两个子目录是否都在、内容是否对应。用dumpbin /headers SQLite.Interop.dll看 machine 字段,x86 是 14C,x64 是 8664。
4.2 现象:Unable to load DLL 'SQLite.Interop.dll',找不到指定模块
原因:interop 根本没被复制到输出目录,或者被复制到了根目录而不是x86/、x64/子目录。托管壳按约定去子目录找,平铺在根下它不认。
解决:检查生成输出,确认bin\Debug\x86\SQLite.Interop.dll和bin\Debug\x64\SQLite.Interop.dll都存在。缺哪个补哪个,别图省事只放一个。
4.3 现象:本机跑得好,部署到客户机就崩
原因:开发机装了对应位数的 VC++ 运行库或系统组件,客户机没有;或者部署时只拷了 exe 没拷 interop 子目录。
解决:发布时把整个输出目录打包,别只挑 exe 和托管 DLL。用依赖检查工具确认原生库的依赖是否齐全。老版本 System.Data.SQLite 对 VC 运行库有依赖,目标机缺了会静默失败。
4.4 现象:同一个解决方案里两个项目,一个能连一个不能
原因:两个项目的目标平台设置不一致,一个 AnyCPU 一个 x86,但共用同一份输出目录,interop 被互相覆盖。
解决:统一各项目的目标平台,或者让每个项目输出到独立目录。混合平台的项目组最容易出这种玄学问题,本质是输出目录污染。
4.5 现象:升级 .NET Framework 版本后突然连不上
原因:换了新版本的 System.Data.SQLite 托管 DLL,但 interop 还是旧的,两者版本不匹配。托管壳和原生库有内部协议,版本错配会加载失败或行为异常。
解决:托管 DLL 和 interop 必须来自同一个发行包,别东拼西凑。升级时整套替换,别只换一个文件。
5. 进阶:用条件编译和运行时探测把兼容做成自动化
前面讲的都是「配置对了就能跑」,但真实项目里,你可能需要一套代码在编译期和运行期都自动适配位数。这里给两个我常用的技巧。
第一个是运行时探测加友好报错。与其让用户看到BadImageFormatException这种黑匣子,不如在启动时主动检查:
static bool CheckSqliteInterop() { string baseDir = AppDomain.CurrentDomain.BaseDirectory; // 按当前进程位数决定该找哪个子目录 string sub = Environment.Is64BitProcess ? "x64" : "x86"; string interop = Path.Combine(baseDir, sub, "SQLite.Interop.dll"); if (!File.Exists(interop)) { Console.WriteLine($"缺少 {sub} 版 SQLite.Interop.dll,请检查部署包"); return false; } return true; }这段代码在Main最开始调用,能在碰 SQLite 之前就把问题暴露出来,报错信息也直白。AppDomain.CurrentDomain.BaseDirectory比Assembly.Location更可靠,后者在某些宿主场景下会返回奇怪路径。
第二个是条件编译处理平台差异。如果你的项目里有些代码只在 32 位下走特定分支,可以用#if配合自定义符号:
#if WIN64 // 64 位专属逻辑,比如更大的缓存配置 int cacheSize = 20000; #else int cacheSize = 10000; #endif在项目属性的「生成」页里,给 x64 配置定义WIN64符号,x86 配置不定义。这样编译出来的两个版本各走各的分支,不用在运行时判断,性能更好,代码也更清晰。
最后一个习惯:每次新建或接手一个用 System.Data.SQLite 的项目,我第一件事就是打开输出目录,确认x86/和x64/两个子目录都在,interop 文件大小正常(通常几百 KB 到 1MB 多)。这个动作花不了十秒,但能省掉后面几小时的排查。位数匹配这件事,配置对了就是一次性工作,配置错了就是反复踩坑。希望帮到你。
本文还有配套的精品资源,点击获取