前言

单元模块:Block/Unit Operations Block

建议看此篇之前先看物性包开发入门篇,对ATL项目有个基础

在我们物性包开发入门篇中,我们说过了在流程模拟软件计算过程中,界面流股、单元模块以及物流对象之前的关系,如下图所示:

我们来把这个简化一下:

实际上在流程模拟软件里的流程是这样的:

我们在计算的时候所输入的信息,比如输入入口流股的信息,下面这个界面:

这就是界面流股,我们在界面流股中输入的信息,就会保存在这个入口界面流股的物流对象中

同样,单元模块也是的:

这些信息都会储存在单元模块的物流对象中,在单元计算完毕之后,同样会生成一个出口界面流股的物流对象,然后出口界面流股会将物流对象中的计算结果显示在流股结果中

单元模块的物流对象也会将单元模块的计算结果显示在单元模块的结果中

那么单元模块是怎么调用到物流对象的信息的,在C++中是使用了指针Pointer(实际上就是内存地址),单元模块中有一个指针,指向了物流对象,同样物流对象中也有指针指向了单元模块

在物性包开发入门篇我们也学习了,物流对象实际上就是一个ATL COM对象,然后这个对象里可以添加很多接口,这些接口的公共的基类就是一个IDispatch指针,那么也就意味着我们只需要在单元模块中保留一个IDispatch指针指向物流对象,那么单元模块就可以访问到物流对象中的所有接口

单元模块中的接口

在CAPE-OPEN官网的文档集里有这么一个文件: CO_Unit_Operations_v6.25.pdf ,和物性包的文档类似,它就是单元模块的一个文档,其中规定了如下接口:

同样,单元模块也是一个ATL COM对象,如果在物流对象中保留一个IDispatch指针指向单元模块,那么物流对象也可以去访问到上述单元模块中的所有接口

也就是说,单元模块开发完毕,并且加载到流程模拟软件里之后,单元模块和物流对象之间就是一个交换指针,也就是交换内存地址的一个过程

ICapeUnit接口: 单元模块

第一个函数 GetValStatus() ,val就是validation,这个函数的意思就是判断单元模块是否可用,也就是看模块的参数是否输入完毕

Calculate() ,顾名思义,就是执行计算

GetPorts() ,是获取流股的端口,流程模拟软件在执行单元模块连接的时候需要获取单元模块的端口,有几个输入口,有几个输出口等,这个接口是供流程模拟软件来调用的

Validate() ,是进行单元模块是否可用的判断函数,和第一个接口的功能实际上有点重复

ICapeUnitPort接口: 单元模块端口,负责单元模块端口连接流股的连接与断开,实际上该接口与第一个接口并不是一个平级的关系,而应该是包含在单元模块中的

后面两个接口现在来说并不重要,不在入门篇中讨论

创建项目

如上文所述,单元模块也是一个ATL COM对象,那么也是创建一个ATL项目:

名字自己定义,后面默认创建:

创建的这个只是一个项目,名字叫MyBlockTest,并不是说这就是一个单元模块,这个项目中会包含单元模块、物流对象等

下面要创建的ATL对象,才是真正的单元模块

创建好之后,我们第一步就是添加一个ATL对象:

添加之后的弹窗默认即可,添加完成之后编译一下,这里编译应该是没有问题的,如果编译报错逐用户重定向问题,更改项目属性即可:

实现接口

回到上文中的接口图:

在上文的理解中,对于CAPE-OPEN的单元模块来说,其实最重要的就是第一个 ICapeUnit 接口,那么现在就来学习和实现这一个接口

首先呢,CAPE-OPEN和物性包开发类似,也提供了一个单元模块的示例用来学习和示例,在官网上下载下来:https://colan.repositoryhosting.com/trac/colan_examples/downloads

下载下来后解压备用

和物性包开发一样,添加接口就需要将CAPE-OPEN的标准接口文件进行导入,在下载好的示例文件中找到下面这个文件,复制到项目根目录中:

然后添加实现接口:

导入进来之后会发现,编译是不通过的,同时编辑器也会提醒错误:

这并不是说导入接口的方式是错误的,看过物性包开发入门篇的,应该都知道,这是CAPR-OPEN标准自己的定义问题,所以还是老方法,从CAPE-OPEN标准提供的示例中,找和现在导入的接口方法有什么不同,就是错误之处

第一处错误:

第二处错误:

更改了这两处错误之后可以发现,编译虽然还是有警告,但已经通过了

注册单元模块

和热力学物性包类似,新开发的单元模块要被流程模拟软件识别到,就需要给单元模块加上“特征码”

