目录
前言
这几天碰到一个需求,需从mfc软件采集数据并通过串口传出,而自己本身是写C#的,所以需将mfc的c++数据通过C#控制台将数据采集并通过modbus rtu通讯写入地址数据,对此做个记录。具体思维逻辑,如下:
一、MFC测试程序编写
1、怎么创建一个MFC程序
目前使用过vs2013(主要)、vs2022创建
1)VS2013创建MFC程序
说明:很直接,下好vs2013后,打开VS2013 -》 点击新建项目 -》找到MFC项
2)VS2022创建MFC程序
说明:VS2022不会默认下载mfc模块,需在Visual Studio Installer中点击修改,在安装详细信息里选中MFC选项。安装完后,创建新项目就能搜索出MFC应用选项。
2、mfc应用程序基本知识
创建MFC应用时,会有三种主要的应用程序类型:多个文档、单个文档和基于对话框进行选择,无论选择哪种方式,都会输出一大堆文件出来,如需加功能代码,需对MFC应用有一定了解。
1)MFC应用程序入口在哪?
MFC应用程序入口有三个明显特点:
- .h文件有头文件<afxwin.h>
- 文件有InitInstance虚函数声明
- .h文件中有类继承CWinApp应用程序类
如下是简单代码说明:
#include <afxwin.h> //mfc头文件
class MyApp :public CWinApp //CWinApp应用程序类
{
public:
//程序入口
virtual BOOL InitInstance();
};
//外部变量声明
extern CMFCApplication2App theApp;
2)MFC应用窗口框架在哪?
应用窗口框架的主要特点:
- .h文件中有类继承CFrameWnd窗口框架类
- 一般名称为MainFrm
注:这个文档只出现在多个文档、单个文档应用程序类型上 ,但在vs2013中都有
3)MFC消息映射机制
3)将新写的方法应用在程序上
1、将方法名加入在MFC应用程序入口的 .h文件 ,能在初始化调用(如下述CSocketNet方法),代码如下:
#include <afxwin.h> //mfc头文件
class MyApp :public CWinApp //CWinApp应用程序类
{
public:
//程序入口
virtual BOOL InitInstance();
};
//外部变量声明
extern CMFCApplication2App theApp;
//CSocketNet方法添加
extern CSocket theNet;
2、在MFC应用程序入口的 .c文件 , 调用CSocket theDate,并在初始化方法中将CSocket的需初始化方法进行写入,代码如下:
// 唯一的一个 CMFCApplication2App 对象
CMFCApplication2App theApp;
CSocketNet theNet;
// CMFCApplication2App 初始化
BOOL CMFCApplication2App::InitInstance()
{
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);
CWinApp::InitInstance();
//需在初始化就实现的方法
theNet.InitWinsock();
theNet.CreateSocket();
theNet.ServerSet();
theNet.SocketConnect();
。。。
}
3、在需要的程序类 .c文件中,加上CSocketNet类的SendMes方法
3、MFC实现与C# 通过Socket通讯的客户端
SocketNet.h 代码如下:
//防止头文件重复包含
#if !defined(AFX_MOTOR_TEST_H__CA795C62_C00C_11D6_8DF6_BD5F2E414260__INCLUDED_)
#define AFX_MOTOR_TEST_H__CA795C62_C00C_11D6_8DF6_BD5F2E414260__INCLUDED_
//使用 #pragma once 优化: #pragma once 是一种更简单且高效的防止头文件重复包含的方法。
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib") // Winsock Library
class CSocketNet
{
public:
WSADATA wsaData;
SOCKET serverSocket;
SOCKET clientSocket;
struct sockaddr_in server;
BOOL InitWinsock(); //初始化Winsock
BOOL CreateSocket(); //创建Socket
BOOL ServerSet(); //设置服务器地址
BOOL SocketConnect(); //Socket连接
BOOL SendMes(CString str); //发送信息
CSocketNet();
~CSocketNet();
private:
};
#endif
SocketNet.cpp 代码如下:
#include "stdafx.h"
#include "SocketNet.h"
#include "MFCApplication2.h"
#include <atlconv.h>
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#define new DEBUG_NEW
#endif
//构造函数
CSocketNet::CSocketNet()
{
}
//析构函数
CSocketNet::~CSocketNet()
{
}
BOOL CSocketNet::InitWinsock()
{
// 初始化Winsock
//如果 WSAStartup()网络初始化函数,返回值 不等于 0,说明初始化失败
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
return FALSE;
}
else
{
return TRUE;
}
}
BOOL CSocketNet::CreateSocket()
{
// 创建Socket
//检查套接字是否创建失败
//如果等于INVALID_SOCKET,说明创建失败,返回FALSE
//否则(创建成功),返回TRUE(表示成功)
if ((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
AfxMessageBox(_T("创建Socket失败!"), MB_OK | MB_ICONINFORMATION);
return FALSE;
}
else
{
return TRUE;
}
}
BOOL CSocketNet::ServerSet()
{
// 设置服务器地址
server.sin_family = AF_INET;
server.sin_port = htons(8888); // 服务器端口
if (inet_pton(AF_INET, "127.0.0.1", &server.sin_addr) <= 0
{
closesocket(clientSocket);
WSACleanup();
AfxMessageBox(_T("设置服务器地址失败!"), MB_OK | MB_ICONINFORMATION);
return FALSE;
}
else
{
return TRUE;
}
}
BOOL CSocketNet::SocketConnect()
{
// 连接到服务器
//如果连接失败(返回值 < 0):
// 1)关闭socket(closesocket(clientSocket))
// 2)清理Winsock资源(WSACleanup())
// 3)返回 FALSE(表示连接失败)
//如果连接成功:直接返回 TRUE(表示连接成功)
if (connect(clientSocket, (struct sockaddr*)&server, sizeof(server)) < 0)
{
closesocket(clientSocket);
WSACleanup();
AfxMessageBox(_T("连接失败!"), MB_OK | MB_ICONINFORMATION);
return FALSE;
}
else
{
return TRUE;
}
}
BOOL CSocketNet::SendMes(CString str)
{
CT2A utf8Msg(str, CP_UTF8); //转换为UTF-8
if (send(clientSocket, utf8Msg, strlen(utf8Msg), 0) < 0)
{
return FALSE;
}
else
{
return TRUE;
}
}
二、C#控制台程序编写
控制台程序编写分为两部分,一部分为接收MFC数据信息,一部分为使用串口通讯实现对相关地址的写入。两者都需要实时运行,所以需使用多线程形式实现。
1.接收MFC数据信息
代码如下(示例):
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1.Service
{
class CodeService
{
private readonly int _port;
private TcpListener listener;
//定义事件
public event EventHandler<string> DataReceived;
public CodeService(int port = 8888)
{
_port = port;
listener = new TcpListener(IPAddress.Any, _port);
listener.Start();
}
public void StartTcpListener()
{
while (true)
{
// 等待客户端连接
TcpClient client = listener.AcceptTcpClient();
Console.WriteLine("Client connected.");
// 处理客户端连接
Task.Run(() => HandleClient(client));
}
}
void HandleClient(TcpClient client)
{
using (client)
{
using (var stream = client.GetStream())
{
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
string receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received from C++:" + receivedData);
// 触发事件
DataReceived?.Invoke(this, receivedData);
}
}
}
}
}
}
2.使用串口通讯实现对相关地址写入
代码如下(示例):
using Modbus.Device;
using System;
using System.IO.Ports;
namespace ConsoleApplication1.Service
{
//modbus串口通讯
class RS485Service
{
//配置文件
public IniFile m_ConfigFile;
//从站地址
byte slaveAddress = 1;
public IModbusMaster master;
public SerialPort serialPort = null;
public RS485Service()
{
}
public void Connect()
{
if (serialPort == null)
{
try
{
//Serial port
string portName = "COM1";
int baudRate = 9600;
Parity parity = Parity.None;
int dataBits = 8;
StopBits stopBits = StopBits.One;
serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
//serialPort.DataReceived += SerialPort_DataReceived;
serialPort.Open();
if (serialPort.IsOpen)
{
master = ModbusSerialMaster.CreateRtu(serialPort);
Console.WriteLine("串口通讯连接成功!");
}
}
catch (Exception ex)
{
Console.WriteLine("串口通讯未连接!" + ex.Message);
}
}
}
#region 地址读值
//读取单个保持寄存器(功能码 0x03)
public ushort ReadRegister(ushort registerAddress)
{
ushort[] registers = master.ReadHoldingRegisters(slaveAddress, registerAddress, 1);
return registers[0];
}
//读取多个保持寄存器(功能码 0x03)
public ushort[] ReadResgisters(ushort startAddress, ushort quantity)
{
return master.ReadHoldingRegisters(slaveAddress, startAddress, quantity);
}
#endregion
#region 地址写值
//写单个保持寄存器(功能码 0x06)
//如果需要写入单个寄存器(例如一个数字量输出),可以使用 WriteSingleRegister 方法
public void WriteRegister(ushort registerAddress, ushort value)
{
master.WriteSingleRegister(slaveAddress, registerAddress, value);
}
//写多个保持寄存器(功能码 0x10)
//如果需要写入多个寄存器,可以使用 WriteMultipleRegisters 方法
public void WriteMultRegisters(ushort startAddress, ushort[] values)
{
master.WriteMultipleRegisters(slaveAddress, startAddress, values);
}
#endregion
}
}