拓冰建站拓冰建站
首页 / 资讯中心 / 正文

中望CAD netload加载DLL插件:动态读取Ribbon.xml配置菜单的方法

简介面向中望CAD二次开发人员及需要定制CAD菜单的工程师示例工程演示了通过netload加载DLL插件、动态读取配置菜单并生成自定义面板的完整流程。压缩包采用7z格式共48个文件、约5MB大小内部包含已编译的DDiuRibbon.dll插件、9个C#源文件、Ribbon.xml配置样例、Visual Studio解决方案与调试符号pdb等源码与编译输出分离还附带用户反馈说明便于查看、加载、调试或二次修改。已有807人学习浏览工程结构清晰覆盖插件开发、菜单配置、编译输出等完整链路适合中高级CAD开发者快速上手。通过研读源码和配置可以掌握中望CAD .NET插件开发流程包括命令入口编写、XML配置解析、菜单动态生成、netload加载集成及注册部署注意事项同时能借鉴作者在错误处理、界面交互与多线程调用上的封装思路为扩展功能区、定制专业工具提供可复用的工程参考。1. 中望CAD的netload加载DLL插件为什么需要动态配置菜单一个设计部的三个项目组共用同一套中望CAD每个组却要不相同的功能菜单一组要批量打印二组要图框校验三组只管图号清理。传统做法是给每组编译一个独立DLL改一次菜单发一次新文件最后安装目录里躺满DDiuRibbon副本.dll、DDiuRibbon - 副本(2).dll。这套方案反着来把界面定义全部抽到Ribbon.xmlDLL启动时动态读XML、动态挂菜单netload一条命令加载进去代码不生变化时只改XML就能换整套界面。工程包里能看到DDiuRibbon.sln、DDiuRibbon.cs、Ribbon.xml以及ZwManaged.dll、ZcWindows.dll、ZwDatabaseMgd.dll这一组中望CAD托管程序集正是这个思路的完整落地。适合已经写过AutoCAD .NET插件、想迁移到中望CAD的人也适合想搞明白netload到底能做什么的新手。2. 开发环境与项目骨架从 .sln 到 ZwManaged.dll 引用2.1 为什么选C#类库而不是LISP或ObjectARX中望CAD的netload面向.NET托管程序集直接加载C#或VB.NET编译出来的DLL不需要像ObjectARX那样处理非托管资源释放。C#在文件解析、字符串处理、Ribbon控件操作上比LISP顺手得多Visual Studio里还能直接断点调试。DDiuRibbon.sln对应的正是标准C#类库工程Properties/AssemblyInfo.cs管理版本号输出DDiuRibbon.dll进入CAD后用netload加载。中望CAD .NET API在命名上兼容经典AutoCAD API但程序集采用Zw系列开头。工程Reference里列了ZwManaged.dll、ZwDatabaseMgd.dll、ZwDatabaseMgdBrep.dll同时还有acmgd.dll、acdbmgd.dll、accoremgd.dll这类兼容层程序集。新建工程时优先引用Zw开头的程序集因为兼容层只是做API映射边界行为和错误信息跟原生Zw系列有差异。项目里同时存在ZdWindows.dll和AcWindows.dllZcWindows.dll负责界面控件这几个程序集要被一起引用。2.2 创建类库项目并配置引用Visual Studio新建C#类库目标Framework建议4.7.2或4.8中望CAD的托管运行时会自动匹配.NET Framework。工程创建后把项目里Reference中列出的程序集加入ZwManaged.dll核心托管入口负责命令注册、文档、编辑器。ZwDatabaseMgd.dll图形数据库操作。ZwDatabaseMgdBrep.dllBRep边界表示访问。ZcWindows.dll / ZdWindows.dll中望CAD Windows界面与控件支持。AdWindows.dll / AcWindows.dllAutoCAD兼容界面层。csproj里要把这些引用DLL的Copy Local设为False否则每次生成都往输出目录复制一份netload加载时可能出现两份ZwManaged版本不一致的问题。项目里出现的obj、x64、x86目录说明原始工程已经做了平台配置x64和x86两个版本都要保留加载时按CAD进程位数选对应输出目录。配置完成后工程结构大致如下DDiuRibbon.sln DDiuRibbon.csproj DDiuRibbon.cs Properties/AssemblyInfo.cs Ribbon.xmlRibbon.xml要设置成“复制到输出目录”这样DLL旁边直接就有配置文件不依赖用户机器的绝对路径。2.3 搭建入口类与netload命令netload能识别的是带CommandMethod特性的托管方法。DDiuRibbon.cs入口需要保留一个能手工触发的命令using System; using ZwSoft.ZwCAD.ApplicationServices; using ZwSoft.ZwCAD.EditorInput; using ZwSoft.ZwCAD.Runtime; namespace DDiuRibbon { public class RibbonLoader { [CommandMethod(DDRibbonReload)] public void ReloadRibbon() { Document doc Application.DocumentManager.MdiActiveDocument; Editor ed doc.Editor; try { string dllDir AppDomain.CurrentDomain.BaseDirectory; string configPath System.IO.Path.Combine(dllDir, Ribbon.xml); MenuBuilder.BuildFromFile(configPath); ed.WriteMessage(\nRibbon 配置加载完成: {0}, configPath); } catch (Exception ex) { ed.WriteMessage(\n加载失败: {0}, ex.Message); } } } }Application.DocumentManager.MdiActiveDocument取当前激活图纸Editor负责和命令行交互。ed.WriteMessage是所有netload插件最直接的反馈渠道。CommandMethod参数“DDRibbonReload”就是加载后在命令行敲的命令名加DD前缀避免和系统命令冲突。try-catch要提前布置好因为netload加载成功后DLL被CAD锁住无法连续编译命令行和日志是主要排错窗口。MenuBuilder类在下一章实现。2.4 与普通.NET DLL的运行差异netload加载不是把程序集安装到GAC也不是静态链接它是在当前CAD进程里动态Assembly.Load。所以插件代码里没有传统Main入口所有暴露给CAD的功能必须通过CommandMethod标记或用静态构造函数挂到CAD的启动事件上。正因为是进程内加载配置文件的读取位置要严格使用AppDomain.CurrentDomain.BaseDirectory而不是当前工作目录。CAD的工作目录经常被快捷方式“起始位置”带到项目文件夹此时用相对路径会解不出Ribbon.xml。另一点是程序集加载上下文netload默认使用Assemly.LoadFrom依赖项查找要求所有DLL在同目录。工程里ZwManaged.dll和DDiuRibbon.dll如果不放在同一目录加载时就会抛FileNotFoundException这点在第4章还会展开。3. 动态读取Ribbon.xml配置生成菜单结构3.1 Ribbon.xml配置格式先定下来“动态读取配置菜单”的关键是把菜单结构描述成数据让代码遍历数据而不是逐个写死按钮。工程里的Ribbon.xml承担这个角色一个完整配置长这样RibbonRoot Tab IdTab_Design Label设计出图 Order0 Panel Label标准工具 Button Text批量打印 CommandDDRibbonPlot Iconplot.ico Tooltip按图框批量打印 / Button Text清理重名 CommandDDRibbonPurge2 Icon Tooltip删除全部重名块 / Button Text图号校验 CommandDDRibbonCheck Iconcheck.ico Tooltip校验图号规则 / /Panel /Tab /RibbonRoot每个节点对应中望CAD Ribbon控件树的一层RibbonRoot是界面容器Tab对应顶部标签页Panel对应标签页内的面板分组Button对应面板上的命令按钮。动态部分放在Command和Label两个属性上后续加新功能时只改XML不用动C#工程。项目里出现Ribbon.xml说明原始设计正是这种数据驱动界面。3.2 用XDocument解析配置解析XML推荐XDocument而非XmlDocumentLINQ写法紧凑且不用维护冗长的NodeList遍历。下面这段代码负责把Button节点转换成内存中的MenuItem集合using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; namespace DDiuRibbon { public class MenuItem { public string Id { get; set; } public string Label { get; set; } public string Command { get; set; } public string Icon { get; set; } public string Tooltip { get; set; } } public static class MenuBuilder { public static ListMenuItem ReadMenuItems(string xmlPath) { XDocument xdoc XDocument.Load(xmlPath); ListMenuItem items xdoc .Descendants(Button) .Select(b new MenuItem { Id (string)b.Attribute(Id) ?? Guid.NewGuid().ToString(), Label (string)b.Attribute(Text), Command (string)b.Attribute(Command), Icon (string)b.Attribute(Icon), Tooltip (string)b.Attribute(Tooltip) }) .ToList(); return items; } } }Descendants(Button)能跨层级取出XML里所有Button节点不管它在哪个Tab和Panel下。Attribute用强转(string)而不是.Value是因为缺属性时前者返回null后者抛InvalidCastException容错性差很多。Id属性没有时就用Guid兜底这是控件去重的基础。解析完成后得到的是纯内存数据还没进入CAD界面。要把它变成真正的Ribbon菜单需要调用中望CAD的UI程序集接口。3.3 把解析结果映射到Ribbon界面不同中望CAD版本Ribbon API命名有差异但整体流程一致定位RibbonControl宿主创建Tab创建Panel创建Button。下面代码基于ZcWindows.dll暴露的界面类型命名空间可能随版本不同存在ZwSoft.ZwCAD.Windows或ZwSoft.ZwCAD.UI以本地程序集实际类型为准。采用先查找再创建的策略保证重复执行Reload不会重复叠加菜单using System.Collections.Generic; using System.IO; using ZwSoft.ZwCAD.Windows; // 不匹配时换成ZwSoft.ZwCAD.UI namespace DDiuRibbon { public static class RibbonApplier { public static void BuildFromFile(string xmlPath) { if (!File.Exists(xmlPath)) throw new FileNotFoundException(Ribbon.xml 不存在: xmlPath); RibbonControl ribbon ZwSoft.ZwCAD.ApplicationServices.Application.UIRibbon; var tabs ParseTabGroups(xmlPath); foreach (var tabDef in tabs) { RibbonTab tab ribbon.FindTab(tabDef.Id) ?? ribbon.CreateTab(tabDef.Id, tabDef.Label); foreach (var panelDef in tabDef.Panels) { RibbonPanel panel tab.FindPanel(panelDef.Label) ?? tab.CreatePanel(panelDef.Label); foreach (var btn in panelDef.Buttons) { if (panel.FindButton(btn.Command) ! null) continue; panel.AddButton(new RibbonButton { Text btn.Label, Command btn.Command, Tooltip btn.Tooltip }); } } } } } }FindTab和CreateTab这类方法名是常规封装某些版本可能叫GetRibbonTab或AddRibbonTab。核心逻辑是“存在则跳过不存在才创建”这就是幂等处理。如果不做FindButton判断每次执行DDRibbonReload都会在面板上叠加一组新按钮Reload次数越多菜单越混乱最终只能重启CAD。3.4 命令加载后如何自动挂菜单netload加载DLL后不会自动执行任何命令必须由用户在命令行敲DDRibbonReload才触发。若希望加载后菜单立即出现可以在静态构造函数里调用构建逻辑static RibbonLoader() { try { string xmlPath Path.Combine( AppDomain.CurrentDomain.BaseDirectory, Ribbon.xml); RibbonApplier.BuildFromFile(xmlPath); } catch (Exception ex) { LogHelper.Error(StaticCtor, ex); } }CLR第一次访问RibbonLoader类时静态构造会先执行netload后只要命令有被解析菜单就自动挂上。代价是如果XML写错异常被吞掉后用户会用不了任何命令但CrashLog能留下堆栈。实际项目里我更推荐保留显式DDRibbonReload命令加静态构造双入口这样既能自动加载又能手工刷新。三种挂载时机的对比时机使用场景风险静态构造函数netload后零操作生效XML错误可能导致后续命令异常命令方法调试配置、手工刷新需要用户多输入一条命令Application.DocumentManager.DocumentCreated事件换图纸时重建菜单多文档环境要处理事件解除开发初期用命令方法最稳妥等配置稳定后再改成静态构造或事件挂载。4. netload加载细节与调试CrashLog、依赖项与x64/x864.1 netload命令行的操作路径加载操作在中望CAD里很直接命令行输入NETLOAD后回车弹出文件选择框选中DDiuRibbon.dll确定加载即完成。加载成功后命令行不会输出任何成功字样必须继续执行DDRibbonReload才能看到“Ribbon 配置加载完成”。加载后CAD进程锁定DLL文件改代码再编译时会报“文件被占用”这时要关闭CAD或者用Unload命令解除加载。如果项目里装了AppStarter.exe监控工具它会把CAD启动时自动加载DLL的操作接管过去这属于部署阶段的自动化手段。4.2 加载失败先查四个方向实际采用这套工程后最容易出问题的点集中在四个方向错误现象原因处理办法提示无法加载程序集DLL依赖的ZwManaged等不在同目录把所有引用DLL放到DDiuRibbon.dll旁边找不到指定文件平台位数与CAD不一致x64 CAD必须加载x64输出x86同理按钮没反应按钮Command和CommandMethod不匹配检查Ribbon.xml中Command字符串与特性参数大小写重复加载行为混乱静态变量或Ribbon对象残留彻底卸载后重载或重启CADx64/x86这套要单独强调。DDiuRibbon工程里同时存在x64和x86输出目录因为中望CAD分32位和64位两个版本。DLL平台必须和CAD进程严格一致不能用.NET的AnyCPU自动适应。Visual Studio的配置管理器里切Active Solution Platform分别生成x64和x86产物分发时按客户CAD位数选一个目录下放即可。4.3 把异常写进CrashLog文件生产环境用户不会开Visual Studio现场问题全靠日志。项目里出现CrashLog目录和用户反馈.txt说明原工程已经在用文件记录问题。下面这个LogHelper类把日志写到DLL目录下的CrashLog文件夹using System; using System.IO; namespace DDiuRibbon { public static class LogHelper { static readonly string LogDir Path.Combine( AppDomain.CurrentDomain.BaseDirectory, CrashLog); public static void Error(string context, Exception ex) { if (!Directory.Exists(LogDir)) Directory.CreateDirectory(LogDir); string file Path.Combine( LogDir, DateTime.Now.ToString(yyyyMMdd) .log); string line string.Format( [{0}] {1}: {2}\r\n{3}, DateTime.Now.ToString(HH:mm:ss), context, ex.Message, ex.StackTrace); File.AppendAllText(file, line); } } }在RibbonLoader的catch块里调用LogHelper.Error(LoadConfig, ex)之后命令行只提示“加载失败详情见CrashLog”。日志文件里能看到异常类型、堆栈和上下文标记比抓取用户屏幕截图可靠得多。日志文件按天切分也能避免单个文件无限增长。4.4 开发调试时如何绕过DLL锁netload加载后DLL被占用Visual Studio无法覆盖输出文件。建议把工程输出路径直接指向中望CAD安装目录的某个子目录比如C:\Program Files\ZWSOFT\ZWCAD 2024\DRibbonPlugins并在CAD启动后用netload加载这个固定路径的DLL。这样调试时关掉CAD重新F5编译即可少走“先复制DLL再启动CAD”的弯路。另一个方式是附加到正在运行的Zcad进程Visual Studio菜单“调试-附加到进程”选中进程后直接断点到插件代码里。注意附加前要确保DLL路径和CAD正在加载的一致否则断点不会命中。调试时看到ZwManaged.dll版本冲突就去References里检查每个Zw程序集是否指向目标CAD安装目录版本必须和运行版本完全一致。5. 菜单插件进阶多线程解析、依赖合并与一键部署Ribbon配置达到几百个按钮时XML解析加UI创建会卡住CAD的UI线程几百毫秒。可以先把解析放到后台线程解析完成后再切回主线程挂菜单Task.Run(() { var items MenuBuilder.ReadMenuItems(xmlPath); System.Windows.Application.Current.Dispatcher.BeginInvoke( new Action(() RibbonApplier.ApplyItems(items))); });中望CAD的WPF界面运行在STA线程上直接在Task里创建RibbonButton会碰到跨线程访问异常。BeginInvoke保证界面操作切回主线程这里Task.Run只做纯文本解析不碰任何CAD对象模型。分发时用户少带一个DLL的情况很常见。对纯托管依赖可以用ILMerge或Costura.Fody把自研辅助程序集合并进DDiuRibbon.dll。中望CAD自己的ZwManaged.dll不能合并它必须由CAD目录加载能合并的只限自己写的XML解析、日志封装这些小模块。合并后测试一次netload加载观察CrashLog是否出现FileLoadException。多版本配置可以靠环境变量实现。给插件加一段读配置路径的逻辑public static string GetConfigPath() { string env Environment.GetEnvironmentVariable(ZDRAF_RIBBON_CFG); if (!string.IsNullOrEmpty(env) File.Exists(env)) return env; return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Ribbon.xml); }环境变量ZDRAF_RIBBON_CFG存在就读取指定路径的XML否则读DLL旁默认配置。不同项目组在自己的终端设置环境变量指向服务器共享路径菜单配置就能统一管理不用每次更新DLL。整个链路验证起来也不复杂命令行输入RIBBON打开Ribbon页签检查按钮修改Ribbon.xml里的按钮文字再执行一次DDRibbonReload看是否刷新。这一步跑通了动态读取配置菜单的完整流程就真正闭环。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门