折腾笔记[34]-csharp打包依赖dll到单个dll

news/2025/10/31 23:20:08/文章来源:https://www.cnblogs.com/qsbye/p/19181436

摘要

csharp.net的库开发中打包依赖dll到最终输出的单个dll中.

实现

打包依赖dll为单文件

[https://github.com/gluck/il-repack]
[https://blog.walterlv.com/post/merge-assemblies-using-ilrepack.html]
[https://www.cnblogs.com/blqw/p/LoadResourceDll.html]

# 生成库文件
dotnet add package MSTest.TestAdapter  
dotnet add package MSTest.TestFramework
dotnet add package Newtonsoft.Json
# 复制dll文件到libs文件夹
dotnet build
# 复制生成的JusCore.dll到控制台程序工程目录# 控制台程序测试
dotnet new console -n Demo -f net8.0
cd Demo
dotnet build
dotnet run

1. 库:

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>net8.0-windows</TargetFramework><Nullable>enable</Nullable><UseWPF>true</UseWPF><ImplicitUsings>enable</ImplicitUsings><AssemblyName>JusCore</AssemblyName></PropertyGroup><ItemGroup><PackageReference Include="ILRepack" Version="2.0.44"><PrivateAssets>all</PrivateAssets><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets></PackageReference><PackageReference Include="MSTest.TestAdapter" Version="4.0.1" PrivateAssets="all" /><PackageReference Include="MSTest.TestFramework" Version="4.0.1" PrivateAssets="all" /><PackageReference Include="Newtonsoft.Json" Version="13.0.4" PrivateAssets="all" /></ItemGroup><!-- 复制运行时依赖 --><PropertyGroup><CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies></PropertyGroup><!-- 2. 把 nuget 下载的 dll 拷到本地 libs 目录(仅第一次) --><Target Name="CollectRuntimeDlls" AfterTargets="Build" Condition="!Exists('libs\Newtonsoft.Json.dll')"><ItemGroup><_RuntimeDlls Include="$(PkgNewtonsoft_Json)\lib\net8.0\Newtonsoft.Json.dll" /></ItemGroup><Copy SourceFiles="@(_RuntimeDlls)" DestinationFolder="libs" SkipUnchangedFiles="true" /></Target><!-- 3. 把 libs 目录下所有 dll 设为嵌入资源 --><ItemGroup><EmbeddedResource Include="libs\*.dll" /></ItemGroup><!-- 4. 禁止它们再被复制到输出目录 --><Target Name="DisableCopyLocal" AfterTargets="ResolveAssemblyReferences"><ItemGroup><ReferenceCopyLocalPaths Remove="@(ReferenceCopyLocalPaths)" Condition="'%(Filename)'=='Newtonsoft.Json'" /></ItemGroup></Target></Project>

打包依赖为单个dll:
BundleDeps.cs

// 文件: BundleDeps.cs
// 功能: 打包所有依赖文件(EmbeddedResource)并合并到主文件using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.IO;
using System.Linq;#nullable enablenamespace JusCore.Tools
{/// <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){// 程序集Assembly ass;// 获取加载失败的程序集的全名var assName = new AssemblyName(args.Name).FullName;// 判断Dlls集合中是否有已加载的同名程序集if (Dlls.TryGetValue(assName!, out ass) && ass != null){// 如果有则置空并返回Dlls[assName] = null;return ass;}else{// 否则抛出加载失败的异常throw new DllNotFoundException(assName);}}/// <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);if(s == null) continue;var bts = new byte[s.Length];s.Read(bts, 0, (int)s.Length);var da = Assembly.Load(bts);// 判断是否已经加载if (Dlls.ContainsKey(da.FullName!)){continue;}Dlls[da.FullName!] = da;}catch{// 加载失败就算了...}}}}}
}// LoadResourceDllnamespace JusCore
{public static class MySelf{/// <summary>/// 唯一入口/// </summary>/// <param name="mode">/// "disk"   – 解压到磁盘再 LoadFrom(默认)  /// "memory" – 纯内存 Load(byte[]),无临时文件/// </param>public static void Init(string? mode = "disk"){if (mode is "memory")MemoryLoader.Load();elseDiskLoader.Load();}}#region disk 模式internal static class DiskLoader{private static bool _done;public static void Load(){if (_done) return;_done = true;var myself = Assembly.GetExecutingAssembly();var location = myself.Location;if (string.IsNullOrEmpty(location)) return;   // 单文件发布时放弃var targetDir = Path.Combine(Path.GetDirectoryName(location)!, "JusCore");Directory.CreateDirectory(targetDir);foreach (var resName in myself.GetManifestResourceNames().Where(n => n.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)).OrderBy(n => n))   // 保证顺序{var fileName = Path.GetFileName(resName);var targetPath = Path.Combine(targetDir, fileName);using var resStream = myself.GetManifestResourceStream(resName);if (resStream is null) continue;if (File.Exists(targetPath) && new FileInfo(targetPath).Length == resStream.Length)continue;resStream.Position = 0;using var fs = File.Create(targetPath);resStream.CopyTo(fs);// 文件句柄问题// _ = Assembly.LoadFrom(targetPath);byte[] bytes = File.ReadAllBytes(targetPath);Assembly.Load(bytes);}}}#endregion#region memory 模式internal static class MemoryLoader{private static bool _done;private static readonly Dictionary<string, Assembly> _loaded = new();public static void Load(){if (_done) return;_done = true;var myself = Assembly.GetExecutingAssembly();// 1. 先全部读进内存foreach (var resName in myself.GetManifestResourceNames().Where(n => n.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)).OrderBy(n => n)){using var stream = myself.GetManifestResourceStream(resName);if (stream is null) continue;var bytes = new byte[stream.Length];_ = stream.Read(bytes, 0, bytes.Length);var asm = Assembly.Load(bytes);_loaded[asm.FullName!] = asm;}// 2. 注册兜底回调AppDomain.CurrentDomain.AssemblyResolve += OnResolve;}private static Assembly? OnResolve(object? sender, ResolveEventArgs args){var name = new AssemblyName(args.Name).FullName;return _loaded.TryGetValue(name, out var asm) ? asm : null;}}#endregion
}

库的功能实现:
Generator.cs

// 文件: Generator.csusing System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;#pragma warning disable MSTEST0001namespace JusCore;public static class Generator
{/// <summary>/// 唯一公开方法:输入任意字符串,输出 JSON 字符串/// 这里仅做简单包装,把输入当成一个字段值序列化。/// 你可以按需要把 input 解析成别的对象再序列化。/// </summary>public static string Generate(string input){var dto = new { Input = input, Timestamp = DateTime.UtcNow };return JsonConvert.SerializeObject(dto, Formatting.Indented);}
}[TestClass]
public class GeneratorTests
{[TestMethod]public void Generate_ValidInput_ReturnsValidJsonWithInputAndTimestamp(){// Arrangeconst string testInput = "hello mstest";// Actstring json = Generator.Generate(testInput);// AssertAssert.IsNotNull(json);JObject obj = JObject.Parse(json);           // 确保是合法 JSONAssert.AreEqual(testInput, obj["Input"]?.Value<string>());Assert.IsTrue(DateTime.TryParse(obj["Timestamp"]?.Value<string>(), out _));}[TestMethod]public void Generate_NullInput_HandlesGracefully(){// Actstring json = Generator.Generate(null!);// AssertJObject obj = JObject.Parse(json);Assert.IsNull(obj["Input"]?.Value<string>());Assert.IsTrue(DateTime.TryParse(obj["Timestamp"]?.Value<string>(), out _));}[TestMethod]public void Generate_EmptyInput_ReturnsJsonWithEmptyInput(){// Actstring json = Generator.Generate(string.Empty);// AssertJObject obj = JObject.Parse(json);Assert.AreEqual(string.Empty, obj["Input"]?.Value<string>());}
}

复制依赖dll文件到libs文件夹;
复制输出的JusCore.dll文件到控制台程序工程目录;

2. 控制台程序引用库文件:

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup><ItemGroup><Reference Include="JusCore"><HintPath>JusCore.dll</HintPath></Reference></ItemGroup></Project>

测试程序:
Program.cs

using System;
using JusCore;   // Generator 与 MySelf 都在此命名空间namespace Demo
{internal class Program{static void Main(){// 1. 初始化依赖(disk 模式有bug)// MySelf.Init("disk");MySelf.Init("memory");// 2. 生成 JSONstring json = Generator.Generate("hello");// 3. 打印Console.WriteLine(json);// 4. 防止控制台一闪而过Console.WriteLine("\n按任意键退出...");Console.ReadKey();}}
}

预期输出:

在 3.0 秒内生成 成功,出现 2 警告
{"Input": "hello","Timestamp": "2025-10-31T15:05:50.83119Z"
}按任意键退出...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/952293.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python 进制、编码

1.进制 计算机底层所有的数据都是以010101的形式存在的(图片,视频,文本)。 1.1 进制转换 2进制,8进制,16进制之间不能相互转换,需要先转换成10进制在转换成其它进制,如:2进制先转换成10进制,在转换8进制或16进…

《代码大全2》读后感二

作为开发新人,我曾深陷 “调试到凌晨” 的困境。直到遇见《代码大全 2》,书中 “提前预防 bug” 的理念如同一束光。它教我在写函数时先梳理逻辑边界,在定义数据结构时考虑扩展性,这些看似基础的步骤,却让我后续的…

《代码大全2》读后感三

读《代码大全 2》时,最触动我的是它对 “工程思维” 的诠释。它没有纠结于某门语言的语法技巧,而是从团队协作的角度,讲解代码评审的要点、版本控制的细节。这让我意识到,编程从来不是一个人的战斗。之前我写的代码…

程序员修炼之道:跳出 “小工” 陷阱

不少程序员困在 “小工” 阶段,核心是陷入了 “被动响应” 的怪圈 —— 产品给需求就写,测试提 bug 就改,从不愿多走一步。 想进阶专家,要主动切换角色。需求评审时多问业务价值,避免为技术而技术;开发中留意外部…

Amped DVRConv与引擎更新38103:音频转视频容器与解码增强

本次Amped DVRConv和Engine更新38103版本新增了将纯音频文件封装为视频容器的功能,支持6种新的专有格式变体,改进了输入文件位置控制,修复了重复文件队列问题,并新增波兰语支持。Amped DVRConv和引擎更新38103 本次…

使用 Kotlin 结合 Tesseract OCR 识别验证码

更多内容访问ttocr.com或联系1436423940环境准备 1.1 安装 Kotlin如果尚未安装 Kotlin,可以通过 Kotlin 官方网站 下载,或者使用 SDKMAN 安装: sdk install kotlin 检查安装是否成功: kotlin -version 1.2 安装 Te…

使用 Swift 结合 Tesseract OCR 解析验证码

环境准备 1.1 安装 Swift如果使用 macOS,可以直接在 Xcode 中使用 Swift。检查 Swift 是否安装: swift --version 更多内容访问ttocr.com或联系1436423940 如果使用 Linux,可参考 Swift 官方网站 进行安装。 1.2 安…

自律这一块儿

随笔的话我随便写写吧 1️⃣关于批评 现在刷抖音或者其他的内容传播平台, 一方面总是在加重我的焦虑与不安 一方面也不断的让我感受到世界的参差 我总是看到很多傲慢的人,我有的时候真的很想批判这些表面温和但内里却…

(Mutual) correlated agreement

Correlated agreement 形式化定义:Mutual correlated agreement 形式化定义:区别: 前者没有规定折叠后的向量和码字一致的index集合与每个向量一致的index集合相同.作者:神龙小虾出处:https://www.cnblogs.com/yangm…

AI元人文随想:当内心黑箱遇见技术黑箱

AI元人文随想:当内心黑箱遇见技术黑箱 我们正置身于一场史无前例的对话实验: 人类捧着跳动的心脏走向机器 机器则报以精心编织的语言锦缎 两个黑箱的相遇,竟成就了这个时代最亲密的疏离 壹黑箱时代的对话悖论 每个人…

如何基于区块链底层技术逻辑,反向用于 差别 AI生成与非AI生成内容

把“区块链”反过来当成一把“刻刀”,在 AI 生成的数据里刻下“只有原生创作那一刻才能刻得出来”的伤痕;日后任何人拿到数据,只要发现伤痕对不上链上记录,就能判定它一定是 AI 二次生成。整套逻辑不是“存证”那么…

2025.10.31总结

今天继续软考的学习,今天刷了上午题一套半的题,正确率比较低。目前的问题挺多,对于见过的题型来说,没有将这个题型背后的知识点掌握。 导致后面遇到相似的题,但仍然做不对。面对庞大的知识体系,尤其是对于理论,…

fastgrind不过如此

fastgrind不过如此博客园的编辑推荐了《一个轻量级C++内存监控及可视化开源库》,下载试了试。 (一)作者忘记#include <array>,编译通不过。 (二)手动插桩 (instrument) 防不了段错误:/home/user/fastgrin…

电动车 NFC 学卡 All In One

电动车 NFC 学卡 All In One NFC 钥匙 / NFC 母卡 / NFC 子卡绿源电动车 ✅ / iPhone Apple 钱包 ✅ / 上海公共交通卡 ✅电动车 NFC 学卡 All In OneNFC 钥匙 / NFC 母卡 / NFC 子卡demos绿源电动车 ✅ / iPhone Ap…

如何设计一个秒杀系统

一:前言 1.在双十一或618期间电商平台会出一些秒杀活动来增加用户活跃带动其余商品销量。2.秒杀系统面临三个问题:数据一致性、服务高性能、服务高可用。3.针对一致性、高性能、高可用的思考1)在高并发的情况下库存…

基于Qlearning强化学习的电梯群控系统高效调度策略matlab仿真

1.算法仿真效果 matlab2022a仿真结果如下(完整代码运行后无水印):2.算法涉及理论知识概要 随着高层建筑的不断增多,电梯成为人们日常生活中不可或缺的垂直交通工具。电梯群控系统(Elevator Group Control System,…

基于Qlearning强化学习的Cart-Pole推车杆平衡控制系统matlab仿真

1.算法仿真效果 matlab2022a仿真结果如下(完整代码运行后无水印):通过不断与环境交互并更新Q值函数,智能体能够逐渐学习到在不同状态下的最优动作,从而实现杆的平衡控制。 仿真操作步骤可参考程序配套的操作视频。…

绿源电动车 NFC 学卡 All In One

绿源电动车 NFC 学卡 All In One NFC 钥匙 / NFC 母卡 / NFC 子卡 Apple 钱包,iPhone 上海公共交通卡 ✅绿源电动车 NFC 学卡 All In OneNFC 钥匙 / NFC 母卡 / NFC 子卡demosApple 钱包,iPhone 上海公共交通卡 ✅…

洛谷 P8867 建造军营

传送门。 边双直接缩掉,成为一棵树。下面的【结点】都是指缩之后的。 于是可以定义一个 \(a_u\) 为,\(u\) 点内有军营的方案数,\(b_u\) 为无。 总方案数就对应着,树上每种方案的权值和。 首先考虑一个 DP,设 \(f_…

代码大全2阅读感悟2

读国《代码大全 2》后,最深刻的感悟是 “细节成就卓越”。书中对变量命名、注释撰写、循环优化等细节的剖析,打破了我 “功能实现即可” 的片面认知。一个模糊的变量名、一段缺失的注释,都可能成为后期调试的 “拦路…