☰
PDMS-AVEVA-E3D二次开发教程(05):PML2 对象与自省——define object、成员类型与 methods()/attributes()
2026/10/17 11:36:48 网站建设 项目流程

版本声明(全系列统一口径)
本篇基线为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 里就能把一个陌生对象"看穿"。

〇认知问题

  1. PML2 的对象和 C# 的对象是同一个概念吗?能互相传吗?
  2. define object里的member有类型吗?能声明哪些类型?
  3. 方法体里的!this到底指谁?和!!CE是什么关系?
  4. 一个对象定义好之后,我怎么知道它有哪些成员和方法?难道要回去翻源码?
  5. 对象定义要放在什么文件里?.pmlobj是必须的吗?
  6. 我已经会用 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)。

三句话概括这段官方表述的实践含义:

  1. PML2 不是另一门语言,是同一体系的两代风格;
  2. PML2 的目标场景是"界面与对象",不是"更快地跑循环";
  3. 官方明确承认 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 才是元数据的权威来源。当你需要确认"某个对象类型有哪些成员与方法"时,查 SCRM(Software 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

SCRM 里真实存在的对象类型名(节选)包括: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……

注意观察这份名单的两个"信号":

  1. 里面有COLLECTION——集合也是对象类型,这与第 07 篇要讲的数据访问直接相关;
  2. 里面有COMMAND——官方对"交付入口"的定位就是把功能做成 command object。

另外,SCRM 还给出了"用object关键字创建对象实例"的真实写法(官方示例):!dim = object measure('mass'),该对象具备default()/suspend()/unsuspend()/reinstate()方法。这个例子很值得看一眼,因为它说明:PML2 里很多"对象"是产品内置的,你既能定义自己的对象,也会大量用到内置对象。

1.5 反直觉点:PML2 对象 ≠ .NET 对象

这是本篇最需要扭正的观念。三条边界:

  1. 不是一个东西。PML2 对象是在 PML 会话里用define object定义的数据载体,由 PML 解释器管理;.NET 对象由编译后的程序集定义、由公共语言运行时管理。名字都叫"对象",但生命周期、类型系统、可见性规则完全不同。
  2. 不能随便互相传递。官方明确列出 PML.NET 的限制:不能把DIRECTION、ORIENTATION等 PML 对象传给 .NET;但数据库引用(DB 引用)可以以数组或字符串形式传递。也就是说,要穿过 PML/NET 的边界,你得用"可序列化的形态"(数组或字符串),而不是直接把 PML 对象递过去。
  3. 模块切换不保持 .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 -- 读元素位置 endfunction

2.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 var:q 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 对象,跨模块功能必须显式重建对象。

四动手练习

练习 1(20 分钟):把 2.1 的对象与 2.2 的三步使用流程跑通。合格判定:q var !info.name打印出的名字与调用时刻的当前元素一致;随后把当前元素切成另一个元素,再重复第 3 步,输出不变(证明快照语义成立)。

练习 2(15 分钟):在 Command Window 里对!info执行.methods()与.attributes()。合格判定:能列出至少 1 个方法名与 4 个成员名;把本机的实际输出格式抄下来(这一条很重要,因为官方材料没有给出输出格式)。

练习 3(20 分钟):找出本环境接受的"方法结束语句"。合格判定:分别用endfunction与endmethod定义一个最小方法,各加载一次,记录哪一次成功、哪一次报什么错,并写出一行团队规范建议。

练习 4(25 分钟):把 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 不支持用户自定义成员变量或方法,以及窗体名不能与既有全局变量重名。

文末 FAQ

Q1:PML2 的对象和 C# 的对象是同一个概念吗?能互相传吗?
不是同一个概念。PML2 的对象是用define object在 PML 里定义的数据容器,由 PML 解释器管理;C# 的对象由编译后的 .NET 程序集定义、由运行时管理,两者的类型系统与生命周期规则不同。互传也受限:官方明确 PML.NET 的限制之一是不能把DIRECTION、ORIENTATION等 PML 对象传给 .NET,而数据库引用可以以数组或字符串形式传递;官方另一条限制是模块切换不保持 .NET 对象。所以跨语言时要传"可传递形态"(数组、字符串、DB 引用),而不是直接把 PML 对象递过去。

Q2:define 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()两个写法,前者列出可用方法、后者列出成员属性。要查"某个对象类型本来该有哪些成员和方法",权威来源是 SCRM(Software 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。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询