yupeng_dyp 发表于 2022-9-11 17:20

.Net CAD调试不重启CAD(源码交流)

本帖最后由 yupeng_dyp 于 2022-9-11 18:12 编辑

由于 .Net CAD 开发调试一直很烦,经常要重启!在网上查了很多的方法,现经测试优化整理出一个可行的方法,不过只能对无依赖的 Dll 文件可以成功,在这里与大家分享交流;而有依赖的暂时还没有找到好的方法,望各位道友共研交流分享。

说明:
TestApp.dll 为要调试的类库
TestApp.ClassMain 为类库中对应的空间名与类名
TestCmd 为要调试执行的方法
上面对应的名称可根据自己的修改就行,把下面的源码单独编译成一个 Dll 然后在 CAD 中加载,另把要调试的 Dll 放到与此 Dll 一个目录就可以了,Json 文件主要用于在不重新编译此 Dll 时,方便修改要调试的 Dll 相应的名称(使用前先安装好 Json 包)

源码如下:
第一次上传,using Autodesk.AutoCAD.Runtime 中好像始终有个自动加的链接,也不知道咋去掉!!!
using System;
using System.IO;
using System.Reflection;

using Autodesk.AutoCAD.Runtime;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Acad.Reload;

public class Reload
{
    private Action cmd;

           //简写命令
    public void RL() { ReloadCmd(); }

       //全写命令
    public void ReloadCmd() {
      var cmdInfo = new CmdInfo("TestApp.dll", "TestApp.ClassMain", "TestCmd");         //目标类库、空间.类、方法的名称
      var dllPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);      //当前类库文件所在目录
      var jsonFile = dllPath + "\\Reload.json";                                           //定义 Json 文件全路径

      //如果 Json 文件存在就读取对应数据
      if (File.Exists(jsonFile)) {
            using var file = File.OpenText(jsonFile);
            using var reader = new JsonTextReader(file);
            var obj = (JObject)JToken.ReadFrom(reader);
            cmdInfo.DllName = obj["DllName"].ToString();
            cmdInfo.ClassName = obj["ClassName"].ToString();
            cmdInfo.MethodName = obj["MethodName"].ToString();
      }

      var adapterFileInfo = new FileInfo(Assembly.GetExecutingAssembly().Location);       //获取当前类库文件全路径
      var targetFilePath = Path.Combine(adapterFileInfo.DirectoryName, cmdInfo.DllName);//获取目标类库文件全路径
      var targetAssembly = Assembly.Load(File.ReadAllBytes(targetFilePath));            //将目标类库以二进制形式加载到程序

      try {
            var targetType = targetAssembly.GetType(cmdInfo.ClassName);                     //定位目标类
            var targetMethod = targetType.GetMethod(cmdInfo.MethodName);                  //定位目标方法
            var targetObject = Activator.CreateInstance(targetType);                        //创建目标类

            cmd = () => targetMethod.Invoke(targetObject, null);                            //cmd指向类中的方法
            cmd?.Invoke();                                                                  //执行方法
      }
      catch (System.Exception ex) {
            System.Windows.Forms.MessageBox.Show(ex.Message, "提示");
      }
    }
}

public class CmdInfo
{
    public string DllName { get; set; }   //类库文件名
    public string ClassName { get; set; }   //空间名.类名
    public string MethodName { get; set; }//方法名

    public CmdInfo(string dllName, string className, string methodName) {
      DllName = dllName;
      ClassName = className;
      MethodName = methodName;
    }
}
Json 文件内容参考:
{
    "DllName":"TestApp.dll",
    "ClassName":"TestApp.ClassMain",
    "MethodName":"TestCmd"
}
下面是一个用于自动加载 Dll 的 Lisp 程序用于交流分享,默认文件名为 LoadApp.lsp ,默认加载同目录中 net35 或 net48 目录下的 Reload.dll 文件,可根据自己需要修改,使用时把此程序设为自动加载即可,源码如下:
;; 通过 appload 命令加载获取最后一个加载的 Lisp 文件目录(已设为自动加载的不可用)
;; 如想要获取当前文件的目录,就必需在 appload 加载的时候直接调用执行,而非通过命令再调用
(defun get-cur-path (/)
(setq last_lisp_path
    (vl-registry-read
      (strcat
      "HKEY_CURRENT_USER\\" (vlax-product-key) "\\Profiles\\"
      (vla-get-activeprofile (vla-get-profiles (vla-get-preferences (vlax-get-acad-object))))
      "\\Dialogs\\Appload"
      )
      "MainDialog"
    )
)
)


