版本声明本篇基线为AVEVA E3D Design 3.1文档版本线存在 1.1 / 2.1 / 3.1教程统一以 3.1 为准前代产品AVEVA PDMS 自 2024 年 4 月 1 日起不再销售、不再提供支持。历史文档PDMS 12.0 时代中的程序集名如Pdms.Database.dll、PDMSFilters.dll与 3.1 时代的Aveva.Core.*不是同一代资产官方未给出旧→新命名空间迁移对照表跨代引用必须逐个验证。本篇所有程序集名、命名空间名、环境变量名与文档 URL 均出自官方 .NET 定制手册NCUG。一句话结论.NET Addin 的加载判定是反射找一个实现IAddin接口的类并调用它的Start而不是靠配置文件逐项声明——所以编译通过了却没被加载这类问题答案几乎都在程序集引用、装载路径与反射可见性这三处。〇、认知问题读完本篇你应该能回答.NET Addin 与 PML.NET 这两条 .NET 定制路径官方给它们的分工是什么IAddin接口是怎么被找到的Start在什么时机被调用模块名Addins.xml与环境变量CAF_ADDINS_PATH是什么关系MODEL 模块对应哪个文件名CAF 相关的四个关键环境变量分别管什么为什么在配置里写了类名不等于Addin 一定会被加载官方的示例工程在哪里、叫什么名字它们能帮我们解决什么问题一、机制解析1.1 两条 .NET 路径的分工官方把 .NET 定制分成两条路径.NET Addin写一个实现IAddin接口的类由 CAF 在模块启动时加载适合做应用级扩展——注册命令、挂菜单工具栏、接企业系统、常驻服务。PML.NET让PML 侧通过IMPORT与USING NAMESPACE主动调用 .NET 代码适合做脚本级扩展——某个 PML 宏需要一段复杂算法或一个外部库时把这段能力放进 dll 由 PML 调用。官方的定位是两者都通过运行时把 .NET 程序集dll动态载入模块来实现并且明确与既有的 PML 定制并存——不是替代关系。这一点决定了选型判断如果功能是由用户点菜单发起的走 Addin Command如果功能是由某段 PML 流程发起的走 PML.NET。1.2IAddin被反射找到的那个类官方的判定标准非常干脆程序集内必须含有一个实现IAddin接口的类CAF 加载时反射查找这个类实例化它然后调用IAddin.Start。由此推出三条工程结论一个程序集里哪个类是 Addin是运行时决定的不是编译期声明的。你不需要也不能用配置文件把类名钉死。类必须能被反射看到。如果类是internal的、或者构造函数需要参数反射实例化就会失败。Start是唯一的入口时机。所有启动时要做的事注册命令、读设置、初始化服务都写在这里面不要在类构造函数里做重活因为构造函数在反射实例化阶段就可能被调用此时 CAF 的服务未必可用。1.3 装载配置模块名Addins.xml每个 CAF 应用都有一个模块名Addins.xml用来列出该模块要加载的 Addin 程序集。官方文档给出的实例是MODEL 模块对应DesignAddins.xml。该文件的默认位置是与可执行文件同目录可以用环境变量CAF_ADDINS_PATH覆盖且官方明确支持用;分隔多个路径使用UNC 路径网络共享。这就给了企业部署一个非常实用的做法不动安装目录把 Addin 放到企业共享盘用CAF_ADDINS_PATH指过去。好处是升级 E3D Design 时安装目录被覆盖也不会丢掉定制坏处是启动时多一次网络读共享盘不可达时加载会失败。1.4 四个关键环境变量官方NCUG 3.04.3列出的四个关键变量是环境变量作用工程含义AVEVA_DESIGN_EXE安装目录定位官方程序集、PML.NET 程序集查找路径的基准CAF_ADDINS_PATHAddin 位置覆盖默认的模块名Addins.xml所在路径支持;分隔与 UNCCAF_UIC_PATH界面定义文件位置按模块名Customisation.xml查找并列出的.uic文件AVEVA_CAF_TRACING开发期跟踪排查为什么没加载的第一手段这四个变量的价值在于把个人机器配置变成项目统一配置。不要依赖某台机器上临时设置的环境变量——把设置写进启动脚本是所有工程化 E3D 定制环境的共同做法。1.5 CAF 程序集与命名空间.NET Addin 开发要用到的官方程序集名称逐页实测自 NCUGAveva.ApplicationFramework.dll—— CAF 本体Aveva.ApplicationFramework.Presentation.dll—— 界面相关的命令与命令栏对应的命名空间Aveva.ApplicationFramework包含AddinManager、ServiceManager、SettingsManagerAveva.ApplicationFramework.Presentation包含CommandBarManager、CommandManager另有与数据库、几何、共享库相关的程序集Aveva.Core.Database.dll、Aveva.Core.Database.Filters.dll、Aveva.Core.Geometry.dll、Aveva.Core.Maths.Geometry.dll、Aveva.Core.Shared.dll、Aveva.Core.Utilities*.dll、Aveva.Core3D.Graphics.dll、Aveva.Engineering.Tags.Pml.dllStandalone 场景另有Aveva.Core3D.Standalone、Aveva.E3D.Standalone。引用原则需要什么引什么不要在 Addin 里全量引用——Addin 加载失败最常见的原因之一就是引用了在当前模块里根本不存在或版本不匹配的程序集。1.6 开发环境Samples.zip与示例工程官方示例代码包是安装目录下的Samples.zip内含 C# 源码与 Visual Studio 工程。官方文档中出现过的示例工程名包括AttributeBrowserAddinExamplesAddinFlexibleExplorerAddinNetGridExamplePMLNetExamplePMLGridExample这几个工程的正确定位是**“权威签名参照物”**当你无法确定某个接口的方法签名、某个 XML 配置的节点名、某项设置的注册方式时Samples.zip里的真实工程是唯一可以逐字对照的来源。官方并未公开可下载的独立示例仓库示例只随安装目录提供——这一点很关键不要指望在网上找到替代品。二、完整代码与逐行剖析2.1 最小 AddinC#// MyFirstAddin.cs// 代码来源性质按官方 NCUG 记载的 IAddin 机制与 CAF 程序集名构造// 需在本地 E3D Design 环境验证Start 的完整签名请以 Samples.zip 中的// 真实示例工程与本机 IAddin 接口定义为准。usingSystem;usingAveva.ApplicationFramework;// 提供 AddinManager / ServiceManager / SettingsManagernamespaceMyCompany.E3DAddin{/// summary/// 最小可运行 Addin实现 IAddin 接口即会被 CAF 反射发现。/// 注意类必须对反射可见public且构造函数不能带参数。/// /summarypublicclassMyFirstAddin:IAddin{// 保存 CAF 注入的管理器引用供后续注册命令/读取设置使用privateAddinManager_addinManager;privateServiceManager_serviceManager;/// summary/// Addin 的唯一入口时机CAF 反射实例化本类后调用。/// 所有启动期工作注册命令、初始化服务、读配置都放在这里。/// /summarypublicvoidStart(AddinManageraddinManager,ServiceManagerserviceManager){_addinManageraddinManager;_serviceManagerserviceManager;// 启动期的第一件事先确认自己真的被调用了Console.WriteLine([MyFirstAddin] Start called.);}/// summary/// 模块关闭时的清理入口。/// /summarypublicvoidStop(){// 释放资源、注销事件、关闭连接Console.WriteLine([MyFirstAddin] Stop called.);}}}2.2 逐行剖析using Aveva.ApplicationFramework;只引 CAF 本体。这一行能不能编译过就直接证明了你的工程是否正确引用了Aveva.ApplicationFramework.dll——这是最有价值的第一道自检。public class MyFirstAddin : IAddinIAddin是官方规定的接口。类必须是public因为判定依赖反射查找如果写成internal在同一个程序集外就找不到。private AddinManager _addinManager;/private ServiceManager _serviceManager;AddinManager与ServiceManager都属于命名空间Aveva.ApplicationFramework。把它们存下来是必须的——后面注册命令要从ServiceManager里取CommandManager第 10 篇会给出完整的注册代码。public void Start(AddinManager addinManager, ServiceManager serviceManager)官方明确了调用时机是实例化并调用IAddin.Start。参数签名以本机接口定义为准如果你的环境里Start的参数列表不同编译期就会报错此时去对照Samples.zip里的示例工程即可。Console.WriteLine(...)最小验证手段。第一版 Addin 不要做任何业务只要让它打印一行——先证明我被加载了再去加功能。public void Stop()清理入口。注意官方限制里有模块切换不保持 .NET 对象这一条——模块切换后你的 .NET 对象不会自动保留所以不要在Start里假设对象能跨越模块切换长期存活。2.3 装载配置DesignAddins.xml?xml version1.0 encodingutf-8?!-- 说明MODEL 模块对应的装载文件为 DesignAddins.xml官方文档实例。 下面的节点与属性名仅示意按程序集装载的组织方式 实际 schema 请以本机安装目录下的默认 DesignAddins.xml 与 NCUG 2.03.10 为准不要凭记忆手写。 --AddinsAddinNameMyFirstAddinAssemblyMyCompany.E3DAddin.dll//Addins逐项说明模块名Addins.xml与模块一一对应MODEL 模块是DesignAddins.xml。不同模块要各配一份别指望一个文件管所有模块。默认位置是与可执行文件同目录改到别处要用CAF_ADDINS_PATH指过去。配置里出现的是程序集dll。具体是哪个类充当 Addin由 CAF 反射查找实现IAddin的那个类决定——这就是反直觉点的由来你在配置里找不到类名因为判定根本不靠它。Samples.zip里各示例工程都自带可用的装载配置照抄它们的写法比自己猜安全得多。2.4 环境变量设置PowerShell# 09-set-caf-env.ps1 —— 设置 E3D Design .NET 定制相关的 CAF 环境变量# 说明前四项为官方 NCUG 3.04.3 记载的关键环境变量# 建议把本脚本纳入项目启动脚本统一执行不靠个人机器配置。$env:AVEVA_DESIGN_EXE C:\Program Files\AVEVA\E3D Design 3.1# 安装目录$env:CAF_ADDINS_PATH C:\E3D_Custom\Addins# Addin 位置支持 ; 分隔多路径与 UNC$env:CAF_UIC_PATH C:\E3D_Custom\UIC# 界面定义文件位置$env:AVEVA_CAF_TRACING 1# 开发期跟踪具体取值以本机验证为准# 打印确认避免设了但没生效Write-HostAVEVA_DESIGN_EXE $env:AVEVA_DESIGN_EXEWrite-HostCAF_ADDINS_PATH $env:CAF_ADDINS_PATHWrite-HostCAF_UIC_PATH $env:CAF_UIC_PATHWrite-HostAVEVA_CAF_TRACING $env:AVEVA_CAF_TRACING# 如需持久化到用户级环境会影响之后启动的所有进程谨慎使用# [Environment]::SetEnvironmentVariable(CAF_ADDINS_PATH, $env:CAF_ADDINS_PATH, User)2.5 部署与排错清单按顺序执行每一步都设计成可单独判定成败步骤动作判定标准1编译 Addin 工程编译通过无警告级的引用了找不到的程序集2确认输出 dll 与依赖dll 落到部署目录依赖项齐备3确认CAF_ADDINS_PATH生效启动脚本回显的路径 实际存放路径4确认模块名Addins.xml可读文件存在、XML 语法正确、程序集名与实际 dll 文件名逐字符一致5打开AVEVA_CAF_TRACING启动模块跟踪输出里能看到该 Addin 的加载记录6观察Start的输出打印出[MyFirstAddin] Start called.7关闭模块观察Stop打印出[MyFirstAddin] Stop called.清单的价值如果第 6 步没输出那么问题一定在第 3–5 步之间路径、配置、反射可见性而不在业务代码里——这就是分段判定的意义。三、常见报错与排查3.1 编译报错找不到IAddin或AddinManager类型现象The type or namespace name IAddin could not be found。原因工程未引用Aveva.ApplicationFramework.dll或引用了错误版本的 dll。排查在工程引用里补上Aveva.ApplicationFramework.dll确认取用的是与当前 E3D Design 版本对齐的那一份铁律版本必须对齐。不要去引 PDMS 12.0 时代的老程序集那些名字在新环境里根本不存在。3.2 编译通过但 Addin 没有被加载现象模块启动后毫无输出Start从未被调用。原因按概率排序模块名Addins.xml不在CAF_ADDINS_PATH指向的路径里也不在可执行文件同目录配置文件里的程序集名与 dll 实际文件名不一致漏字、大小写、多了路径前缀dll 里的类没有实现IAddin接口反射找不到实现类不是public或构造函数带参数反射无法实例化。排查打开AVEVA_CAF_TRACING看跟踪输出再按上面 4 条逐条核对。注意配置里写了类名这件事根本不存在——判定是反射做的所以核对的对象只有程序集是否被加载与类是否可实现。3.3 Addin 加载了但引用的业务类型找不到现象Start被调用了但访问数据库/几何相关类型时报类型不存在。原因缺少对应程序集引用例如Aveva.Core.Database.dll、Aveva.Core.Geometry.dll。排查按功能逐个补齐程序集用不到的一定不要引多引一个不存在的程序集会让整个 Addin 加载失败比少引更麻烦。3.4 换到另一台机器就失效现象开发机正常换到同事机器上不加载。原因环境变量是个人机器配置只在开发机上设过或CAF_ADDINS_PATH指向的是本机专有路径。排查把四个环境变量写进项目统一的启动脚本见 2.4企业内改用 UNC 路径让所有人的CAF_ADDINS_PATH指向同一份共享部署。3.5 模块切换后行为异常现象在设计模块里正常切到别的模块再回来Addin 的某些状态丢了或报错。原因这是官方列明的 PML.NET 限制之一——模块切换不保持 .NET 对象。虽然该条写在 PML.NET 章节但它反映的是跨模块生命周期这件事本身的约束。排查不要把关键状态只放在内存对象里需要跨模块保活的状态落到设置SettingsManager或数据库里。四、动手练习最小 Addin 跑通用 2.1 的代码建一个 C# 类库工程输出MyCompany.E3DAddin.dll按 2.3 配置DesignAddins.xml按 2.4 设置环境变量启动 MODEL 模块。判定标准Command Window 或跟踪输出中出现[MyFirstAddin] Start called.关闭模块时出现Stop called.。故意制造三种失败各记录一条排查结论分别①把实现类改成internal②把DesignAddins.xml里的程序集名改错一个字符③把CAF_ADDINS_PATH指到一个不存在的目录。每次只改一处重新启动并记录现象。判定标准能给出三条互不相同的现象描述并说明每一条为什么指向该原因例如配置文件没被读到与程序集被读到但类不可实例化的现象不同。切换到共享部署把 Addin 部署目录改成一个 UNC 路径用CAF_ADDINS_PATH指过去重新验证。判定标准共享路径部署下仍然能在启动跟踪里看到加载记录把共享盘断开后能观察到加载失败但模块本身仍能启动这一现象。对照Samples.zip校对签名打开安装目录下Samples.zip里的ExamplesAddin或AttributeBrowserAddin工程把你的IAddin实现与它的写法逐行对照。判定标准能指出自己代码里至少一处与官方示例不一致的地方哪怕只是命名习惯并说明是否需要修正。五、小结与下一篇预告本篇的要点可以压缩成一句话Addin 的加载是反射找接口实现部署是三件事程序集引用对齐版本、装载路径可读、类可实现。四件事记牢两条路分工Addin 做应用级扩展PML.NET 做脚本级扩展IAddin.Start是唯一入口启动期工作全放这里模块名Addins.xmlMODEL →DesignAddins.xmlCAF_ADDINS_PATHAVEVA_DESIGN_EXE/CAF_ADDINS_PATH/CAF_UIC_PATH/AVEVA_CAF_TRACING四个变量统一在启动脚本里设。下一篇10 Command 机制与 UICAddin 只是活下来了但用户在界面上还看不到它。要把功能挂到菜单和工具栏上需要继承 CAF 的抽象基类Command、在构造函数里设置Key、把命令实例注册进CommandManager.Commands再用.uic文件把界面与命令的Key关联起来。FAQQ1.NET Addin 与 PML.NET 这两条 .NET 定制路径官方给它们的分工是什么官方把 .NET 定制分成 .NET Addin 与 PML.NET 两条路径两者都是在运行时把 .NET 程序集dll动态载入模块并且都与既有 PML 定制并存。经验上的分工是由界面上用户操作发起的功能走 .NET Addin注册命令、挂菜单工具栏、常驻服务、接企业系统由 PML 脚本流程发起的功能走 PML.NETPML 用IMPORT与USING NAMESPACE调用某段 .NET 代码。判断依据是谁发起这次调用而不是哪种技术更强。Q2IAddin接口是怎么被找到的Start在什么时机被调用官方的判定标准是程序集内必须含有一个实现IAddin接口的类CAF 在加载程序集时通过反射查找这个类实例化它然后调用IAddin.Start。这意味着哪个类是 Addin是运行时由反射决定的配置文件只负责把程序集交给 CAF。由此推出三条硬要求实现类必须能被反射看到用public、构造函数不能需要参数、启动期工作统一写在Start里。Q3模块名Addins.xml与环境变量CAF_ADDINS_PATH是什么关系MODEL 模块对应哪个文件名每个 CAF 应用都有一个模块名Addins.xml列出该模块要加载的 Addin 程序集官方文档给出的实例是 MODEL 模块对应DesignAddins.xml。该文件默认放在与可执行文件同目录可以用环境变量CAF_ADDINS_PATH覆盖该路径并且官方明确支持用;分隔多个路径、支持 UNC 路径。工程上的意义是不动安装目录、把 Addin 放到企业共享位置批量部署。Q4CAF 相关的四个关键环境变量分别管什么AVEVA_DESIGN_EXE指向安装目录CAF_ADDINS_PATH指定 Addin 的位置覆盖默认装载文件路径CAF_UIC_PATH指定界面定义文件位置按模块名Customisation.xml查找并列出的.uicAVEVA_CAF_TRACING用于开发期跟踪。排查为什么没加载时AVEVA_CAF_TRACING是第一手段而把四个变量写进统一的启动脚本可以避免只在某台机器上能跑的问题。Q5为什么在配置里写了类名不等于Addin 一定会被加载因为这个前提本身不成立Addin 的判定不是配置声明而是CAF 反射查找程序集内实现IAddin接口的类。配置模块名Addins.xml负责把程序集交给 CAF至于程序集里哪个类充当 Addin、这个类能不能被实例化全在反射那一步决定。所以没被加载的核对清单只有三项程序集有没有被读到、装载文件路径对不对、类是否public且可无参实例化。Q6官方的示例工程在哪里、叫什么名字它们能帮我们解决什么问题官方示例代码包是安装目录下的Samples.zip内含 C# 源码与 Visual Studio 工程官方文档中出现过的示例工程名包括 AttributeBrowserAddin、ExamplesAddin、FlexibleExplorerAddin、NetGridExample、PMLNetExample、PMLGridExample。它们的用途是充当权威签名参照物当你无法确定接口方法签名、XML 配置节点名、设置注册方式时逐字对照这些真实工程比任何转述都可靠。官方并未公开可下载的独立示例仓库示例只随安装目录提供。