前面有一篇文章讲过C++和三菱plc的通讯:https://blog.csdn.net/V_Gogol/article/details/103389983,本文是其拓展。
背景
前面讲过在工程中添加Activex控件,并使用控件创建变量,此对象包含了PLC的多个操作函数。但是在实际项目中遇到了一个问题,就是只能在主对话框放入activex控件,相当于这个对象的作用域仅限于主对话框对应的头文件和源文件,如果要在其他类中对PLC进行操作(例如设置或读取指定端口的值)就会显得比较麻烦(在其他类中创建主对话框类的对象并调用plc操作函数,执行时程序会报错)。本文讲的是编写一个专门的PLC操作类包含回调函数来实现其他类里也可以方便的对plc进行操作。
思路
想要将plc操作做成其他类可用,我的做法是采用回调函数。
为了代码的可重用性,我专门封装了一个关于plc操作的类,后续跟plc操作相关的直接调用里面的函数即可。
为什么用回调呢?
因为activex控件放在了主对话框中,导致plc操作相关的函数只能在主对话框对应的源文件可用(注意:不只是在主对话框类中,在主对话框对应整个源文件都可用,笔者亲测),用回调的优点在于,我可以把plc的操作函数放到一个全局可用的类中,任何其他类包含这个头文件创建对象后都可以使用,而把plc操作的具体实现代码放到主对话框类的源文件中。任何其他类调用plc的操作时,都会自动调用主对话框类中相应的操作代码,这样就避免前面说的那种问题了。
实现
前面说了,我专门封装了一个plc操作的类,下面代码是这个类的头文件中的内容
#pragma once
const int PLC_NO_ERROR = 0;
const int PLC_ERROR = 1;
class CFXPlc
{
protected:
//回调函数指针类型及实例创建
//轴数据传递给PLC回调--参数只含一个整型
typedef BOOL(CALLBACK*IADTPLC)(int);
//设置端口值回调
typedef BOOL(CALLBACK*SPLCV)(CString, long );
//读取端口值回调
typedef BOOL(CALLBACK*RPLCV)(CString,long*);
///轴动作时将标志或数据发送到plc
IADTPLC m_pADaToPlc; //数据传入plc
//设置plc指定端口值
SPLCV m_pSPlcV;
//读取plc指定端口值
RPLCV m_pRPlcV;
//回调中间函数
//回调中间函数--轴相关数据传到PLC
BOOL Mid_DataToPlc(int axisno);
//回调中间函数--设置指定端口值
BOOL Mid_SetPortValue(CString PortName, long PortValue);
//回调中间函数--读取指定端口值
BOOL Mid_ReadPortValue(CString PortName, long* PortValue);
public: ///回调函数
//回调--设置指定端口值
BOOL C_SetPortValue(BOOL(CALLBACK*lpSetPortValue)(CString PortName, long PortValue));
//回调--读取指定端口值
BOOL C_ReadPortValue(BOOL(CALLBACK*lpReadPortValue)(CString PortName, long* PortValue));
//回调--轴动作时将标志或数据发送到plc
BOOL C_AxisMovDataToPlc(BOOL(CALLBACK*lpAxsDataToPlc)(int));
public:
//设置指定端口值
int SetPortValue(CString PortName, long PortValue);
//读取指定端口值
int ReadPortValue(CString PortName, long* PortValue);
public:
CFXPlc();
~CFXPlc();
};
下面是plc操作类源文件中的函数实现:
#include "stdafx.h"
#include "FXPlc.h"
回调--设置指定端口值
BOOL CFXPlc::C_SetPortValue(BOOL(CALLBACK*lpSetPortValue)(CString PortName, long PortValue))
{
m_pSPlcV = lpSetPortValue;
return PLC_NO_ERROR;
}
//回调中间函数--设置指定端口值
BOOL CFXPlc::Mid_SetPortValue(CString PortName, long PortValue)
{
if (NULL == m_pSPlcV)
{
AfxMessageBox(L"fx-setportvalue未编写回调函数!");
return PLC_ERROR;
}
else
return(*m_pSPlcV)(PortName, PortValue);
}
//设置指定端口值
int CFXPlc::SetPortValue(CString PortName, long PortValue)
{
if (PLC_ERROR == Mid_SetPortValue(PortName, PortValue))
{
AfxMessageBox(PortName + L"值设置失败!");
return PLC_ERROR;
}
else
return PLC_NO_ERROR;
}
//回调--读取指定端口值
BOOL CFXPlc::C_ReadPortValue(BOOL(CALLBACK*lpReadPortValue)(CString PortName, long* PortValue))
{
m_pRPlcV = lpReadPortValue;
return PLC_NO_ERROR;
}
//回调中间函数--读取指定端口值
BOOL CFXPlc::Mid_ReadPortValue(CString PortName, long* PortValue)
{
if (NULL == m_pRPlcV)
{
AfxMessageBox(L"fx-readportvalue未编写回调!");
return PLC_ERROR;
}
else
return(*m_pRPlcV)(PortName, PortValue);
}
//读取指定端口值
int CFXPlc::ReadPortValue(CString PortName, long* PortValue)
{
if (PLC_ERROR == Mid_ReadPortValue(PortName, PortValue))
{
AfxMessageBox(PortName + L"值读取失败!");
return PLC_ERROR;
}
else
return PLC_NO_ERROR;
}
上面是plc操作类中读取和设置plc指定端口值得函数实现,下面还有最后一步,就是在主对话框的源文件中进行回调注册和回调函数的代码实现。
注意,下面的代码是在主对话框的源文件中,而不是在plc操作类中
首先,在主对话框类的初始化函数(OnInitDialog函数)中进行回调函数的注册
///注册PLC轴动作时数据传送回调函数
G::fxplc.C_SetPortValue(CB_SetPortValue);
G::fxplc.C_ReadPortValue(CB_ReadPortValue);
前面的G::fxplc是我在主对话框类中创建的plc操作类的对象,下面再是括号内两个回调函数的代码实现:
//回调函数--设置指定端口值
BOOL CALLBACK CB_SetPortValue(CString PortName, long Value)
{
try
{
if (PLC_NO_ERROR != SetPlcPortValue(0, PortName, Value))
return PLC_ERROR;
else
return PLC_NO_ERROR;
}
catch (...)
{
AfxMessageBox(L"设置"+PortName+L"出错!");
return PLC_ERROR;
}
}
//回调函数--读取指定端口值
BOOL CALLBACK CB_ReadPortValue(CString PortName, long* Value)
{
try
{
if (PLC_NO_ERROR != GetPlcPortValue(0, PortName, Value))
return PLC_ERROR;
else
return PLC_NO_ERROR;
}
catch (...)
{
AfxMessageBox(L"读取" + PortName + L"出错!");
return PLC_ERROR;
}
}
里面的SetPlcPortValue函数和GetPlcPortValue函数是具体的plc实现函数,详细可见我的另一篇博文:https://blog.csdn.net/V_Gogol/article/details/103389983
本文中我的操作函数没有定义在主对话框内部,我的回调函数也是对源文件全局的,如果你的操作函数是定义在类中的话,把上面两个回调也作出相应的更改就可以了。