同样我们也不需要自己写,直接使用CAPE-OPEN的模板即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
'Implemented Categories'
{
{678C09A5-7D66-11D2-A67D-00105A42887F}
{678C09A1-7D66-11D2-A67D-00105A42887F}
{4150C28A-EE06-403f-A871-87AFEC38A249}
{0D562DC8-EA8E-4210-AB39-B66513C0CD09}
{4667023A-5A8E-4CCA-AB6D-9D78C5112FED}
}
CapeDescription
{
val Name = s 'CPP Mixer Splitter Example'
val Description = s 'Microsoft Visual C++ 2005 Mixer and Splitter Example according to CAPE-OPEN Unit Operation specification'
val CapeVersion = s '1.0'
val ComponentVersion = s '1.0.2'
val VendorURL = s 'http://www.colan.org/'
val About = s 'See http://www.colan.org/ for more information'
}

编译一下,没啥毛病

调用单元模块

以AspenV11为例,之前在物性包开发入门篇中也讲到了如何使用aspen调用CAPE-OPEN的模块,重新来实现一下:

可以看到图中已经成功的创建了一个上文中写的单元模块,但是此时这个单元模块是没有任何界面和功能的,也无法进行流股的连接,这是因为这些功能在示例中还没有实现,下面就来实现这些功能

单元模块端口

在实际开发中,可能这个单元模块的端口并不只是一个,所以在本文中导入的函数中:

ports 是一个数组类型的,但是普通的C++数组是无法实现这个功能的,它是一个ATL规定的数组类型,该类型在CAPE-OPEN标准中也进行了规定

在CAPE-OPEN标准文档里有这个文件: Collection_Common_Interface.pdf ,这个文档规定了开放集合公共接口,如图:

可以看到,是很简单的,Count() 函数用于获取数组的长度,Item 则是获取特定位置的数组成员。这里我们试着来实现它也就是说要编写的ATL数组必须要遵循 ICapeCollection 这个接口规范才可以,那么就来实现这个ATL数组,首先给项目添加一个ATL对象:

点击添加之后的弹窗默认即可

如果这里弹出了idl文件路径报错,请参考物性包开发入门篇2中的报错处理

编译一下,没啥毛病,继续添加 ICapeCollection 接口:

添加完成之后编译一下,没毛病,继续在 MyBlockPortsArray.h 中编写实现端口的代码:

可以看到,本文在这里修改了参数的变量名,因为在C++语法里,是不允许函数名和变量名同名的,但是在CAPE-OPEN标准导入进来的接口中是同名的Cont,所以要进行修改一下

接下来在 MyBlock.h 中编写获取端口数,首先添加头文件:

写入创建实例:

编译一下,没啥毛病,已经获取到了端口数,那么接下来肯定就是要让流程模拟软件知道去连接哪个端口,在上文的接口图中已经了解到 ICapeUnitPort 接口就是用来描述单元模块端口的

那么继续添加这个接口ATL对象:

再给BlockPort添加 ICapeUnitPort 实现接口:

编译一下,果不其然又报错了,同样啊,导入的这个接口是有问题的,还是老方法,按照CAPE-OPEN提供的示例对照一下,看是哪里又写错了:

好的,纠正了这两个参数类型之后编译已经没有问题了,现在来编写获取端口的方法

回到 MyBlockPortsArray.h

由于 Item() 函数实现起来比较麻烦,入门篇暂时先搁置,本文现在假设该单元模块仅有一个流股端口,那么怎么实现端口的获取呢,为了方便学习和理解,本文直接在文件头部进行声明:

编译一下,没啥毛病,但是现在在aspen中发现还是无法连接流股,猜测是上文中的 CMyBlockPortsArray 实例和端口1一样,不能每次临时在方法中进行创建,应该在头部全局创建,回到文件 MyBlock.h 文件,将 CMyBlockPortsArray 在头部全局创建:

和上文的端口1不一样,这里还是在函数内实例化

然后再来到 BlockPort.h 文件,给上述创建的端口1赋值:

编译一下,没啥毛病,再来aspen里试一下,好吧,aspen还是识别不到,但是可以看到COFE已经成功识别到了刚才创建的这个端口:

没有实现流股连接是因为目前 BlockPort.h 接口中的 Connect 函数还没有实现,下文再讲,现在来总结一下

在上文中提到,单元模块中包含一个 BlockArray 的数组,这个数组中又有多个 BlockPort 端口,而这个端口中就储存着对应的物流对象的指针,也就是内存地址,要做到对物流对象的连接,就需要让 Connect 函数记录下该物流对象

物理对象的连接

同样,为了方便理解,还是在 BlockPort.h 文件的头部创建一个物流对象实例:

修改上文中的连接方式:

编译一下,没毛病,aspen测试还是不行,但是COFE已经可以成功连接上流股了:

aspen为什么不可以连接呢,aspen对于单元模块的流股连接规定的更为严格,不只是要通过 ICapeUnit 这一个接口,还要通过 ICapeUtilities 等接口来继承

本篇到这里就暂告一段落,等待下一篇再来完善这个模块~