You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在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/x86Resources/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

火山引擎 最新活动