前言
因为距离上一次开发已经过去了好几个月,中间因为重建博客和论坛的原因,同样也是因为在 《单元模块开发入门实例Heater(一)》 篇中最后遗留的bug因素,所以打算重新详细的讲解一下一个单元模块的构建。
第一次开发这个模块的时候都是24年2月份的时候了,是在同年10月份重新开始CAPE-OPEN(以下简称CO)的学习和开发,在11月份解决了无法计算和闪退的bug,发布了第一版BlockTest01,这一版其实已经和刚开始2月份的myBlockTest那一版有了很多的不同,担心很多人再开始看今天的这个Heater模块开发实例(二)的时候看不懂,所以本文开始直接从零详细的一步步讲解如何构建一个CO单元模块。
虽然说本文中代码是重新演示的,其中的原理或者接口用法,可以去回顾博客中的《热力学物性包开发篇》和《单元模块开发入门篇》,哪一个步骤有疑惑也可以去翻看这两篇讲解。
同时,如果有哪一步没跟上,或者不理解,也可以参考上面这三篇中的源码,地址如下:
- GitHub - laugh0608/BlockTest01: 基于CAPE-OPENv1.1开发的一个测试闪蒸模块
- GitHub - laugh0608/myBlockTest: 第一个基于CAPE-OPEN开发失败的单元模块
- GitHub - laugh0608/myThermoTest: 基于CAPE-OPEN开发的,第一个,刚入门的,半吊子的热力学物性包
备注:BlockTest01是最新的,没有bug的版本;myBlockTest是完全根据《单元模块开发入门篇》教程写的,有一点点小bug;myThermoTest是《热力学物性包开发篇》教程中的源码;
如果有需要,建议可以对比一下BlockTest01和myBlockTest就知道闪退和无法计算的那个bug如何解决得了,哦对了,值得一提的是,BlockTest01目前只支持AspenV11(我用这个版本测试的,你也可以试试v12/v14),不支持COFE,应该是使用的CO版本或者获取流股的时候使用了智能指针的关系;
哦对了,还有一件事,本文是根据B站蔡老师(ID:bcbooo)的讲解来写的,所以中间可能会夹杂着一些bug和解决bug的步骤,会显得很繁琐,但是对于新手来说跟着写一遍、改一遍收获还是有的,最起码就知道哪里应该怎么写。
创建项目
环境:VS2022,Win11LTSC
打开VS2022,点击创建新项目,创建ATL项目:
命名为HeaterExample,点击创建:
名称可以自己定义。
这里也可以不选择那个将解决方案和项目放在同一目录中;
默认动态链接库即可,点击确定:
HeaterExampleOperation
为了防止idl文件路径报错问题,先点击一下全部保存:
添加第一个ATL简单对象,右键项目解决方案资源管理器中的项目名称,点击添加,新建项:
选择 ATL,ATL简单对象,命名为 HeaterExampleOperation,点击添加:
ATL简单对象的 属性默认 即可,点击完成:
如果这里报错idl文件路径问题,建议直接删除项目,然后重新建立,创建项目之后先点击全部保存按钮,然后再进行添加,这是一个Visual Studio的bug,如果不想重建项目,可以参考:VS2022社区版新建ATL简单对象报错idl文件路径问题 和 热力学物性包开发入门2 | OrdisBlog 解决。
或者(暂时没想到解决办法)
添加完成后如图所示:
不要做任何编辑,默认即可,点击一下全部保存,点击最上方的工具栏的生成,生成解决方案:
如果有如下报错:
Microsoft.CppCommon.targets(2412,5): error MSB8011: 未能注册输出。请尝试启用“逐用户重定向”,或者使用提升的权限从命令提示符处注册该组件。
点击顶部工具栏项目,HeaterExample 项目属性:
将“ 逐用户重定向 ”选项设置为“ 是 ”,点击应用:
然后点击顶部工具栏,生成,重新生成解决方案,编译成功如下提示:
ICapeUnit
在CAPE-OPEN官网下载混合器的一个代码示例,解压后备用,链接如下:
https://colan.repositoryhosting.com/trac/colan_examples/downloads
在解压之后的源码文件夹 CPPSource 中,找到 CAPE-OPENv1-1-0.tlb
文件,将其复制到 本项目根目录 中备用,
点击一下 全部保存,切换到 类视图:
右键 CHeaterExampleOperation
,点击添加,实现接口:
选择文件类型,选择 刚才复制到项目根目录 下的 CAPE-OPENv1-1-0.tlb
文件,找到 ICapeUnit
接口,点击添加:
这里如果报错了
pch.cpp
文件问题,参考: 热力学物性包开发入门2 | OrdisBlog 解决。或者(暂时没有想到更好的解决办法)
分割线分割线分割线
这里插入一个题外话,如果你下载了我的源码,或者是你自己的项目在另一台电脑上打开,发现编译失败的问题,那么大概率是 pch.h
中的CAPE-OPEN接口预编译文件路径有问题,需要修改为新电脑上的 绝对路径,
分割线分割线分割线
如何判断导入是否成功,切换回解决资源管理器视图,选择对应类的源文件(假设我们刚才对类 CHeaterExampleOperation
进行了添加,那么对应的源文件就是 HeaterExampleOperation.h
),查看是否添加进来了对应的接口以及接口实现方法:
点击一下 全部保存,点击重新生成解决方案,然后会发现报错了,编译不通过,这并不是说导入接口的方式是错误的,看过物性包开发入门篇的,应该都知道,这是CO标准自己的定义问题,所以还是需要从CO标准提供的示例中(上文下载的示例代码),找和现在导入的接口方法有什么不同,就是错误之处,
在解压之后的源码文件夹 CPPSource 中,找到 CPPMixerSplitterUnitOperation.h
这个文件,往下翻,找到 ICapeUnit Methods
部分,对比有什么区别:
第一处:
第二处:
修改为与示例一致即可,但是我们可以看到,实际上这几个实现函数,参数的命名都是不规范的,所以我们略微修改一下,最终这部分的代码如下:
// ICapeUnit Methods
public:
STDMETHOD(get_ports)(LPDISPATCH *ports)
{
return E_NOTIMPL;
}
STDMETHOD(get_ValStatus)(CapeValidationStatus *pValStatus)
{
return E_NOTIMPL;
}
STDMETHOD(Calculate)()
{
return E_NOTIMPL;
}
STDMETHOD(Validate)(BSTR *message, VARIANT_BOOL *pValidateStatus)
{
return E_NOTIMPL;
}
};
组件注册
要让流程模拟软件访问到我们写的模块,就得通过注册表注册生成的dll文件,打开资源文件中的 HeaterExampleOperation.rgs
文件,写入下列代码:
'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 'LaughHeater'
val Description = s 'Written By laugh'
val CapeVersion = s '1.1'
val ComponentVersion = s '24.11.29.1'
val VendorURL = s 'https://imbhj.com/'
val About = s 'Heater Example Test Operation Block'
}
键值是固定的,不需要改,下面的 Description 部分可以自己根据实际情况来定义
插入位置如图所示:
点击全部保存,点击生成->重新生成解决方案,编译完成之后打开Aspen,添加CAPE-OPEN模块就可以看到我们注册好的组件了:
在COFE中:
到这里注册就结束了,下面正式开始代码部分。
PortsArray
单元模块的 port 端口,是一个数组类型,为了描述这个 port,需要新建一个 ATL简单对象 来保存和描述这个端口;
先点击生成,重新生成解决方案,然后等待编译完成,点击全部保存,右键项目名称进行新建:
名称为 PortsArray:
点击添加之后的ATL简单对象的 属性页面默认 即可,直接点击完成;
如果点击完成之后报错,翻看上文中的处理办法。
新建好的 PortsArray
如图所示:
点击全部保存,点击生成-重新生成解决方案,编译无错误,进行下一步。
ICapeCollection
点击 视图 - 类视图,给刚才新建的 PortsArray
添加实现接口:
类视图中的
CPortsArray
前面的C是class的意思,也就是C++中的类。
在 PortArray.h
文件中查看添加完成的接口:
点击全部保存,点击生成-重新生成解决方案,编译无错误,但是为了防止后续出现问题,还是尽量不要让传入的参数和返回的参数名称一致,修改如下:
// ICapeCollection Methods
public:
STDMETHOD(Item)(VARIANT id, LPDISPATCH *pItem)
{
return E_NOTIMPL;
}
STDMETHOD(Count)(long *pCount)
{
return E_NOTIMPL;
}
首先来定义端口数量,一般来说,一个单元模块的端口数量最少为2,也就是一进一出,如下:
PortsArray.h
文件中,ICapeCollection Methods
部分:
STDMETHOD(Count)(long *pCount)
{
// 定义端口数
*pCount = 2;
return S_OK;
}
返回到 HeaterExampleOperation
部分,首先添加对 PortsArray
的引用:
HeaterExampleOperation.h
文件中,头部引用部分:
// HeaterExampleOperation.h: CHeaterExampleOperation 的声明
#pragma once
#include "resource.h" // 主符号
#include "PortsArray.h" // 添加对 PortsArray 的引用
#include "HeaterExample_i.h"
然后创建 PortsArray
实例:
HeaterExampleOperation.h
文件中,ICapeUnit Methods
部分:
STDMETHOD(get_ports)(LPDISPATCH *ports)
{
// 创建端口数组
CComObject<CPortsArray> *pPortArray;
// 实例化创建的端口数组
CComObject<CPortsArray>::CreateInstance(&pPortArray);
// 返回获取的 ports 结果
pPortArray->QueryInterface(IID_IDispatch, (LPVOID*)ports);
return S_OK;
}
点击生成-重新生成解决方案,编译无报错,但是测试发现在Aspen中却无法读取到端口信息,继续往下写。
MaterialPort
端口是有一个专门的接口来实现的,其中包含一些端口的信息,比如名字、类型、描述等。
先点击生成,重新生成解决方案,然后等待编译完成,点击全部保存,右键项目名称进行新建:
命名为 MaterialPort:
点击添加之后的ATL简单对象的属性页面默认即可,直接点击完成;
添加好的如图所示:
ICapeUnitPort
点击 视图 - 类视图,给刚才新建的 MaterialPort
添加实现接口:
返回 MaterialPort.h
文件,查看添加好的接口:
点击全部保存,重新生成解决方案,发现编译出现错误,那么还是和前文的解决方案一致,在解压之后的源码文件夹 CPPSource 中,找到 MaterialPort.h
这个文件,往下翻到 ICapeUnitPort Methods
部分,看一下有什么区别:
修改类型和名称后的代码:
// ICapeUnitPort Methods
public:
STDMETHOD(get_portType)(CapePortType *portType)
{
return E_NOTIMPL;
}
STDMETHOD(get_direction)(CapePortDirection *portDirection)
{
return E_NOTIMPL;
}
STDMETHOD(get_connectedObject)(LPDISPATCH *connectedObject)
{
return E_NOTIMPL;
}
STDMETHOD(Connect)(LPDISPATCH objectToConnect)
{
return E_NOTIMPL;
}
STDMETHOD(Disconnect)()
{
return E_NOTIMPL;
}
全部保存,重新编译,无报错;
返回 PortsArray.h
文件,将端口数修改为 1
(肯定是不对的,后面会改),假设只有一个端口:
PortsArray.h
文件,ICapeCollection Methods
部分:
// ICapeCollection Methods
public:
STDMETHOD(Item)(VARIANT id, LPDISPATCH *pItem)
{
return E_NOTIMPL;
}
STDMETHOD(Count)(long *pCount)
{
// 定义端口数
*pCount = 1;
//*pCount = 2;
return S_OK;
}
PortsArray.h
文件,顶部引入部分:
// PortsArray.h: CPortsArray 的声明
#pragma once
#include "resource.h" // 主符号
#include "MaterialPort.h" // 添加对 MaterialPort 的引用
#include "HeaterExample_i.h"
创建一个端口,在 PortsArray.h
文件下图所示部分:
private:
// 创建一个端口实例
CComObject<CMaterialPort> *port1;
将端口 port1
实例化,在 PortsArray.h
文件中 CPortsArray()
部分:
public:
CPortsArray()
{
// 实例化创建的端口
CComObject<CMaterialPort>::CreateInstance(&port1);
}
实例化之后就是对其进行赋值:
PortsArray.h
文件,ICapeCollection Methods
部分:
// ICapeCollection Methods
public:
STDMETHOD(Item)(VARIANT id, LPDISPATCH *pItem)
{
// 给实例化好的端口进行赋值
port1->QueryInterface(IID_IDispatch, (LPVOID*)pItem);
return S_OK;
}
STDMETHOD(Count)(long *pCount)
{
// 定义端口数
//*pCount = 2;
*pCount = 1;
return S_OK;
}
全部保存,重新编译一下,无报错;发现在Aspen中还是不能获取到端口,继续往下写,
返回到 HeaterExampleOperation.h
文件,ICapeUnit Methods
部分,注释掉下面一行:
然后和端口实例类似,在顶部私有成员进行创建:
HeaterExampleOperation.h
文件,顶部部分:
private:
// 创建端口数组
CComObject<CPortsArray> *pPortArray;
然后来到 MaterialPort.h
文件中,设置端口的一些参数:
MaterialPort.h
文件,ICapeUnitPort Methods
部分:
// ICapeUnitPort Methods
public:
STDMETHOD(get_portType)(CapePortType *portType)
{
// 设置端口类型为流股类型
*portType = CapePortType::CAPE_MATERIAL;
return S_OK;
}
STDMETHOD(get_direction)(CapePortDirection *portDirection)
{
// 设置端口流股方向为进口
*portDirection = CapePortDirection::CAPE_INLET;
return S_OK;
}
STDMETHOD(get_connectedObject)(LPDISPATCH *connectedObject)
{
// 设置端口流股连接状态为未连接
*connectedObject = NULL;
return S_OK;
}
STDMETHOD(Connect)(LPDISPATCH objectToConnect)
{
return E_NOTIMPL;
}
STDMETHOD(Disconnect)()
{
return E_NOTIMPL;
}
为了更好的描述端口和流股的连接状态,也就是 objectToConnect
部分,需要定义一个变量来存放,在 MaterialPort.h
文件,顶部部分:
private:
// 创建一个物流对象连接实例
LPDISPATCH pMaterialObject;
接着完善 MaterialPort.h
文件,ICapeUnitPort Methods
部分:
// ICapeUnitPort Methods
public:
STDMETHOD(get_portType)(CapePortType *portType)
{
// 设置端口类型为流股类型
*portType = CapePortType::CAPE_MATERIAL;
return S_OK;
}
STDMETHOD(get_direction)(CapePortDirection *portDirection)
{
// 设置端口流股方向为进口
*portDirection = CapePortDirection::CAPE_INLET;
return S_OK;
}
STDMETHOD(get_connectedObject)(LPDISPATCH *connectedObject)
{
// 设置端口流股连接状态为未连接
//*connectedObject = NULL;
// 设置端口流股连接状态为连接状态变量中存放的
*connectedObject = pMaterialObject;
return S_OK;
}
STDMETHOD(Connect)(LPDISPATCH objectToConnect)
{
// 连接时的状态,强行连接到手动创建的物流对象
pMaterialObject = objectToConnect;
return S_OK;
}
STDMETHOD(Disconnect)()
{
// 断开时的状态,强行赋值
pMaterialObject = NULL;
return S_OK;
}
全部保存,重新编译之后发现还是不行,那么应该是设置的 LPDISPATCH
类型不对,更改一下 MaterialPort.h
文件,头部部分的定义并给它一个初始值:
private:
// 创建一个物流对象连接实例
//LPDISPATCH pMaterialObject;
IDispatch *pMaterialObject;
public:
CMaterialPort()
{
// 给物流对象链接状态实例赋一个初始值
pMaterialObject = NULL;
}
更改一下 MaterialPort.h
文件,ICapeUnitPort Methods
部分的 objectToConnect
方式:
STDMETHOD(Connect)(LPDISPATCH objectToConnect)
{
// 连接时的状态,强行连接到手动创建的物流对象
//pMaterialObject = objectToConnect;
objectToConnect->QueryInterface(IID_IDispatch, (LPVOID*)&pMaterialObject);
return S_OK;
}
全部保存,重新编译,无报错;打开COFE,测试已经可以连接一个流股:
Aspen还是不可以,下面继续修改和完善。
回到 HeaterExampleOperation.h
文件中,继续完善 ICapeUnit Methods
部分,首先还是从我们之前下载的CPPSource文件夹里,找到 BSTR.h
和 Variant.h
这两个文件,将其复制到项目根目录下,右键点击-添加-现有项:
选择刚才复制进来的两个文件,点击确定:
在 HeaterExampleOperation.h
文件中的头部,添加对 Variant.h
文件的引用:
// HeaterExampleOperation.h: CHeaterExampleOperation 的声明
#pragma once
#include "resource.h" // 主符号
#include "PortsArray.h" // 添加对 PortsArray 的引用
#include "Variant.h" // 添加对 Variant 的引用
#include "HeaterExample_i.h"
在刚引入的 Variant.h
文件中的头部,添加对 std::wstring
的引用:
#pragma once
#include "BSTR.h"
// 添加对 wstring 的引用
#include <string>
using namespace std;
如图:
返回 HeaterExampleOperation.h
文件中,继续完善 ICapeUnit Methods
部分:
// ICapeUnit Methods
public:
STDMETHOD(get_ports)(LPDISPATCH *ports)
{
// 创建端口数组
//CComObject<CPortsArray> *pPortArray;
// 实例化创建的端口数组
CComObject<CPortsArray>::CreateInstance(&pPortArray);
// 返回获取的 ports 结果
pPortArray->QueryInterface(IID_IDispatch, (LPVOID*)ports);
return S_OK;
}
STDMETHOD(get_ValStatus)(CapeValidationStatus *pValStatus)
{
// 默认端口状态可用
*pValStatus = CapeValidationStatus::CAPE_VALID;
return S_OK;
}
STDMETHOD(Calculate)()
{
// 计算部分先空着
return S_OK;
}
STDMETHOD(Validate)(BSTR *message, VARIANT_BOOL *pValidateStatus)
{
// 带有检查功能的状态获取
CBSTR msg(L"NO ERROR");
*message = msg;
// 状态:成功
*pValidateStatus = TRUE;
return S_OK;
}
全部保存,编译一下,没问题;
ICapeUtilities
点击视图-类试图,右键 CHeaterExampleOperation
-添加-实现接口:
返回 HeaterExampleOperation.h
文件中,检查接口是否添加:
按照惯例,修改一下返回的参数名称,并且填充一下相关的内容:
在
HeaterExampleOperation.h
文件中,ICapeUtilities Methods
部分:
// ICapeUtilities Methods
public:
STDMETHOD(get_parameters)(LPDISPATCH *pParameters)
{
// 暂时忽略这个接口,赋值为空(与工况分析、灵敏度分析等有关)
*pParameters = NULL;
return S_OK;
}
STDMETHOD(put_simulationContext)(LPDISPATCH pSimulationContext)
{
// 该接口是当单元模块状态异常(如计算陷入死循环)时,单元模块与模拟软件通信,告诉模拟软件单元模块状态异常,需要强制结束
// 这里暂时不做实现
return S_OK;
}
STDMETHOD(Initialize)()
{
// 端口数组已在前文的构造函数 CHeaterExampleOperation() 中初始化完成,这里直接返回 OK 即可
return S_OK;
}
STDMETHOD(Terminate)()
{
// 单元模块卸载,这里暂时不做实现,返回空结果
return S_OK;
}
STDMETHOD(Edit)()
{
// 双击单元模块的逻辑,显示一个弹窗
MessageBox(NULL, L"Hello World", L"by laugh", MB_OK);
return S_OK;
}
在上一段中,因为实例初始化只需要一次即可,也就是 ICapeUnit Methods中
的 pPortArray
变量,所以之前写的位置不对,需要将初始化挪到前文中的 CHeaterExampleOperation()
部分,如图:
复制并注释上图这一行,粘贴到下图中的位置:
全部保存,编译一下,没有报错;
ParametersArray
和 PortsArray
类似,Parameter
也是一个数组,所以需要一个ATL简单对象来承载它,右键项目-添加-新建项:
选择 ATL简单对象,命名为 ParametersArray
:
属性页面默认即可,直接点确定;
添加好的简单对象如图:
ICapeCollection
和 PortsArray
一样,ParametersArray
简单对象也是只需要一个 ICapeCollection
接口即可,点击视图-类试图切换到类试图,右键 CParametersArray
,添加-实现接口:
添加好的接口如图:
这里的 ParametersArray
数组实际上并不需要真的传值过去,只需要具有这个功能即可,所以实现很简单,当然和之前的类似,记得修改相关的参数名称:
在
ParametersArray.h
文件中,ICapeCollection Methods
部分:
// ICapeCollection Methods
public:
STDMETHOD(Item)(VARIANT id, LPDISPATCH *pItem)
{
// 返回一个空数组,暂不做实现
return S_OK;
}
STDMETHOD(Count)(long *pCount)
{
// 返回一个空数组,暂不做实现
*pCount = NULL;
return S_OK;
}
返回 HeaterExampleOperation.h
文件中,在头部加入对 ParametersArray.h
文件的引用:
在
HeaterExampleOperation.h
文件中,头部部分:
// HeaterExampleOperation.h: CHeaterExampleOperation 的声明
#pragma once
#include "resource.h" // 主符号
#include "PortsArray.h" // 添加对 PortsArray 的引用
#include "Variant.h" // 添加对 Variant 的引用
#include "ParametersArray.h" // 添加对 ParametersArray 的引用
#include "HeaterExample_i.h"
创建 Parameters
数组实例并进行初始化:
在
HeaterExampleOperation.h
文件中,CHeaterExampleOperation
部分:
private:
// 创建端口数组
CComObject<CPortsArray> *pPortArray;
// 创建 Parameter 参数集数组
CComObject<CParametersArray>* pParametersArray;
public:
CHeaterExampleOperation()
{
// 实例化创建的端口数组
CComObject<CPortsArray>::CreateInstance(&pPortArray);
// 实例化创建的 Parameters 参数集数组
CComObject<CParametersArray>::CreateInstance(&pParametersArray);
}
更改上文中的 get_parameters
函数中获取 parameters
的方式:
在
HeaterExampleOperation.h
文件中,ICapeUtilities Methods
部分:
STDMETHOD(get_parameters)(LPDISPATCH *pParameters)
{
// 暂时忽略这个接口,赋值为空(与工况分析、灵敏度分析等有关)
//*pParameters = NULL;
// 返回获取的 parameters 结果
pParametersArray->QueryInterface(IID_IDispatch, (LPVOID*)pParameters);
return S_OK;
}
全部保存,编译一下,没有报错;
但是发现还是有问题,经过断点测试,应该是在 get_port
的过程中出现了问题,对获取端口的方式进行修正,回到 MaterialPort.h
文件中,补充一下端口的参数:
在
MaterialPort.h
文件中,CMaterialPort
部分:
private:
// 创建一个物流对象连接实例
//LPDISPATCH pMaterialObject;
IDispatch *pMaterialObject;
// 传入参数,为端口流股方向
CapePortDirection pDirection;
public:
//CMaterialPort()
CMaterialPort(CapePortDirection pDirection)
{
// 给物流对象链接状态实例赋一个初始值
pMaterialObject = NULL;
// 将端口方向参数传入公有
this->pDirection = pDirection;
}
将下面的 ICapeUnitPort Methods
部分的获取方式也更改一下:
在
MaterialPort.h
文件中,ICapeUnitPort Methods
部分:
STDMETHOD(get_direction)(CapePortDirection *portDirection)
{
// 设置端口流股方向为进口
//*portDirection = CapePortDirection::CAPE_INLET;
// 改为参数传入形式
*portDirection = this->pDirection;
return S_OK;
}
来到 PortsArray.h
文件中,修改端口数和端口实例:
private:
// 创建一个端口实例
CComObject<CMaterialPort> *port1;
// 创建另一个端口实例,单元模块最少两个端口,一进一出
CComObject<CMaterialPort> *port2;
但是到这里又发现不对了,port1/2
是带参数的,明显不能使用 CComObject<CMaterialPort>::CreateInstance()
方式来分别进行实例化,所以说明刚才的设置端口方向的 CMaterialPort(CapePortDirection pDirection)
方式是有问题的;
返回到 MaterialPort.h
文件中,手动创建一个设置端口方向的函数:
在
MaterialPort.h
文件中,CMaterialPort
部分:
private:
// 创建一个物流对象连接实例
//LPDISPATCH pMaterialObject;
IDispatch *pMaterialObject;
// 传入参数,为端口流股方向
CapePortDirection pDirection;
public:
CMaterialPort()
//CMaterialPort(CapePortDirection pDirection)
{
// 给物流对象链接状态实例赋一个初始值
pMaterialObject = NULL;
// 将端口方向参数传入公有
this->pDirection = pDirection;
}
// 设置端口流股方向
void SetDirection(CapePortDirection pDirection) {
// 将端口方向参数传入共有
this->pDirection = pDirection;
}
回到 PortsArray.h
文件中,继续实例化两个端口:
在
PortsArray.h
文件中,CPortsArray
部分:
private:
// 创建一个端口实例
CComObject<CMaterialPort> *port1;
// 创建另一个端口实例,单元模块最少两个端口,一进一出
CComObject<CMaterialPort> *port2;
public:
CPortsArray()
{
// 实例化创建的端口1
CComObject<CMaterialPort>::CreateInstance(&port1);
// 设置端口1方向
port1->SetDirection(CapePortDirection::CAPE_INLET);
// 实例化创建的端口2
CComObject<CMaterialPort>::CreateInstance(&port2);
// 设置端口2方向
port2->SetDirection(CapePortDirection::CAPE_OUTLET);
}
同时修改下方的端口数量:
在
PortsArray.h
文件中,ICapeCollection Methods
部分:
// ICapeCollection Methods
public:
STDMETHOD(Item)(VARIANT id, LPDISPATCH *pItem)
{
// 给实例化好的端口进行赋值
port1->QueryInterface(IID_IDispatch, (LPVOID*)pItem);
return S_OK;
}
STDMETHOD(Count)(long *pCount)
{
// 定义端口数
*pCount = 2;
//*pCount = 1;
return S_OK;
}
这个时候问题就来了,在这里的 STDMETHOD(Item)(VARIANT id, LPDISPATCH *pItem){}
方法里,获取到的 id
是端口号还好说,比如 id=1
那就是 port1
,id=2
那就是 port2
,假如不是端口号呢?获取到的 id
是端口的名字呢?所以说这里还是欠缺了一部分考虑,就需要准确的获取端口的名字和标识符,就需要用到接下来的这个接口。
ICapeIdentification-1
点击视图-类视图,切换到类试图,右键 CMaterialPort
,添加-实现接口:
添加好的接口如图:
还是老规矩,更改一下这个变量名称:
在
MaterialPort.h
文件中,ICapeIdentification Methods
部分:
// ICapeIdentification Methods
public:
STDMETHOD(get_ComponentName)(BSTR *pComponentName)
{
return E_NOTIMPL;
}
STDMETHOD(put_ComponentName)(BSTR pComponentName)
{
return E_NOTIMPL;
}
STDMETHOD(get_ComponentDescription)(BSTR *pComponentDescription)
{
return E_NOTIMPL;
}
STDMETHOD(put_ComponentDescription)(BSTR pComponentDescription)
{
return E_NOTIMPL;
}
然后来对端口的名字和描述做一个实现,首先来引入需要的头文件:
在
MaterialPort.h
文件中,头部部分:
// MaterialPort.h: CMaterialPort 的声明
#pragma once
#include "resource.h" // 主符号
#include <string> // 添加对 wstring 的引用
using namespace std;
#include "Variant.h" // 添加对 CBSTR 的引用
#include "atlbase.h" // 添加对 CA2W 的引用
#include "atlconv.h" // 添加对 CA2W 的引用
#include "HeaterExample_i.h"
然后创建端口名字和描述的变量,已经对应的设置函数:
在
MaterialPort.h
文件中,CMaterialPort
部分:
private:
// 创建一个物流对象连接实例
//LPDISPATCH pMaterialObject;
IDispatch *pMaterialObject;
// 传入参数,为端口流股方向
CapePortDirection pDirection;
// 端口名称
//string pName;
wstring pName;
// 端口描述
//string pDesc;
wstring pDesc;
public:
CMaterialPort()
//CMaterialPort(CapePortDirection pDirection)
{
// 给物流对象链接状态实例赋一个初始值
pMaterialObject = NULL;
// 将端口方向参数传入公有
this->pDirection = pDirection;
}
// 设置端口流股方向
void SetDirection(CapePortDirection pDirection) {
// 将端口方向参数传入共有
this->pDirection = pDirection;
}
// 设置端口名称和描述
//void SetNameAndDesc(string pName, string pDesc) {
void SetNameAndDesc(wstring pName, wstring pDesc) {
this->pName = pName;
this->pDesc = pDesc;
}
对端口名字和描述做一个实现:
在
MaterialPort.h
文件中,ICapeIdentification Methods
部分:
// ICapeIdentification Methods
public:
STDMETHOD(get_ComponentName)(BSTR *pComponentName)
{
// 获取端口的名字
CBSTR n(SysAllocString(CA2W(pName.c_str()))); // string 转 const OLECHAR* 类型
*pComponentName = n;
return S_OK;
}
STDMETHOD(put_ComponentName)(BSTR pComponentName)
{
// 不做实现,返回空结果
return S_OK;
}
STDMETHOD(get_ComponentDescription)(BSTR *pComponentDescription)
{
// 获取端口的描述
CBSTR d(SysAllocString(CA2W(pDesc.c_str()))); // string 转 const OLECHAR* 类型
*pComponentDescription = d;
return S_OK;
}
STDMETHOD(put_ComponentDescription)(BSTR pComponentDescription)
{
// 不做实现,返回空结果
return S_OK;
}
然后在 PortsArray.h
文件中,给端口设置名字和描述:
在
PortsArray.h
文件中,CPortsArray
部分:
private:
// 创建一个端口实例
CComObject<CMaterialPort> *port1;
// 创建另一个端口实例,单元模块最少两个端口,一进一出
CComObject<CMaterialPort> *port2;
public:
CPortsArray()
{
// 实例化创建的端口1
CComObject<CMaterialPort>::CreateInstance(&port1);
// 设置端口1方向
port1->SetDirection(CapePortDirection::CAPE_INLET);
// 设置端1口名字和描述
//port1->SetNameAndDesc("INLET", "PORT1");
port1->SetNameAndDesc(L"INLET", L"PORT1");
// 实例化创建的端口2
CComObject<CMaterialPort>::CreateInstance(&port2);
// 设置端口2方向
port2->SetDirection(CapePortDirection::CAPE_OUTLET);
// 设置端口2名字和描述
//port2->SetNameAndDesc("OUTLET", "PORT2");
port2->SetNameAndDesc(L"OUTLET", L"PORT2");
}
在下面继续完成端口ID的部分:
在
PortsArray.h
文件中,ICapeCollection Methods
部分:
// ICapeCollection Methods
public:
STDMETHOD(Item)(VARIANT id, LPDISPATCH *pItem)
{
// 获取 id
CVariant v(id, TRUE);
wstring error;
// 如果 id 是整数数组
if (v.CheckArray(VT_I4, error))
{
// 给实例化好的端口进行赋值
if (v.GetLongAt(0) == 0) port1->QueryInterface(IID_IDispatch, (LPVOID*)pItem);
else port2->QueryInterface(IID_IDispatch, (LPVOID*)pItem);
}
// 如果 id 是字符串数组
else if (v.CheckArray(VT_BSTR,error))
{
CBSTR name = v.GetStringAt(0);
if (CBSTR::Same(L"INLET", name)) port1->QueryInterface(IID_IDispatch, (LPVOID*)pItem);
else port2->QueryInterface(IID_IDispatch, (LPVOID*)pItem);
}
// 给实例化好的端口进行赋值
//port1->QueryInterface(IID_IDispatch, (LPVOID*)pItem);
return S_OK;
}
STDMETHOD(Count)(long *pCount)
{
// 定义端口数
*pCount = 2;
//*pCount = 1;
return S_OK;
}
编译一下,发现有错误,应该是 MaterialPort.h
文件中获取端口名字的那个方法有问题,修改一下:
在
MaterialPort.h
文件中,ICapeIdentification Methods
部分:
// ICapeIdentification Methods
public:
STDMETHOD(get_ComponentName)(BSTR *pComponentName)
{
// 获取端口的名字
//CBSTR n(SysAllocString(CA2W(pName.c_str()))); // string 转 const OLECHAR* 类型
//*pComponentName = n;
*pComponentName = SysAllocString(pName.c_str());
return S_OK;
}
STDMETHOD(put_ComponentName)(BSTR pComponentName)
{
// 不做实现,返回空结果
return S_OK;
}
STDMETHOD(get_ComponentDescription)(BSTR *pComponentDescription)
{
// 获取端口的描述
//CBSTR d(SysAllocString(CA2W(pDesc.c_str()))); // string 转 const OLECHAR* 类型
//*pComponentDescription = d;
*pComponentDescription = SysAllocString(pDesc.c_str());
return S_OK;
}
STDMETHOD(put_ComponentDescription)(BSTR pComponentDescription)
{
// 不做实现,返回空结果
return S_OK;
}
全部保存,编译一下, 没什么问题,但是测试还是发现无法连接流股,看来哪里还是有问题在,继续完善;