前言

在之前的文章中,我们已经说过了流程模拟软件是如何去调用和计算的,并且自己写了一个空的物性包管理者接口,今天我们就来实现物流对象模板和配置好的物流对象

在C++中,物流对象模板就类似于class类,而配置好的物流对象就是对象

前文中的物性包管理者接口我们是通过ATL简单对象来添加的,同样,物流对象模板也是一个ATL对象,不过是储存在物性包管理者内部的

添加物流模板类

如果这里出现了idl文件路径错误,如下:

报错处理

那么恭喜你,这是VS2022的傻逼bug,此时不要慌,首先把我们新建失败的ATL对象进行一个删除,选择查找,在所有文件中查找新建ATL对象的名字,在下列两个文件中删除新建ATL对象的引用:

第一个就是我们的idl文件,删除下面新增的这一行:

这个箭头指错了,应该是右上角那个rc文件

第二在Resource这个头文件中,删除新增的这一行:

最后,再删除新增的rgs文件:

这样我们就成功的删除了添加失败的ATL对象,现在开始重新添加

首先需要将我们项目的这个idl文件,重新换一个位置,比如在项目根目录下新建一个文件夹,把这个idl文件复制进去,然后点击添加现有项,导入复制的idl文件

然后就会发现我们有两个idl文件:

这个时候需要进入项目根目录,直接删除根目录中的这个idl文件,此时VS里会提示该文件已被删除:

然后我们直接再解决方案资源管理器中删除这个idl文件,再重新添加ATL对象,就可以发现可以成功添加了:

而且此时可以看到,我们的idl文件又在项目根目录下生成了,这时候回到我们刚才新建的idl文件夹内,将我们已经注册新的ATL对象的idl文件,直接剪切,然后替换项目根目录下的idl文件就可以了

后面如果还要添加新ATL对象的话,还是得这么操作,很莫名其妙,不知道是我自己电脑的bug还是其他人也会遇到

多说一嘴,我在下面添加接口的时候遇到了同种类问题:

如果这里报错 pch.cpp 文件问题,是一次性添加多个接口的问题,不知道为什么VS2022不允许一次性添加多个,可是我看的教程明明人家可以添加多个,如果要解决这个错误只能添加一个接口,重新编译整个项目,保存,再添加一个再重新编译一次,好麻烦啊,不知道是哪里我没设置好还是什么

建议一个一个接口进行添加,如果一次性添加多个接口可能会出现一个接口被重复添加的错误

大胆猜测是idl文件或者pch文件在被自动更改之后触发VS的文件保护了,需要重新加载一次,也就是重新编译、保存一次就好了

记得对照一下左边的项目目录,如果有多出来的其他文件,估计是刚才新建ATL对象失败所产生的文件:

编译了一下,没啥毛病:

添加物流模板中的接口

在新建好物流对象之后就需要给物流对象继续添加接口,切换到类视图,找到新建的 MaterialObject ,添加接口,我们要添加什么接口呢,回到之前我们的文章,找到接口列表:

上篇文章中我们已经添加了第一个 ICapeThermoPropertyPackageManager ,也就是物性包管理者,那么我们的物流对象需要添加哪些接口呢,除了物性包管理者之外的所有:

ICapeThermoMaterialContextICapeThermoUniversalConstants 暂时不用管,后续再讲

这里建议 ICapeThermoPropertyRoutine 接口也不添加哈,后面会报错,报错原因下文会说

如果这里报错 pch.cpp 文件问题,是一次性添加多个接口的问题,不知道为什么VS2022不允许一次性添加多个,可是我看的教程明明人家可以添加多个,如果要解决这个错误只能添加一个接口,重新编译整个项目,保存,再添加一个再重新编译一次,好麻烦啊,不知道是哪里我没设置好还是什么

可以看到哈,已经添加成功了:

此时我们编译一下,会发现有如下的报错:

