网上一些使用fody对c#的调用的dll文件捆包成exe文件,并不适合我,因为我的.net项目为了在低端机上使用,只安装.net2.0
这个改动主要分为两部分。,第一部分是先写一个自动通过资源加载dll文件的类
第二个部分就是在程序的入口处进行dll的动态加载.注意不是main函数
步骤一共分为三部,
首先第一步是把要打包进来的dll文件。直接拽到项目当中当做资源文件。
也可以放到指定的文件夹中.放入以后,修改文件的属性:
生成操作->嵌入的资源
复制到输出目录->如果较新则复制
第二步。编写一个dll文件的动态加载器我这里已经写好啦可以直接复制拿过去用
#region 引用
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.IO;
#endregion
#region 主体
/// <summary>
/// dll动态加载器
/// </summary>
static class DllLoader
{
#region 变量
static Dictionary<string, Assembly> DllsDic = new Dictionary<string, Assembly>();
static Dictionary<string, object> AssembliesDic = new Dictionary<string, object>();
#endregion
#region 公共函数,外部调用
/// <summary>
/// 加载
/// </summary>
public static void Load()
{
Console.WriteLine("注册dll方法调用");
//获取Program所属程序集
var ass = new StackTrace(0).GetFrame(1).GetMethod().Module.Assembly;
//判断是否已处理
if (AssembliesDic.ContainsKey(ass.FullName))
{
return;
}
//程序集加入已处理集合
AssembliesDic.Add(ass.FullName, null);
//绑定程序集加载失败事件(这里我测试了,就算重复绑也是没关系的)
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
//获取所有资源文件文件名
var res = ass.GetManifestResourceNames();
foreach (var r in res)
{
if (r.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
{
try
{
#region 实际应用中,不把dll解开放到某个地方 也是一样可以用的.具体根据自己的情况确定是否要解开注释内容
//if (r.ToLower().Contains(".dll"))
//{
// ExtractResource2File(r, GetUnpackPatch() + @"/" + r.Substring(r.IndexOf('.') + 1));
//}
#endregion
var s = ass.GetManifestResourceStream(r);
var bts = new byte[s.Length];
s.Read(bts, 0, (int)s.Length);
var da = Assembly.Load(bts);
//判断加载结果
if (DllsDic.ContainsKey(da.FullName))
{
continue;
}
DllsDic[da.FullName] = da;
}
catch (Exception e)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
Console.ResetColor();
}
}
}
}
/// <summary>
/// 解包文件
/// </summary>
/// <param name="resourceName">资源名</param>
/// <param name="filename">目标文件名</param>
#endregion
#region 私有函数,内部使用
static Assembly AssemblyResolve(object sender, ResolveEventArgs args)
{
//程序集
Assembly ass;
//获取加载失败的程序集的全名
var assName = new AssemblyName(args.Name).FullName;
//判断Dlls集合中是否有已加载的同名程序集
if (DllsDic.TryGetValue(assName, out ass) && ass != null)
{
DllsDic[assName] = null;//如果有则置空并返回
return ass;
}
else
{
throw new DllNotFoundException(assName);//否则抛出加载失败的异常
}
}
static void ExtractResource2File(string resourceName, string filename)
{
Console.WriteLine("解压文件:{0} {1}", resourceName, filename);
if (!System.IO.File.Exists(filename))
{
using (System.IO.Stream s = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
{
using (System.IO.FileStream fs = new System.IO.FileStream(filename, System.IO.FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
{
byte[] b = new byte[s.Length];
s.Read(b, 0, b.Length);
fs.Write(b, 0, b.Length);
}
}
}
}
/// <summary>
/// 获取解压目录,根据自己的需求改位置,如果解压到系统文件夹,需要有相关权限,可能会被杀毒软件误报
/// </summary>
/// <returns></returns>
static string GetUnpackPatch()
{
//string strPath = "C:";
string strPath = AppDomain.CurrentDomain.BaseDirectory;
if (!Directory.Exists(strPath))
{
Directory.CreateDirectory(strPath);
}
return strPath;
}
#endregion
}
#endregion
把这个文件直接放到项目中即可
第三步在program.cs文件中插入相应的自动加载dll文件的代码.
默认新建的一个控制台应用程序中program 这个文件中只有main函数
在main函数上方加入Program函数,并进行dllloader的调用
static Program()
{
Console.WriteLine("开始检查dll");
DllLoader.Load();
Console.WriteLine("结束检查dll");
}
修改后的Program.cs文件是这样的:
using System;
using System.IO;
using NPOI.SS.UserModel;
using NPOI.HSSF.UserModel;
namespace Dll打包进Exe
{
class Program
{
static Program()
{
Console.WriteLine("开始检查dll");
DllLoader.Load();
Console.WriteLine("结束检查dll");
}
static void Main(string[] args)
{
//Dictionary<string, object> waitJsonObj = new Dictionary<string, object>();
//waitJsonObj.Add("ce", "测试内容");
//waitJsonObj.Add("i", 12321321);
//string json = Newtonsoft.Json.JsonConvert.SerializeObject(waitJsonObj);
//Console.WriteLine(json);
//Console.ReadLine();
IWorkbook workbook = new HSSFWorkbook();
ISheet sheet = workbook.CreateSheet();
IRow row0 = sheet.CreateRow(0);
ICell cella = row0.CreateCell(0);
cella.SetCellValue("测试新表格的表头 行1 列a");
SaveSheet2NewFile("c:\\ceshi.xls", sheet);
Console.WriteLine("写入文件完成");
Console.ReadLine();
}
public static int SaveSheet2NewFile(string fileName, ISheet sheet)
{
int i = 0;
int j = 0;
int count = 0;
//IWorkbook workbook = null;
FileStream fs = null;
try
{
fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write);
//if (fileName.IndexOf(".xlsx") > 0) // 2007版本
// workbook = new XSSFWorkbook();
//else if (fileName.IndexOf(".xls") > 0) // 2003版本
// workbook = new HSSFWorkbook();
}
catch (Exception err)
{
Console.WriteLine(err.Message);
//MessageBox.Show(err.Message);
return -1;
}
try
{
//ISheet newsheet = workbook.CreateSheet();
sheet.Workbook.Write(fs);
//workbook.Write(fs); //写入到excel
fs.Close();
return count;
}
catch (Exception ex)
{
Console.WriteLine("err:",ex.Message);
//MessageBox.Show("Exception: " + ex.Message);
fs.Close();
return -1;
}
}
}
}
该例子是把NPOI这个表格处理的相关dll打包到exe文件中
注意,如果dll可能会变动,比如被当做资源的a.dll是你的a.csproj项目生成的,那么你在编辑了a项目以后,a项目会生成新的dll,所以,加入到当前项目中的dll应该是在 a项目下的bin/debug或者release目录下的xx.dll,如果你的主项目还在测试中,调试模式是debug,那么资源文件选择用a项目的bin/debug下面的文件,如果是release,也要对应使用a项目的对应release生成的dll,不然可能会因为dll调试模式问题造成不能有效装载.
生成项目之前,dll还是正常引用,但是生成了以后,虽然bin/debug目录下会有相应的dll,但是不用管的,直接把生成的exe文件拿出来是可以用的.你会发现exe文件的大小是包含了自身大小和装载进去的dll文件的大小的和
还有一个补充的是,如果包进来的dll还引用了其他的dll,或者是本身这个被引用的dll所需要引用的dll的版本会和系统环境产生兼容问题,要把引用的dll引用的dll也加到资源里面.比如 NPOI的dll如果创建xlsx文件的话,xssfworkbook类需要用到CSharpCode.SharpZipLib.dll这个文件,但是系统环境和vs的debug环境不一致导致了无法创建xlsx文件.
在关于:未能加载文件或程序集“ICSharpCode.SharpZipLib”或它的某一个依赖项异常的解决方案 - 追逐时光者 - 博客园
这个文章中下载的文件.
技巧:
1.
dll也是可以打包dll在里面的
2.
动态调用dll,(使用Assembly.LoadFile的方式)时,a使用动态引用引用b,但是a项目中直接使用项目引用的方式引用了b,要把b引用先删除才能测试出来动态引用时候 b是否有依赖项不兼容.
3.动态引用dll的时候,添加bin//debug目录下的dll,移除了项目本身的引用方式对<项目>的引用的话,但是有把被引用的b项目加入到解决方案中,在b项目中设置断点也是能被检测到的.