相关文章推荐
玩滑板的绿茶  ·  生成 Windows ...·  7 月前    · 
私奔的牛腩  ·  Mockito模拟器_mockito ...·  11 月前    · 
霸气的烈马  ·  PHP ftp_nb_put() 函数 | ...·  1 年前    · 

CAD.net开发动态加载dll

我们 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();