C# 将 DLL 打包嵌入资源并实现自动加载详解

Theo
Theo
发布时间:2026-10-14 | 5人浏览过
原创

在 C# 开发中,将外部 DLL 嵌入程序资源是解决运行时依赖缺失、实现单文件发布的常用技术。通过监听 AppDomain.CurrentDomain.AssemblyResolve 事件,可以在系统找不到对应程序集时,从嵌入的资源流中动态加载 DLL,从而避免分发多个文件。

默认加载行为与依赖问题

假设项目中引用了一个名为 TestLibrary1.dll 的第三方库,其中包含一个测试类:

namespace TestLibrary1
{
    public class Test
    {
        public void Point()
        {
            Console.WriteLine("aaabbbccc");
        }
    }
}

在主程序中调用该方法:

using System;

namespace ConsoleApplication5
{
    class Program
    {
        static void Main(string[] args)
        {
            var test = new TestLibrary1.Test();
            test.Point();
            Console.ReadLine();
        }
    }
}

当生成的 .exe 与 TestLibrary1.dll 位于同一目录时,程序正常运行。但如果只发送 .exe 文件而未附带 DLL,运行时便会因找不到依赖而崩溃:

解决方案:资源嵌入与事件劫持

C# 提供了 AppDomain.CurrentDomain.AssemblyResolve 事件,用于捕获程序集加载失败的情况。我们可以将 DLL 添加为项目的“嵌入式资源”,并在该事件中从资源流读取字节并加载。

具体操作步骤如下:

第1步:将 DLL 添加为嵌入式资源

在 Visual Studio 项目中右键添加现有文件(如 TestLibrary1.dll),并将文件的“生成操作”属性设置为“嵌入的资源”。这确保了 DLL 会被打包进最终的 .exe 文件中。

第2步:绑定 AssemblyResolve 事件

需要在任何使用到该程序集的代码执行之前绑定事件,通常在静态构造函数中完成:

class Program
{
    static Program()
    {
        // 必须在使用 TestLibrary1 之前绑定事件,否则首次加载时会直接抛出异常
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    }

    static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        // 获取失败的程序集全名
        var assName = new AssemblyName(args.Name).FullName;
        
        // 匹配目标程序集名称(可根据需要修改判断逻辑)
        if (args.Name == "TestLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")
        {
            // 从嵌入资源中读取 DLL 字节
            using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ConsoleApplication5.TestLibrary1.dll"))
            {
                var bytes = new byte[stream.Length];
                stream.Read(bytes, 0, (int)stream.Length);
                // 从字节数组加载程序集
                return Assembly.Load(bytes);
            }
        }
        
        // 如果没找到匹配的 DLL,抛出原始异常
        throw new DllNotFoundException(assName);
    }

    static void Main(string[] args)
    {
        var test = new TestLibrary1.Test();
        test.Point();
        Console.ReadLine();
    }
}

配置完成后,程序即可独立运行,无需携带额外的 DLL 文件:

通用方案:批量加载多个资源 DLL

若项目中包含大量 DLL,逐个判断名字不仅繁琐且难以维护。可以通过反射遍历所有嵌入资源,自动加载所有后缀为 .dll 的文件,并利用字典缓存已加载的程序集以提高性能。

第1步:创建通用加载类

以下 LoadResourceDll 类可复用。它通过 StackTrace 获取调用者的程序集,扫描其中的嵌入式资源,并将所有 .dll 预加载到内存字典中。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;

namespace blqw
{
    /// <summary> 载入资源中的动态链接库(dll)文件
    /// </summary>
    static class LoadResourceDll
    {
        static Dictionary<string, Assembly> Dlls = new Dictionary<string, Assembly>();
        static Dictionary<string, object> Assemblies = new Dictionary<string, object>();

        static Assembly AssemblyResolve(object sender, ResolveEventArgs args)
        {
            // 检查是否已在缓存中
            if (Dlls.TryGetValue(new AssemblyName(args.Name).FullName, out Assembly ass) && ass != null)
            {
                Dlls[new AssemblyName(args.Name).FullName] = null; // 已加载过,置空防止重复查找
                return ass;
            }
            else
            {
                throw new DllNotFoundException(args.Name);
            }
        }

        /// <summary> 注册资源中的dll
        /// </summary>
        public static void RegistDLL()
        {
            // 获取调用者的程序集
            var ass = new StackTrace(0).GetFrame(1).GetMethod().Module.Assembly;
            
            // 防止重复注册
            if (Assemblies.ContainsKey(ass.FullName))
            {
                return;
            }
            Assemblies.Add(ass.FullName, null);
            
            // 绑定全局解析事件
            AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
            
            // 遍历所有嵌入资源名称
            var res = ass.GetManifestResourceNames();
            foreach (var r in res)
            {
                // 仅处理 .dll 文件
                if (r.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
                {
                    try
                    {
                        using (var s = ass.GetManifestResourceStream(r))
                        {
                            var bts = new byte[s.Length];
                            s.Read(bts, 0, (int)s.Length);
                            var da = Assembly.Load(bts);
                            
                            // 存入缓存字典,键为程序集全名
                            if (!Dlls.ContainsKey(da.FullName))
                            {
                                Dlls[da.FullName] = da;
                            }
                        }
                    }
                    catch
                    {
                        // 忽略无法加载的资源(如损坏的文件)
                    }
                }
            }
        }
    }
}

第2步:在项目启动处调用注册方法

只需在任何逻辑执行前调用一次 RegistDLL(),项目中的所有嵌入式 DLL 都会被自动检测并注册。当系统报出任何 DLL 缺失时,事件处理程序会先从字典中查找是否存在同名资源,存在则直接返回,从而实现透明加载。

完成判断

编译运行后,确认主程序能正常实例化并调用不同 DLL 中的类型,且输出目录下无额外产生的 DLL 文件,即表示资源加载配置成功:

以上就是将 C# DLL 打包嵌入资源并实现自动加载的详细内容,更多关于 C# 程序集管理与单文件发布优化的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表