不要慌哈,这是由于 ICapeThermoPropertyRoutine 接口出现的问题,CAPE-OPEN标准已经比较久远了,出现了部分代码上的bug,我们暂时不用管哈

那么我们就暂时不适用这个接口了,需要注释掉下面几行:

这里注意要删掉上面一行最后的逗号,否则会出现报错的哈

最后注释掉这一整个类:

这个时候重新编译一下,已经没有问题了,接着学习

添加物性集列表

前文中我们已经知道了,当流程给物性包管理者发出请求之后,会返回一个物性包的列表,也就是 VARIANT *GetPropertyPackageList 这个 VARIANT 在COM中就是一个数组,后面自然就是这个数组的名字

那么返回的这个数组怎么实现呢,我们也不用自己去研究,CAPE已经写好了,我们只需要调用即可,打开我们之前下载好的源文件目录,找到下列两个头文件:

BSTR.hVariant.h 这两个文件,都在这个目录下,然后复制到我们的项目根目录下,添加到项目头文件中:

FulidPackageManager.h 中添加引用:

BSTR.h文件已经在Variant中引用了

接下来就应该有返回的物性集列表了,如果一个物性包是在其他软件导出的,那么这个列表其实应该在软件中已经创建好了,我们这里是自己开发的,所以物性集列表我们现在直接写在代码中,方便学习和理解:

但是此时如果编译的话会产生错误,形参传输不正确,主要是因为我们这里的参数类型和标准里的不一致

为了学习和理解方便,我们对标准的 Variant.h 文件进行修改:

文件开头添加:

第246行给参数添加命名空间声明:

这样就可以编译通过啦

返回物性集列表

上文中我们为了方便学习和理解,手动用代码写了一个物性集列表,中含有两个状态方程,接下来我们开始学习如何返回这个列表

现在软件就可以访问到我们返回的这个数组了,重新编译,测试一下:

可以看到COFE已经成功访问到我们这个物性集列表了,接下来就是实现选择某个物性集和调用了

选择物性集并创建物流对象

FluidPackageManager.h 先写好选择了我们返回的物性集列表中的一个物性集的操作逻辑:

根据BSTR.h文件中的规则,这里不能使用 == 来对比物性包名的值,而是使用 .Same() 方法

接下来我们要干什么呢,在前文的流程里,我们说过,当流程/工段获取到了物性集列表之后,会进行选择,然后物性包管理者会将你选择的这个物性集加载(或者说安装)到物流模板中,也就是我们前文创建的 MaterialObject 中,生成一个含有固定组分、物性集的物流对象

那么现在就是要生成一个含有固定组分、物性集的物流对象,创建这个物流对象是很复杂且麻烦的,为了方便学习和理解这个过程,我们对这个步骤进行简化

首先,当我们选择了物性集列表之后,我们需要创建一个ATL对象来存储组分、用的什么状态方程等这些信息,CAPE-OPEN标准( PropertyPackageManager.h )里是通过下列代码:

1
2
3
4
5
6
7
8
9
STDMETHOD(GetPropertyPackage)(BSTR PackageName, LPDISPATCH *Package)
{
if ((!PackageName)||(!Package)) return E_POINTER;
CComObject<CPropertyPackage> *p;
CComObject<CPropertyPackage>::CreateInstance(&p);
p->name=PackageName;
p->QueryInterface(IID_IDispatch,(LPVOID*)Package);
return NOERROR;
}

可以看到CAPE-OPEN是通过 CComObject 函数来创建ATL对象的,CPropertyPackage 对应的就是我们之前创建的ATL对象(物流对象) MaterialObject 中的 CMaterialObject ,这样我们创建的这个 *p 就是一个物流对象的实例

那么是怎么返回到物流对象中的呢,我们创建的这个物流对象实际上是一个ATL对象,它是通过 IID_IDispatch 这个接口继承的,自然就可以将要返回的物流对象(ATL对象)转换成一个 IDispatch 对象

这样有什么作用呢,就是通过 *Package 这个指针记录我们物流对象中所赋值的内容,也就是 p->name=PackageName; 语句中写的,以达到返回这些数据的目的