;; 获取已设为自动加载程序的文件目录(附加三个文件或目录查找的可选参数增强判断准确性,不加就传 nil 即可)
(defun get-folder-path (autoloadapp_fullname fileorfolder1 fileorfolder2 fileorfolder3
                        / i acad_temp app_count reg_path list_path reg_file_name return_path)
(setq acad_temp (getenv "ACAD"));; 暂存系统原支持搜索路径

;; 设置注册表 appload 程序列表路径
(setq reg_path
    (strcat
      "HKEY_CURRENT_USER\\" (vlax-product-key) "\\Profiles\\"
      (vla-get-activeprofile (vla-get-profiles (vla-get-preferences (vlax-get-acad-object))))
      "\\Dialogs\\Appload\\Startup"
    )
)

(setq app_count (atoi (vl-registry-read reg_path "NumStartup")));; 获取列表中程序数量

(setq i 1)
(while (<= i app_count) ;; 逐个读取列表进行查找判断
    (setq list_path (fnsplitl (vl-registry-read reg_path (strcat (itoa i) "Startup"))))
    (setq reg_file_name (strcat (cadr list_path) (caddr list_path)))
    (if (= (strcase reg_file_name) (strcase autoloadapp_fullname))
      (progn
      (setenv "ACAD" (strcat acad_temp ";" (car list_path)));; 将目录加入系统支持搜索路径
      
      (if (and (/= fileorfolder1 nil) (/= fileorfolder2 nil) (/= fileorfolder3 nil)
            (findfile fileorfolder1) (findfile fileorfolder2) (findfile fileorfolder3)
            )
          (progn
            (setq i (+ app_count 1));; 用于结束循环
            (setenv "ACAD" acad_temp) ;; 还原系统原支持搜索路径
            (setq return_path (car list_path));; 返回文件目录
          )
          (if (and (/= fileorfolder1 nil) (/= fileorfolder2 nil)
                (findfile fileorfolder1) (findfile fileorfolder2)
            )
            (progn
            (setq i (+ app_count 1));; 用于结束循环
            (setenv "ACAD" acad_temp) ;; 还原系统原支持搜索路径
            (setq return_path (car list_path));; 返回文件目录
            )
            (if (and (/= fileorfolder1 nil) (findfile fileorfolder1))
            (progn
                (setq i (+ app_count 1));; 用于结束循环
                (setenv "ACAD" acad_temp) ;; 还原系统原支持搜索路径
                (setq return_path (car list_path));; 返回文件目录
            )
            (progn
                (setq i (+ i 1))
                (setq return_path nil);; 返回空
            )
            )
          )
      )
      )
      (progn
      (setq i (+ i 1))
      (setq return_path nil);; 返回空
      )
    )
)
)


;; 加载应用程序
(defun loadapp (/ old_CMDECHO load_path)
(setq old_CMDECHO (getvar "CMDECHO"));; 获取命令回显变量初始值
(setvar "CMDECHO" 0);; 关闭命令回显

(cond
    ;; 读取自动加载列表并通过 loadapp.lsp 文件与 net35、net48 两个目录来判断是否为指定路径
    ((not (setq load_path (get-folder-path "loadapp.lsp" "net35" "net48" nil)))
    ;; 获取通过 appload 加载时的目录(未设为自动加载程序时)
    (setq load_path (get-cur-path)))
)

(if (>= (atof (getvar "acadver")) 18.0) ;; 要求 CAD2010 及以上版本
    (if (>= (atof (getvar "acadver")) 19.0)
      (progn
      ;; CAD版本2013(版本号:19.0)及以上
      (if (>= (atof (getvar "acadver")) 19.1) ;; CAD 2014 及以上版本
          ;; 添加受信任位置且避免重复添加
          (progn;;下面通过在查寻与被查寻的字串前后加分号来去除查寻路径的子路径如:";D:\\ABC;" 与 ";D:\\ABC\\EFG;"
            (if (= (vl-string-search (strcat ";" load_path ";") (strcat ";" (getvar "TRUSTEDPATHS") ";")) nil)
            (setvar "TRUSTEDPATHS" (strcat (getvar "TRUSTEDPATHS") ";" load_path))
            )
            (if (= (vl-string-search (strcat ";" load_path "net48\\" ";") (strcat ";" (getvar "TRUSTEDPATHS") ";")) nil)
            (setvar "TRUSTEDPATHS" (strcat (getvar "TRUSTEDPATHS") ";" load_path "net48\\"))
            )
          )
      )
      
      (command "netload" (strcat load_path "net48\\Reload.dll"))
      (princ "\n\n程序 Reload 加载完成,版本 Net48\n\n")
      )
      (progn
      ;; CAD版本2010(版本号:18.0)至2012(版本号:18.2)
      (command "netload" (strcat load_path "net35\\Reload.dll"))
      (princ "\n\n程序 Reload 加载完成,版本 Net35\n\n")
      )
    )
    (princ "\n\n程序未能加载, 要求 CAD 版本不低于 2010\n\n")
)

(setvar "CMDECHO" old_CMDECHO);; 还原命令回显变量初始值
)

(loadapp) ;; 用函数加载程序(由于要用临时变量存储系统变量值,固用函数来限定为局部变量)







小手一抖 发表于 2022-9-11 22:35

支持楼主,这个方法方便多了

szhorse 发表于 2022-9-12 18:05

感谢分享,过几天有时间折腾一下

Linxian1028 发表于 2022-10-3 13:16

有VB.net的源码?

yupeng_dyp 发表于 2022-10-3 15:07

Linxian1028 发表于 2022-10-3 13:16
有VB.net的源码?

没有,不过你可以根据原理用VB重新写一下就行了嘛

cabinsummer 发表于 2022-10-25 14:25

用反射的方式也能做到热加载

using System.Reflection;//反射

Assembly assy = Assembly.LoadFrom(@"路径\插件.dll"); //加载插件.dll
Type CommandType = assy.GetType("Namespace.Class"); // 创建类实例对象

//用反射调用属性方法

ytianxia 发表于 2022-11-1 11:39

1. 可以改造下,用反射的方法来直接获取 的类,获取 Init() 函数,使用默认的类和接口

2. 读取二进制文件加载,会不会丢失相对信息,比如相对路径资源什么的?我没有具体测试。

如果是高版本,可以试试隔壁的热加载
页: [1]
查看完整版本: .Net CAD调试不重启CAD(源码交流)