如何在C#中将非托管DLL合并到EXE且无需临时写入磁盘?
解决SQLite.Interop.dll嵌入资源后程序崩溃的问题
我之前也踩过这个坑!非托管的SQLite.Interop.dll和托管的System.Data.SQLite.dll的加载逻辑完全不同,AssemblyResolve钩子只负责处理托管.NET程序集的加载请求,根本管不了非托管DLL——这就是你程序直接关闭的核心原因。下面是亲测有效的解决步骤:
核心思路
非托管DLL无法直接从嵌入资源中加载,必须先将它提取到磁盘上的某个可访问路径,再让Windows加载器找到它。
具体实现步骤
1. 正确嵌入SQLite.Interop.dll资源
- 把x86和x64版本的
SQLite.Interop.dll分别添加到项目中,比如放在项目的Resources/x86和Resources/x64目录下 - 右键每个DLL文件,设置生成操作为
嵌入的资源,确保文件名(包括路径)在资源中是唯一的(比如资源名称可能是YourProjectName.Resources.x86.SQLite.Interop.dll)
2. 程序启动时提取并加载DLL
在程序入口(比如Main方法)添加以下逻辑,先提取DLL到临时目录,再让系统能找到它:
using System; using System.IO; using System.Reflection; using System.Runtime.InteropServices; class Program { [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern IntPtr LoadLibrary(string lpFileName); static void Main(string[] args) { // 确定当前进程的架构,选择对应的Interop DLL string arch = Environment.Is64BitProcess ? "x64" : "x86"; string resourceName = $"YourProjectName.Resources.{arch}.SQLite.Interop.dll"; string tempPath = Path.Combine(Path.GetTempPath(), "SQLiteInterop"); string dllPath = Path.Combine(tempPath, arch, "SQLite.Interop.dll"); // 创建目录并提取DLL(如果不存在的话) if (!File.Exists(dllPath)) { Directory.CreateDirectory(Path.GetDirectoryName(dllPath)); using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) using (var fileStream = new FileStream(dllPath, FileMode.Create)) { stream.CopyTo(fileStream); } } // 两种方式让系统找到DLL: // 方式1:将路径添加到PATH环境变量 string currentPath = Environment.GetEnvironmentVariable("PATH"); Environment.SetEnvironmentVariable("PATH", $"{Path.GetDirectoryName(dllPath)};{currentPath}", EnvironmentVariableTarget.Process); // 方式2:显式调用LoadLibrary加载DLL(二选一即可) // LoadLibrary(dllPath); // 接下来正常初始化SQLite即可 InitializeSQLite(); } private static void InitializeSQLite() { // 这里写你的SQLite初始化逻辑,比如创建连接等 using (var conn = new System.Data.SQLite.SQLiteConnection("Data Source=test.db")) { conn.Open(); // ... } } }
3. 关键注意事项
- 区分架构:一定要根据当前进程的位数加载对应的x86/x64版本的
SQLite.Interop.dll,混装必然崩溃 - 资源名称正确性:
resourceName必须和嵌入资源的完全限定名称一致,可以通过Assembly.GetExecutingAssembly().GetManifestResourceNames()方法输出所有资源名称来确认 - 权限问题:临时目录(
Path.GetTempPath())一般都有写入权限,避免用程序安装目录(可能需要管理员权限) - 重复提取优化:只在DLL不存在的时候提取,避免每次启动都写磁盘
为什么AssemblyResolve行不通?
AppDomain.CurrentDomain.AssemblyResolve事件仅在.NET运行时尝试加载托管程序集失败时触发,非托管DLL的加载是由Windows内核的加载器负责的,.NET的这个钩子根本捕获不到非托管库的加载请求——你强行在这个钩子中处理非托管DLL,相当于返回了无效的托管程序集引用,直接导致程序崩溃。
内容的提问来源于stack exchange,提问作者Ma Dude




