CAD.net开发动态加载dll
我们 http:// cad.net 开发都会面临一个问题,加载了的dll无法实现覆盖操作,也就是cad一直打开的状态下,netload两次版本不一样的dll,它只会用第一次载入的...也没法做到热插拔...
testa项目代码
namespace testa
public class MyCommands
[CommandMethod("testa")]
public static void testa()
Document doc = Acap.DocumentManager.MdiActiveDocument;
Editor ed;
if (doc != null)
ed = doc.Editor;
ed.WriteMessage("\r\n自带函数testa.");
[CommandMethod("gggg")]
public void gggg()
Document doc = Acap.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
if (doc != null)
ed.WriteMessage("\r\n **********gggg");
testb.MyCommands.TestBHello();
}
testb项目代码
namespace testb
public class MyCommands
public static void TestBHello()
Document doc = Acap.DocumentManager.MdiActiveDocument;
Editor ed;
if (doc != null)
ed = doc.Editor;
ed.WriteMessage("************testb的Hello");
testc.MyCommands.TestcHello();
[CommandMethod("testb")]
public static void testb()
Document doc = Acap.DocumentManager.MdiActiveDocument;
Editor ed;
if (doc != null)
ed = doc.Editor;
ed.WriteMessage("\r\n自带函数testb.");
}
testc项目代码
namespace testc
public class MyCommands
public static void TestcHello()
Document doc = Acap.DocumentManager.MdiActiveDocument;
Editor ed;
if (doc != null)
ed = doc.Editor;
ed.WriteMessage("************testc的Hello");
[CommandMethod("testc")]
public static void testc()
Document doc = Acap.DocumentManager.MdiActiveDocument;
Editor ed;
if (doc != null)
ed = doc.Editor;
ed.WriteMessage("\r\n自带函数testc");
}
迭代版本号
必须更改版本号最后是*,否则无法重复加载(所有)
如果想加载时候动态修改dll的版本号,需要学习PE读写.(此文略)
net framework要直接编辑项目文件.csproj,启用由vs迭代版本号:
<PropertyGroup>
<Deterministic>False</Deterministic>
</PropertyGroup>
然后修改AssemblyInfo.cs
net standard只需要增加.csproj的这里,没有自己加一个:
<PropertyGroup>
<AssemblyVersion>1.0.0.*</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<Deterministic>False</Deterministic>
</PropertyGroup>
调用方法
var ad = new AssemblyDependent(path);
var msg = ad.Load();
bool allyes = true;
foreach (var item in msg)
if (!item.LoadYes)
ed.WriteMessage(Environment.NewLine + "**" + item.Path +
Environment.NewLine + "**此文件已加载过,重复名称,重复版本号,本次不加载!" +
Environment.NewLine);
allyes = false;
if (allyes)
ed.WriteMessage(Environment.NewLine + "**链式加载成功!" + Environment.NewLine);
}
链式加载完整代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
namespace RemoteAccess
[Serializable]
public class AssemblyDependent : IDisposable
string _dllFile;
/// <summary>
/// cad程序域依赖_内存区(不可以卸载)
/// </summary>
private Assembly[] _cadAs;
/// <summary>
/// cad程序域依赖_映射区(不可以卸载)
/// </summary>
private Assembly[] _cadAsRef;
/// <summary>
/// 加载DLL成功后获取到的程序集
/// </summary>
public List<Assembly> MyLoadAssemblys { get; private set; }
/// <summary>
/// 当前域加载事件,运行时出错的话,就靠这个事件来解决
/// </summary>
public event ResolveEventHandler CurrentDomainAssemblyResolveEvent
AppDomain.CurrentDomain.AssemblyResolve += value;
remove
AppDomain.CurrentDomain.AssemblyResolve -= value;
/// <summary>
/// 链式加载dll依赖
/// </summary>
/// <param name="dllFile"></param>
public AssemblyDependent(string dllFile)
_dllFile = Path.GetFullPath(dllFile);//相对路径要先转换 Path.GetFullPath(dllFile);
//cad程序集的依赖
_cadAs = AppDomain.CurrentDomain.GetAssemblies();
//映射区
_cadAsRef = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();
//被加载的都存放在这里
MyLoadAssemblys = new List<Assembly>();
/// <summary>
/// 返回的类型,描述加载的错误
/// </summary>
public class LoadDllMessage
public string Path;
public bool LoadYes;
public LoadDllMessage(string path, bool loadYes)
Path = path;
LoadYes = loadYes;
public override string ToString()
if (LoadYes)
return "加载成功:" + Path;
return "加载失败:" + Path;
/// <summary>
/// 加载信息集合
/// </summary>
List<LoadDllMessage> LoadYesList;
bool _byteLoad;
/// <summary>
/// 加载程序集
/// </summary>
/// <param name="byteLoad">true字节加载,false文件加载</param>
/// <returns>返回加载链的</returns>
public void Load(bool byteLoad = true)
_byteLoad = byteLoad;
if (!File.Exists(_dllFile))
throw new ArgumentNullException("路径不存在");
LoadYesList = new List<LoadDllMessage>();
//查询加载链之后再逆向加载,确保前面不丢失
var allRefs = GetAllRefPaths(_dllFile);
allRefs.Reverse();
foreach (var path in allRefs)
//路径转程序集名
string assName = AssemblyName.GetAssemblyName(path).FullName;
//路径转程序集名
var assembly = _cadAs.FirstOrDefault(a => a.FullName == assName);
if (assembly != null)
LoadYesList.Add(new LoadDllMessage(path, false));//版本号没变不加载
continue;
byte[] buffer = null;
bool flag = true;
//实现字节加载
if (path == _dllFile)
LoadOK = true;
#if DEBUG
//为了实现Debug时候出现断点,见链接,加依赖
// https://www.cnblogs.com/DasonKwok/p/10510218.html
// https://www.cnblogs.com/DasonKwok/p/10523279.html
var dir = Path.GetDirectoryName(path);
var pdbName = Path.GetFileNameWithoutExtension(path) + ".pdb";
var pdbFullName = Path.Combine(dir, pdbName);
if (File.Exists(pdbFullName) && _byteLoad)
var pdbbuffer = File.ReadAllBytes(pdbFullName);
buffer = File.ReadAllBytes(path);
var ass = Assembly.Load(buffer, pdbbuffer);
MyLoadAssemblys.Add(ass);
flag = false;
#endif
if (flag)
Assembly ass = null;
if (_byteLoad)
buffer = File.ReadAllBytes(path);
ass = Assembly.Load(buffer);
ass = Assembly.LoadFile(path);
MyLoadAssemblys.Add(ass);
LoadYesList.Add(new LoadDllMessage(path, true));//加载成功
catch
LoadYesList.Add(new LoadDllMessage(path, false));//错误造成
MyLoadAssemblys.Reverse();
//链条后面的不再理会,因为相同的dll引用辨识无意义
/// <summary>
/// 第一个dll加载是否成功
/// </summary>
public bool LoadOK { get; private set; }
/// <summary>
/// 加载出错信息
/// </summary>
public string LoadErrorMessage
var sb = new StringBuilder();
bool allyes = true;
foreach (var item in LoadYesList)
if (!item.LoadYes)
sb.Append(Environment.NewLine + "** 此文件已加载过,重复名称,重复版本号,本次不加载!");
sb.Append(Environment.NewLine + item.ToString());
sb.Append(Environment.NewLine);
allyes = false;
if (allyes)
sb.Append(Environment.NewLine + "** 链式加载成功!");
sb.Append(Environment.NewLine);
return sb.ToString();
/// <summary>
/// 获取加载链
/// </summary>
/// <param name="dll"></param>
/// <param name="dlls"></param>
/// <returns></returns>
List<string> GetAllRefPaths(string dll, List<string> dlls = null)
if (dlls == null)
dlls = new List<string>();
if (dlls.Contains(dll) || !File.Exists(dll))
return dlls;
dlls.Add(dll);
//路径转程序集名
string assName = AssemblyName.GetAssemblyName(dll).FullName;
//在当前程序域的assemblyAs内存区和assemblyAsRef映射区找这个程序集名
Assembly assemblyAs = _cadAs.FirstOrDefault(a => a.FullName == assName);
Assembly assemblyAsRef;
//内存区有表示加载过
//映射区有表示查找过但没有加载(一般来说不存在.只是debug会注释掉Assembly.Load的时候用来测试)
if (assemblyAs != null)
assemblyAsRef = assemblyAs;
assemblyAsRef = _cadAsRef.FirstOrDefault(a => a.FullName == assName);
//内存区和映射区都没有的话就把dll加载到映射区,用来找依赖表
if (assemblyAsRef == null)
// assemblyAsRef = Assembly.ReflectionOnlyLoad(dll); 没有依赖会直接报错
var byteRef = File.ReadAllBytes(dll);
assemblyAsRef = Assembly.ReflectionOnlyLoad(byteRef);
//遍历依赖,如果存在dll拖拉加载目录就加入dlls集合
foreach (var assemblyName in assemblyAsRef.GetReferencedAssemblies())
//dll拖拉加载路径-搜索路径(可以增加到这个dll下面的所有文件夹?)
string directoryName = Path.GetDirectoryName(dll);
var path = directoryName + "\\" + assemblyName.Name;
var paths = new string[]
path + ".dll",
path + ".exe"
foreach (var patha in paths)
GetAllRefPaths(patha, dlls);
return dlls;
/// <summary>
/// 递归删除文件夹目录及文件
/// </summary>
/// <param name="dir"></param>
/// <returns></returns>
static void DeleteFolder(string dir)
if (Directory.Exists(dir)) //如果存在这个文件夹删除之
foreach (string d in Directory.GetFileSystemEntries(dir))
if (File.Exists(d))
File.Delete(d); //直接删除其中的文件
DeleteFolder(d); //递归删除子文件夹
Directory.Delete(dir, true); //删除已空文件夹
/// <summary>
/// Debug的时候删除obj目录,防止占用
/// </summary>
public void DebugDelObjFiles()
var filename = Path.GetFileNameWithoutExtension(_dllFile);
var path = Path.GetDirectoryName(_dllFile);
var pdb = path + "\\" + filename + ".pdb";
if (File.Exists(pdb))
File.Delete(pdb);
var list = path.Split('\\');
if (list[list.Length - 1] == "Debug" && list[list.Length - 2] == "bin")
var bin = path.LastIndexOf("bin");
var proj = path.Substring(0, bin);
var obj = proj + "obj";
DeleteFolder(obj);
catch
#region Dispose
public bool Disposed = false;
/// <summary>
/// 显式调用Dispose方法,继承IDisposable
/// </summary>
public void Dispose()
//由手动释放
Dispose(true);
//通知垃圾回收机制不再调用终结器(析构器)_跑了这里就不会跑析构函数了
GC.SuppressFinalize(this);
/// <summary>
/// 析构函数,以备忘记了显式调用Dispose方法
/// </summary>
~AssemblyDependent()
//由系统释放
Dispose(false);
/// <summary>
/// 释放
/// </summary>
/// <param name="ing"></param>
protected virtual void Dispose(bool ing)
if (Disposed)
//不重复释放
return;
//让类型知道自己已经被释放
Disposed = true;
GC.Collect();
#endregion
}
运行域事件: 重要的是这个事件,它会在运行的时候找已经载入内存上面的程序集.
AppDomain.CurrentDomain.AssemblyResolve += RunTimeCurrentDomain.DefaultAssemblyResolve;
封装
using System;
using System.Linq;
using System.Reflection;
namespace JoinBoxCurrency
public static class RunTimeCurrentDomain
#region 程序域运行事件
// 动态编译要注意所有的引用外的dll的加载顺序
// cad2008若没有这个事件,会使动态命令执行时候无法引用当前的程序集函数
// 跨程序集反射
// 动态加载时,dll的地址会在系统的动态目录里,而它所处的程序集(运行域)是在动态目录里.
// netload会把所处的运行域给改到cad自己的,而动态编译不通过netload,所以要自己去改.
// 这相当于是dll注入的意思,只是动态编译的这个"dll"不存在实体,只是一段内存.
/// <summary>
/// 程序域运行事件
/// </summary>
public static Assembly DefaultAssemblyResolve(object sender, ResolveEventArgs args)
var cad = AppDomain.CurrentDomain.GetAssemblies();
/*获取名称和版本号都一致的,调用它*/
Assembly load = null;
load = cad.FirstOrDefault(a => a.GetName().FullName == args.Name);
if (load == null)
/*获取名称一致,但是版本号不同的,调用最后的可用版本*/
var ag = args.Name.Split(',')[0];
//获取 最后一个符合条件的,
//否则a.dll引用b.dll函数的时候,b.dll修改重生成之后,加载进去会调用第一个版本的b.dll
foreach (var item in cad)
if (item.GetName().FullName.Split(',')[0] == ag)
//为什么加载的程序版本号最后要是*
//因为vs会帮你迭代这个版本号,所以最后的可用就是循环到最后的.
load = item;
return load;
#endregion
}
编写一个方法调用
using Autodesk.AutoCAD.Runtime;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
[assembly: CommandClass(typeof(HotLoadAcad.AcadHot_loading))]
namespace HotLoadAcad
/* net standard只需要增加.csproj的这里,没有自己加一个:
* <PropertyGroup>
* <AssemblyVersion>1.0.0.*</AssemblyVersion>
* <FileVersion>1.0.0.0</FileVersion>
* <Deterministic>False</Deterministic>
* </PropertyGroup>
* net framework要直接编辑项目文件.csproj,启用由vs迭代版本号
* <PropertyGroup><Deterministic>False</Deterministic></PropertyGroup>
* [assembly: AssemblyVersion("1.0.*")]
class AcadHot_loading
[CommandMethod("HotLoading_dll")]
public void MainLoading()
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "dll|*.dll"; //删选、设定文件显示类型
if (ofd.ShowDialog() !=DialogResult.OK)
return;
string path = ofd.FileName; //获得选择的文件路径
var ad = new AssemblyDependent(path);
//运行时出错的话,就靠这个事件来解决
ad.CurrentDomainAssemblyResolveEvent += RunTimeCurrentDomain.DefaultAssemblyResolve;
ad.Load();