最后 return NOERROR; 返回返回成功的状态码,那么我们现在来写我们自己的实例:

实际上我们的组分列表在获取的时候并不是直接获取组分名称,组分在创建的时候就会创建唯一的key值,我们调用组分是通过这个key值进行的,这里为了示例,直接使用111、222来代替H2O和CO2

同样,下方的SRK与这部分代码大同小异:

1
2
3
4
5
6
7
8
CComObject<CMaterialObject>* p;
CComObject<CMaterialObject>::CreateInstance(&p);
p->myEos = "SRK";
p->myCompounds.push_back("333");
p->myCompounds.push_back("444");
p->myCompounds.push_back("555");
p->QueryInterface(IID_IDispatch, (LPVOID*)GetPropertyPackage);
return S_OK;

同时为了让我们的物性包管理者访问到物流对象模板,需要通过头文件进行引用:

MaterialObject.h 文件中添加对应的头文件和类:

编译一下,好,没问题,用COFE测试一下:

OK,COFE成功连接到了我们的物性包

实现接口中的函数

我们回到之前的CAPE标准接口图:

举个例子, ICapeThermoPropertyRoutine 接口的 CalcSinglePhaseProp() 函数,函数名直译过来就是计算单相工具,也就是一个单相态计算函数

那么什么是单相态计算呢,例如密度计算,就是液态或气态单相去计算的

现在来做一个示例,填充一下 ICapeThermoCompounds 接口的 GetCompoundList() 函数,在 MaterialObject.h 文件中找到该接口的该函数:

可以看到该函数有很多参数,我们不一一详细写了,只是做个示例,去怎么补充这些接口里的函数

实际上我们应该都清楚,获取组分列表以及相关参数已经是需要调用数据库了,但我们这里也是为了学习和理解方便,直接在代码里写上相应的数据:

先引入头文件:

再补充函数逻辑:

再返回组分数:

编译一下,没啥问题,OK

实际上现在我们的软件还只是可以访问到物性包列表,还是访问不到我们创建的两个组分的,因为补充的函数太少了,实际上可以看到空白的函数是非常多的,目前只是做一个示例,尤其是 ICapeThermoPropertyRoutine 这个接口还导入失败了,后面再继续进行深入学习

实现物性计算

在上文中,我们添加了一个 ICapeThermoPropertyRoutine 接口,但是因为报错的原因暂时注释掉了,没有使用,现在暂时没办法解决,但是不妨碍我们演示如何进行单相物性计算

我们先取消这个接口的函数注释:

在前文中我们讲过,单元模块执行计算,是通过单元模块中输入的参数,如温度、压力等,然后将这些参数传给物流对象,物流对象再去调用热力学部分进行计算

那么我们首先来实现单元模块如何告诉物流对象你要进行什么计算:

MaterialObject.h 文件中,先创建两个全局变量存放温度、压力:

image-20240121094938685

CalcTwoPhaseProp 函数中写入密度的计算公式:

这里的温度,压力是通过 ICapeThermoMaterial 接口的 SetOverallProp 函数来设置的:

这样,就简单完成了一个指定温度、压力的密度计算,但是这里的密度公式是需要自己去写相对应的算法的

密度计算肯定缺不了组分,但是这里为了简化,没有设置组分,所以运行起来肯定是不对的

小总结

现在看来,从0开发一个物性包是非常非常困难的,在这个过程中会遇到各种各样的问题,开发环境的问题、标准老旧问题、接口失效问题等等等等,而且并不是说我们开发一个物性包只实现某一个单一的物性计算,而是需要我们将所有的,一个完成的物性计算系统全部补充进去才可以去用其他软件调用来验证这个物性包是否可以使用

庞大、复杂、交织

所以我们只是示例去使用或者开发这个标准提供的一些东西,而并不是直接真正的开发一个物性包,这是非常非常复杂的事情,我一个人的精力根本就不可能完成~