PDMS-AVEVA-E3D二次开发教程(05):PML2 对象与自省——define object、成员类型与 methods()/attributes() 版本声明全系列统一口径本篇基线为AVEVA E3D Design 3.1。对象示例GETELEMENTINFORMATION、成员访问与自省方法出自官方培训材料与官方文档原文摘录PML2 的定位表述出自官方文档https://help.aveva.com/AVEVA_Everything3D/3.1/PMLPUG/PMLCG2.02.3.html。凡本系列按官方已发表写法组合构造、未在本机跑通的代码均标注**“按官方已发表的写法组合构造需在本地 E3D Design 环境验证”**官方材料未给出的语法一律不臆造只在正文中显式留出待核实标记。官方把 PML 交互入口称为Command Window命令窗口Tools 标签 → Display 组 → Commands。一句话结论PML2 的对象是写 PML 的人自己定义的数据载体成员访问一律用点号、把自己叫!this——它和 .NET 的对象不是一回事而methods()与attributes()这两个自省方法让你在 Command Window 里就能把一个陌生对象看穿。〇认知问题PML2 的对象和 C# 的对象是同一个概念吗能互相传吗define object里的member有类型吗能声明哪些类型方法体里的!this到底指谁和!!CE是什么关系一个对象定义好之后我怎么知道它有哪些成员和方法难道要回去翻源码对象定义要放在什么文件里.pmlobj是必须的吗我已经会用 PML1 了为什么还要学 PML2 对象什么时候用它更划算一机制解析1.1 PML2 是为界面而生的面向对象扩展先把官方定位抄下来PML2 是面向对象语言专为定制 AVEVA 产品的 Forms and Menus 而设计。并且官方说明它与 PML1 的关系是当前版本常称 PML2可视为对 PML1 能力的扩展但**“许多编程任务的做法不同”而某些任务用 PML1 更高效**https://help.aveva.com/AVEVA_Everything3D/3.1/PMLPUG/PMLCG2.02.1.html。同时PML1 的 expressions package 在数据库手册DBRM中单独成章官方口径是一般来说这些设施与 PML 2 兼容https://help.aveva.com/AVEVA_Everything3D/1.1/DBRM/DBRM9.10.01.html。三句话概括这段官方表述的实践含义PML2 不是另一门语言是同一体系的两代风格PML2 的目标场景是界面与对象不是更快地跑循环官方明确承认 PML1 在某些任务上更高效——所以全都改成 PML2不是进步是没读懂官方定位。1.2define object与成员类型官方培训材料的对象示例原样摘录define object GETELEMENTINFORMATION member .element is DBREF member .name is STRING member .type is STRING member .position is POSITION endobject结构要点define object 名字…endobject定义一个对象类型。对象名同样处在全局命名空间里所以第 04 篇讲的变量名不能与既有对象/全局变量重名在这里同样适用——定义一个与已存在对象同名的类型会互相覆盖。member .名字 is 类型成员变量声明。.名字前面的点号是成员声明语法的一部分和访问时写的点号是配套的。成员是带类型的示例中出现的是DBREF、STRING、POSITION三种。DBREF数据库引用即指向模型里某个元素的引用。这是二次开发里最重要的成员类型因为模型数据访问的入口就是它。STRING字符串。POSITION位置坐标。为什么成员要带类型因为对象是数据的容器。类型声明让你在写方法时能确认这个成员拿到的是元素引用、不是名字字符串——这是 PML2 相对 PML1 的实质性收益之一。文件放哪按官方培训材料口径对象放在.pmlobj文件里与.pmlfnc、.pmlfrm并列依赖环境变量PMLLIB定位。惯例同样是对象名即文件名。1.3define method与!this官方同一份示例中的方法部分原样摘录注意其结束语句见下方说明define method .getinformation() !this.element !!ce !this.name !!ce.name !this.type !!ce.type !this.position !!ce.position endfunction逐层拆解define method .getinformation()方法名以点号开头表示这是对象的成员方法。括号里是参数列表这里为空。!this指调用这个方法的那个对象实例。所以!this.element !!ce的语义是把当前元素!!ce存进我这个对象实例的element成员。!!ce与!this的关系!!ce是全局的当前元素——它是环境状态不是这个对象的成员。方法体里做的事正是把环境状态快照进对象。这是对象化最典型的价值把此刻的当前元素固化成一份可携带的快照之后无论当前元素怎么变这份快照都不变。!!ce.name/!!ce.type/!!ce.position读取当前元素的三类属性。注意这三个属性名是元素的属性不是你自己对象的成员写法上与!this.name很像都是点号但一个是元素属性、一个是你自己的成员——这是新手最容易混淆的地方。关于结束语句的一处必须点明的细节本篇依据的摘录在这里用的是endfunction而官方另一处表单方法示例用的是endmethod。两处原文如此本系列照录并不做取舍。实践建议在你本机的 Command Window 里各写一次确认本环境接受哪个并在团队代码规范里统一——这属于看起来很琐碎、实际会导致文件加载失败的细节。1.4 自省methods()与attributes()自省是 PML2 最实用的能力之一官方材料给出的两个方法原样摘录!exampleObject.methods() !exampleObject.attributes().methods()列出该对象可用的方法。.attributes()列出该对象的成员属性。这两个方法的价值在于反向可读拿到别人写的对象或者自己半年前写的对象你不必回去翻源码直接在 Command Window 里创建实例、调.methods()和.attributes()就能把它的接口看明白。这在排查这个方法名到底叫什么时比翻文档快得多。进一步SCRM 才是元数据的权威来源。当你需要确认某个对象类型有哪些成员与方法时查 SCRMSoftware Customisation的这几节Summary of Objects, Members and MethodsMethods Available to All ObjectsForms and Menus ObjectsSummary of Gadget-Specific MethodsGadget Syntax GraphsObject Type DetailsSCRM 里真实存在的对象类型名节选包括ALERT、ARC、ARRAY、BANNER、BAR、BLOCK、BOOLEAN、BORE、BUTTON、CMSYS、COLLECTION、COLUMN、COLUMNFORMAT、COMBOBOX、COMMAND、CONTAINER、DATEFORMAT、DATETIME、DB、DBREF、DBSESS、DIRECTION、EXPRESSION、FILE、FMSYS、FORM、FORMAT、FRAME……注意观察这份名单的两个信号里面有COLLECTION——集合也是对象类型这与第 07 篇要讲的数据访问直接相关里面有COMMAND——官方对交付入口的定位就是把功能做成 command object。另外SCRM 还给出了用object关键字创建对象实例的真实写法官方示例!dim object measure(mass)该对象具备default()/suspend()/unsuspend()/reinstate()方法。这个例子很值得看一眼因为它说明PML2 里很多对象是产品内置的你既能定义自己的对象也会大量用到内置对象。1.5 反直觉点PML2 对象 ≠ .NET 对象这是本篇最需要扭正的观念。三条边界不是一个东西。PML2 对象是在 PML 会话里用define object定义的数据载体由 PML 解释器管理.NET 对象由编译后的程序集定义、由公共语言运行时管理。名字都叫对象但生命周期、类型系统、可见性规则完全不同。不能随便互相传递。官方明确列出 PML.NET 的限制不能把DIRECTION、ORIENTATION等 PML 对象传给 .NET但数据库引用DB 引用可以以数组或字符串形式传递。也就是说要穿过 PML/NET 的边界你得用可序列化的形态数组或字符串而不是直接把 PML 对象递过去。模块切换不保持 .NET 对象。官方限制之一——这意味着在 A 模块里创建的 .NET 对象切到 B 模块后不能指望还在。设计跨模块功能时必须显式重建。实践结论把 PML2 对象当作结构清晰的数据容器 方法集合来用不要把它当作能跨语言传的通用对象。1.6 什么时候该用 PML2 对象场景建议理由一次性批量操作PML1 宏官方明确某些任务用 PML1 更高效宏无需定义类型需要把多个属性绑成一份记录PML2 对象member 类型声明让数据有形状避免一堆散变量需要把同一份逻辑作用在很多元素上PML2 对象方法可复用!this提供稳定上下文要做表单界面PML2官方定位PML2 为 Forms and Menus 设计窗体本身就是全局变量表示的对象要对接 .NETPML.NET不是 PML2 对象跨边界只能传数组/字符串等可传递形态单纯做数值计算PML1 函数.pmlfnc函数有类型化签名比对象更轻一条判断口诀“要不要一份带形状的数据”——要就用 PML2 对象只是跑一段过程——用 PML1。二完整代码与逐行剖析2.1 官方对象示例完整复刻-- 文件GETELEMENTINFORMATION.pmlobj -- 来源官方培训材料对象示例原样复刻 -- 说明结束语句按原文照录为 endfunction官方另一处表单方法示例用的是 endmethod -- 请在本机确认本环境接受哪个并统一团队规范。 define object GETELEMENTINFORMATION member .element is DBREF -- 指向模型元素的数据库引用 member .name is STRING -- 元素名 member .type is STRING -- 元素类型 member .position is POSITION -- 元素位置 endobject define method .getinformation() !this.element !!ce -- 把当前元素存进本实例快照 !this.name !!ce.name -- 读元素名 !this.type !!ce.type -- 读元素类型 !this.position !!ce.position -- 读元素位置 endfunction2.2 使用与逐行剖析在 Command Window 里使用它创建实例 → 调方法 → 读成员-- 第 1 步创建对象实例对象名处在全局命名空间 !info object GETELEMENTINFORMATION() -- 第 2 步把当前元素的信息快照进来 !info.getinformation() -- 第 3 步读出快照此时与 !!ce 已经无关改选别的元素也不影响它 q var !info.name q var !info.type q var !info.position逐行剖析!info object GETELEMENTINFORMATION()用object 对象名()的形式创建实例。这与官方 SCRM 里!dim object measure(mass)是同一种构造写法后者是内置对象。该构造写法源自官方示例需在本地 E3D Design 环境验证你的自定义对象是否同样适用。!info.getinformation()调用方法。方法名前是实例变量、方法名后带括号——!info.getinformation()而不是getinformation(!info)。这是 PML2 与 PML1 在调用风格上的最大区别之一。q var !info.name/!info.type/!info.position读成员。成员访问一律点号且可与q var组合做调试输出。验证快照语义先跑上面三步然后在模型里把当前元素切到别的元素再重复执行第 3 步的三行——如果输出的仍是原元素的信息就证明方法体里的赋值确实是拷贝到了本实例而不是存了个引用到当前元素。2.3 自省演示官方写法-- 自省把这个对象看穿 !info.methods() -- 列出可用方法官方写法 !info.attributes() -- 列出成员/属性官方写法预期用途.methods()会列出你定义的方法名如getinformation.attributes()会列出element、name、type、position四个成员。注意具体输出格式与是否包含继承来的方法本篇依据的材料没有说明请以本机实际输出为准并记录——这正是自省的价值让我猜变成我看到。2.4 一个实用对象元素类型校验器按官方已发表的写法组合构造需在本地 E3D Design 环境验证这个对象解决一个真实问题在批量脚本前先确认当前元素是不是我期望的类型避免在错误的元素上执行写操作。-- 文件CETYPEWATCH.pmlobj -- 说明只使用官方示例里出现过的成员类型DBREF / STRING / POSITION与方法写法 -- 属按官方已发表的写法组合构造需在本地 E3D Design 环境验证。 -- 用途抓取当前元素的类型快照并输出一条人可读的结论。 define object CETYPEWATCH member .snapref is DBREF -- 快照时的元素引用 member .snapname is STRING -- 快照时的元素名 member .snaptype is STRING -- 快照时的元素类型 endobject define method .capture() !this.snapref !!ce !this.snapname !!ce.name !this.snaptype !!ce.type endfunction define method .report() P --- CETYPEWATCH report --- P element name : $!this.snapname P element type : $!this.snaptype endfunction逐行剖析三个成员都是快照字段一个DBREF保留引用 两个STRING人可读信息。为什么不只存STRING因为DBREF是后续操作的入口——要真正操作这个元素而不只是打印名字必须持有引用。!this.snapref !!ce与官方示例完全同构的一行把当前元素存进实例。define method .report()第二个方法。注意到这里已经出现两个方法——这正是对象相对散变量的收益数据和作用于数据的逻辑被绑在一起。P element name : $!this.snapname把成员值注入输出文本。$!this.snapname用的是第 03 篇讲过的变量注入机制。这里的!this.成员与$!变量的组合写法是按已知机制组合的需在本机验证若你的环境不接受这种写法退回到先赋值给局部变量再注入的两步写法!t !this.snaptype然后P element type : $!t这一点同样需要本机确认。报告方法里不要写q varq var更适合调试时看原始值而写成键值对形式便于日志检索与人工核对。批量作业的日志规范应当统一成这种形式。三常见报错与排查症状一对象定义之后创建实例时报找不到对象。现象!x object MYOBJECT()报错。原因.pmlobj文件所在目录不在PMLLIB里或者文件没被加载也可能对象名与已有对象重名导致覆盖。处置用第 02 篇的PMLLIB探针确认目录已覆盖检查对象名是否与既有对象/全局变量重名确认文件的扩展名为.pmlobj。症状二方法定义处直接报语法错。现象define method ...块加载失败。原因结束语句不一致。官方材料的两处示例分别用了endfunction与endmethod不同环境/版本可能只接受其中一种。处置在 Command Window 里用最小样例各试一次确定本环境接受的写法并在团队规范里统一同时确认方法名以点号开头。症状三!this.xxx读出来是空的。现象!info.name为空但元素明明有名字。原因没有先调用getinformation()或capture()。成员初始为空只有调了抓取方法才有值。这是数据容器语义的自然结果不是 bug。处置在建实例的下一步立刻调抓取方法养成先快照、后使用的固定顺序。症状四把 PML 对象往 C# 里传报类型不匹配。现象PML.NET 调用时参数转换失败。原因官方限制明确——不能把DIRECTION、ORIENTATION等 PML 对象传给 .NET跨边界要使用可传递形态。处置数据库引用可以用数组或字符串形式传递改用这种方式同时记住模块切换不保持 .NET 对象跨模块功能必须显式重建对象。四动手练习练习 120 分钟把 2.1 的对象与 2.2 的三步使用流程跑通。合格判定q var !info.name打印出的名字与调用时刻的当前元素一致随后把当前元素切成另一个元素再重复第 3 步输出不变证明快照语义成立。练习 215 分钟在 Command Window 里对!info执行.methods()与.attributes()。合格判定能列出至少 1 个方法名与 4 个成员名把本机的实际输出格式抄下来这一条很重要因为官方材料没有给出输出格式。练习 320 分钟找出本环境接受的方法结束语句。合格判定分别用endfunction与endmethod定义一个最小方法各加载一次记录哪一次成功、哪一次报什么错并写出一行团队规范建议。练习 425 分钟把 2.4 的CETYPEWATCH落地并加一条类型不符就警告的逻辑用IF (!this.snaptype eq ...) THEN的结构类型标识请用你自己模型里真实存在的类型判断。合格判定在两种不同类型的元素上各跑一次报告内容不同且不一致时能看到明确提示。五小结与下一篇预告本篇钉住四点PML2 是面向对象的扩展专为 Forms and Menus 设计官方承认有些任务 PML1 更高效对象用define object定义、成员用member .x is 类型声明示例中出现的是DBREF/STRING/POSITION、方法用define method定义、方法体里!this指对象实例成员访问一律点号methods()/attributes()是现成的自省入口SCRM 是元数据权威来源PML2 对象与 .NET 对象不是一回事跨边界只能传数组或字符串等可传递形态。下一篇《表单开发.pmlfrm》进入 PML2 的主场setup form与formTitle、gadget 作为窗体对象的成员、点号访问与.val取值、回调其实是文本字符串形式的 PML 表达式/函数/方法调用、FMSYS/CMSYS的作用与布局要点——并揭穿两条硬限制gadget 不支持用户自定义成员变量或方法以及窗体名不能与既有全局变量重名。文末 FAQQ1PML2 的对象和 C# 的对象是同一个概念吗能互相传吗不是同一个概念。PML2 的对象是用define object在 PML 里定义的数据容器由 PML 解释器管理C# 的对象由编译后的 .NET 程序集定义、由运行时管理两者的类型系统与生命周期规则不同。互传也受限官方明确 PML.NET 的限制之一是不能把DIRECTION、ORIENTATION等 PML 对象传给 .NET而数据库引用可以以数组或字符串形式传递官方另一条限制是模块切换不保持 .NET 对象。所以跨语言时要传可传递形态数组、字符串、DB 引用而不是直接把 PML 对象递过去。Q2define object里的member有类型吗能声明哪些类型有类型写法是member .名字 is 类型。官方培训材料的对象示例中出现的是三种DBREF数据库引用指向模型元素的引用最常用于数据访问、STRING字符串、POSITION位置/坐标。成员带类型是 PML2 相对 PML1 的实质性收益之一它让这个成员里装的是元素引用还是名字字符串变成显式约定。需要声明更多类型时请到 SCRM 的Object Type Details与Summary of Objects, Members and Methods两节查找本环境支持的类型名。Q3方法体里的!this到底指谁和!!CE是什么关系!this指调用这个方法的那个对象实例而!!ce是全局的当前元素属于环境状态、不是对象成员。官方示例里二者的配合正是对象化的典型用法!this.element !!ce把此刻的当前元素存进对象实例形成一份快照——之后当前元素怎么变这份快照都不受影响。!this.name与!!ce.name写法上都是点号但前者是读你自己的成员、后者是读元素的属性这是最容易混淆的一处务必分清。Q4一个对象定义好之后我怎么知道它有哪些成员和方法难道要回去翻源码不必翻源码PML2 提供自省官方材料给出!exampleObject.methods()与!exampleObject.attributes()两个写法前者列出可用方法、后者列出成员属性。要查某个对象类型本来该有哪些成员和方法权威来源是 SCRMSoftware Customisation它的相关章节为 Summary of Objects, Members and Methods、Methods Available to All Objects、Forms and Menus Objects、Summary of Gadget-Specific Methods、Gadget Syntax Graphs、Object Type Details。另注意客观输出格式官方材料没有给出请以本机实际输出为准并记录下来。Q5对象定义要放在什么文件里.pmlobj是必须的吗按官方培训材料口径PML 的三类文件是.pmlfnc函数、.pmlfrm表单、.pmlobj对象它们依赖环境变量PMLLIB来定位。所以对象应放在PMLLIB覆盖的目录下、以.pmlobj为扩展名并建议对象名即文件名。还有一条容易忽略的约束对象名处在全局命名空间里与既有对象、全局变量PML2 里窗体就是全局变量重名会互相覆盖所以命名前先确认没有冲突。Q6我已经会用 PML1 了为什么还要学 PML2 对象什么时候用它更划算因为官方对二者的定位不同PML2 是面向对象的扩展专为定制 AVEVA 产品的 Forms and Menus 设计而官方同时承认**“某些任务用 PML1 更高效”**。划算的边界大致是这样需要一份有形状的数据多个属性绑成一条记录、需要跨步骤携带时用 PML2 对象比如把元素引用 名字 类型 位置绑成一个快照对象需要做表单界面时必须用 PML2因为窗体本身就是用全局变量表示的对象而纯粹的一次性批量操作、单纯的数值计算用 PML1 宏或.pmlfnc函数更轻。判断口诀要一份带形状的数据 → PML2 对象只跑一段过程 → PML1。