串口类

// CESeries.h: interface for the CCESeries class.
//
//


#if !defined(AFX_CESERIES_H__4040241A_FA58_4655_88BA_8D8DF018446D__INCLUDED_)
#define AFX_CESERIES_H__4040241A_FA58_4655_88BA_8D8DF018446D__INCLUDED_


#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000




#define WM_COMMDATAEVENT WM_USER + 100   // 串口数据到达
#define WM_SENDDATATOCOM WM_USER + 101   // 发送COMM数据,WPARAM -->端口对象 ,LPARAM --> 数据包
#define WM_SENDFAULTLOG   WM_USER + 102   //发送故障数据,WPARAM -->故障记录


#define  WM_LIST                WM_USER + 103//doc->left message


typedef struct tagUSB_Datapack  //USB数据包
{
BYTE *pPtr;          // USB接收到的数据
int nLen; // 数据长度
} USB_DATAPACK, *LPUSB_DATAPACK;


typedef struct tagComm_Datapack
{
BYTE *pPtr;          // 串口接收到的数据
int nLen; // 数据长度
} COMM_DATAPACK, *LPCOMM_DATAPACK;
typedef struct cmd1
{
BYTE cmd;
BYTE end;
}ReadVal;
typedef struct cmd2
{
BYTE cmd_1;
BYTE cmd_2;
BYTE cmd_3;
BYTE cmd_4;
BYTE end;
}ModSet;
typedef struct  read_plc_m
{
public:
read_plc_m();
BYTE STX;
BYTE CMD;
BYTE ADDR_1;
BYTE ADDR_2;
BYTE ADDR_3;
BYTE ADDR_4;
BYTE NUM_1;
BYTE NUM_2;
BYTE ETX;
BYTE SUMCHK_1;
BYTE SUMCHK_2;
}readPlcM;
typedef struct  write_plc_m
{
public:
BYTE STX;
BYTE CMD;
BYTE ADDR_1;
BYTE ADDR_2;
BYTE ADDR_3;
BYTE ADDR_4;
BYTE ETX;
BYTE SUMCHK_1;
BYTE SUMCHK_2;
}writePlcM;
typedef struct myData 
{
public:
myData();
myData(int id,double val,CString time);
int nId;
double fVal;
CString strTime;


}testData,*lpTestData;
//CE串口通讯类
class CCESeries
{


public:
CCESeries();
virtual ~CCESeries();
CTime m_LastPackTime;
public:
BOOL GetCommObjState();
//打开串口
BOOL OpenPort(CWnd* pPortOwner, /*使用串口类,窗体句柄*/
 UINT portNo = 2, /*串口号*/
 UINT baud = 9600, /*波特率*/
 UINT parity = NOPARITY, /*奇偶校验*/
 UINT databits = 8, /*数据位*/
 UINT stopbits = 0         /*停止位*/
 );
//关闭串口
void ClosePort();
//设置串口读取、写入超时
BOOL SetSeriesTimeouts(COMMTIMEOUTS CommTimeOuts);
//向串口写入数据,供外部调用
BOOL WritePort(const BYTE *buf,DWORD bufLen);
CRITICAL_SECTION m_Csection; 
private:
    //串口读线程函数
    static  DWORD WINAPI ReadThreadFunc(LPVOID lparam);


    //协议分析线程函数


//串口写线程函数
    static DWORD WINAPI WriteThreadFunc(LPVOID lparam);




//实际向串口写入数据
static BOOL WritePort(HANDLE hComm,const BYTE *buf,DWORD bufLen);


//关闭读线程
void CloseReadThread();
//关闭写线程
void CloseWriteThread();
private:
CWnd* m_pPortOwner;
    //已打开的串口句柄
HANDLE m_hComm;


CString idstr;
//读写线程句柄
HANDLE m_hReadThread;
HANDLE m_hWriteThread;
HANDLE m_hAnsysThread;

//读写线程ID标识
DWORD m_dwReadThreadID;
DWORD m_dwWriteThreadID;
DWORD m_dwAnsysThreadID;

//读线程退出事件
HANDLE m_hReadCloseEvent;
//写线程退出事件
HANDLE m_hWriteCloseEvent;
HANDLE m_hAnsysCloseEvent;

};


// CESeries.cpp: implementation of the CCESeries class.
//
//


#include "StdAfx.h"
#include "CESeries.h"


#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//定义向写线程发送的消息常量
const int CM_THREADCOMMWRITE = WM_USER+110;
const int CM_THREADCOMMGETNEWCHAR = WM_USER+111;
#define DATAHEAD 0x7E
#define DATATAIL 0x0D




read_plc_m::read_plc_m()
{
STX=0x02;
CMD=0x30;
NUM_1=0x30;
NUM_2=0x31;
ETX=0x03;
}
myData::myData()
{


}
myData::myData(int id,double val,CString time)
{
nId=id;
fVal=val;
strTime=time;
}
//类CCESeries构造函数
CCESeries::CCESeries()
{
idstr.Empty();
m_LastPackTime=CTime(1970,1,1);
m_hComm = INVALID_HANDLE_VALUE;
m_hReadCloseEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
m_hWriteCloseEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
m_hAnsysCloseEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
::InitializeCriticalSection(&m_Csection);
}


//类CCESeries析构函数
CCESeries::~CCESeries()
{
ClosePort();
CloseHandle(m_hReadCloseEvent);
CloseHandle(m_hWriteCloseEvent);
CloseHandle(m_hAnsysCloseEvent);
::DeleteCriticalSection(&m_Csection);
}


/*
*函数介绍:打开串口
*入口参数:pPortOwner :使用此串口类的窗体句柄
  portNo :串口号
  baud :波特率
  parity :奇偶校验
  databits :数据位
  stopbits :停止位
*出口参数:(无)
*返回值:TRUE:成功打开串口;FALSE:打开串口失败
*/
BOOL CCESeries::OpenPort(CWnd* pPortOwner, /*使用串口类,窗体句柄*/
UINT portNo , /*串口号*/
UINT baud , /*波特率*/
UINT parity , /*奇偶校验*/
UINT databits , /*数据位*/
UINT stopbits   /*停止位*/
)
{
DCB commParam;
TCHAR szPort[15];

// 已经打开的话,直接返回
if (m_hComm != INVALID_HANDLE_VALUE)
{
return TRUE;
}
ASSERT(pPortOwner != NULL);

//设置串口名
wsprintf(szPort, _T("COM%d:"), portNo);
//打开串口
m_hComm = CreateFile(
szPort,
GENERIC_READ | GENERIC_WRITE, //允许读和写
0, //独占方式(共享模式)
NULL,
OPEN_EXISTING, //打开而不是创建(创建方式)
FILE_FLAG_OVERLAPPED,
NULL 
);
idstr.Format(_T("ID:Com%d"),portNo);
if (m_hComm == INVALID_HANDLE_VALUE)
{
// 无效句柄,返回。
return FALSE;
}

// 得到打开串口的当前属性参数,修改后再重新设置串口。
// 设置串口的超时特性为立即返回。
if (!GetCommState(m_hComm,&commParam))
{
//关闭串口
CloseHandle (m_hComm);
m_hComm = INVALID_HANDLE_VALUE;
return FALSE;
}
GetCommState(m_hComm,&commParam);
commParam.BaudRate = baud; // 设置波特率 
commParam.fBinary = TRUE; // 设置二进制模式,此处必须设置TRUE
commParam.fParity = FALSE;//TRUE; // 支持奇偶校验 
commParam.ByteSize = databits; // 数据位,范围:4-8 
commParam.Parity = parity; // 校验模式
commParam.StopBits = stopbits;   // 停止位 

commParam.fOutxCtsFlow = FALSE; // No CTS output flow control 
commParam.fOutxDsrFlow = FALSE; // No DSR output flow control 
commParam.fDtrControl = DTR_CONTROL_ENABLE; 
// DTR flow control type 
commParam.fDsrSensitivity = FALSE; // DSR sensitivity 
commParam.fTXContinueOnXoff = FALSE; // XOFF continues Tx 
commParam.fOutX = FALSE; // No XON/XOFF out flow control 
commParam.fInX = FALSE; // No XON/XOFF in flow control 
commParam.fErrorChar = FALSE; // Disable error replacement 
commParam.fNull = FALSE; // Disable null stripping 


commParam.fRtsControl = RTS_CONTROL_ENABLE; //RTS_CONTROL_ENABLE; 
// 485改动
// RTS flow control 
commParam.fAbortOnError = FALSE; // 当串口发生错误,并不终止串口读写

if (!SetCommState(m_hComm, &commParam))
{
//关闭串口
CloseHandle (m_hComm);
m_hComm = INVALID_HANDLE_VALUE;
return FALSE;
}

    //设置串口读写超时时间
COMMTIMEOUTS CommTimeOuts;
//得到超时参数
GetCommTimeouts (m_hComm, &CommTimeOuts);
CommTimeOuts.ReadIntervalTimeout = MAXDWORD;  
CommTimeOuts.ReadTotalTimeoutMultiplier = 0;  
CommTimeOuts.ReadTotalTimeoutConstant = 0;    
CommTimeOuts.WriteTotalTimeoutMultiplier = 0;  
CommTimeOuts.WriteTotalTimeoutConstant = 0;  

if(!SetCommTimeouts( m_hComm, &CommTimeOuts ))
{
//关闭串口
CloseHandle (m_hComm);
m_hComm = INVALID_HANDLE_VALUE;
return FALSE;
}

m_pPortOwner = pPortOwner;

//指定端口监测的事件集, 留到线程中指定
SetCommMask (m_hComm, EV_RXCHAR);

//分配设备缓冲区
SetupComm(m_hComm,512,512);

//初始化缓冲区中的信息
PurgeComm(m_hComm,PURGE_TXCLEAR|PURGE_RXCLEAR);


EscapeCommFunction(m_hComm,CLRDTR);
EscapeCommFunction(m_hComm,CLRRTS);

ResetEvent(m_hAnsysCloseEvent);
ResetEvent(m_hReadCloseEvent);
ResetEvent(m_hWriteCloseEvent);
//创建读串口线程
m_hReadThread = CreateThread(NULL,0,ReadThreadFunc,this,CREATE_SUSPENDED,&m_dwReadThreadID);


//创建写串口线程
m_hWriteThread = CreateThread(NULL,0,WriteThreadFunc,this,CREATE_SUSPENDED,&m_dwWriteThreadID);//本程序不需要写串口,所以关闭写线程定义。


ResumeThread(m_hWriteThread);
ResumeThread(m_hReadThread);


return TRUE;
}


/*
*函数介绍:关闭串口
*入口参数:(无)
*出口参数:(无)
*返回值:  (无)
*/
void CCESeries::ClosePort()
{
//表示串口还没有打开
if (m_hComm == INVALID_HANDLE_VALUE)
{
return ;
}

//关闭读线程
CloseReadThread();
//关闭写线程
CloseWriteThread();

//关闭串口
if (CloseHandle (m_hComm))
{
m_hComm = INVALID_HANDLE_VALUE;
return ;
}
}




/*
*函数介绍:向串口发送数据
*入口参数:buf : 将要往串口写入的数据的缓冲区
  bufLen : 将要往串口写入的数据的缓冲区长度
*出口参数:(无)
*返回值:TRUE:表示成功地将要发送的数据传递到写线程消息队列。
FALSE:表示将要发送的数据传递到写线程消息队列失败。
注视:此处的TRUE,不直接代表数据一定成功写入到串口了。
*/
BOOL CCESeries::WritePort(const BYTE *buf,DWORD bufLen)
{
//将要发送的数据传递到写线程消息队列
if (PostThreadMessage(m_dwWriteThreadID,CM_THREADCOMMWRITE,
WPARAM(bufLen), LPARAM(buf)))
{
return TRUE;
}
return FALSE;
}


/*
*函数介绍:设置串口读取、写入超时
*入口参数:CommTimeOuts : 指向COMMTIMEOUTS结构
*出口参数:(无)
*返回值:TRUE:设置成功;FALSE:设置失败
*/
BOOL CCESeries::SetSeriesTimeouts(COMMTIMEOUTS CommTimeOuts)
{
ASSERT(m_hComm != INVALID_HANDLE_VALUE);
return SetCommTimeouts(m_hComm,&CommTimeOuts);
}




//串口读线程函数
DWORD CCESeries::ReadThreadFunc(LPVOID lparam)
{
CCESeries *ceSeries = (CCESeries*)lparam;

DWORD evtMask;
BYTE * readBuf = NULL;//读取的字节
DWORD actualReadLen=0;//实际读取的字节数
DWORD willReadLen;
DWORD dwCommModemStatus;

DWORD dwReadErrors;
COMSTAT cmState;


OVERLAPPED ovn;
HANDLE hevent;
hevent=CreateEvent(NULL,TRUE,FALSE,NULL);
ZeroMemory(&ovn,sizeof(ovn));
ovn.hEvent=hevent;

// 清空缓冲,并检查串口是否打开。
ASSERT(ceSeries->m_hComm !=INVALID_HANDLE_VALUE); 


//清空串口
PurgeComm(ceSeries->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR );

SetCommMask(ceSeries->m_hComm, EV_RXCHAR);

//如果收到读线程退出信号,则退出线程
while (WaitForSingleObject(ceSeries->m_hReadCloseEvent,1) == WAIT_TIMEOUT)
{  
SetCommMask (ceSeries->m_hComm, EV_RXCHAR);


if (WaitCommEvent(ceSeries->m_hComm,&evtMask,0))
{
ClearCommError(ceSeries->m_hComm,&dwReadErrors,&cmState);
//表示串口收到字符
if (evtMask & EV_RXCHAR) 
{
willReadLen = cmState.cbInQue ;
if (willReadLen <= 0)
{
continue;
}
readBuf=(BYTE*)malloc(willReadLen);
::EnterCriticalSection(&ceSeries->m_Csection);
BOOL bret=ReadFile(ceSeries->m_hComm, readBuf, willReadLen, &actualReadLen,&ovn);
if(!bret)
{
if(GetLastError()==ERROR_IO_PENDING)
{
//
DWORD des;
des=WaitForSingleObject(ovn.hEvent,1000);
if(des==WAIT_OBJECT_0)//
{
BOOL bResult = GetOverlappedResult(ceSeries->m_hComm, &ovn, 
&actualReadLen, FALSE) ; 
// if there was a problem ... 
if (!bResult) 
actualReadLen=0;
}
}
}
::LeaveCriticalSection(&ceSeries->m_Csection);

//如果读取的数据大于0,
if (actualReadLen>0)
{
COMM_DATAPACK *pptr=(COMM_DATAPACK*)malloc(sizeof(COMM_DATAPACK));
pptr->nLen=actualReadLen;
pptr->pPtr=readBuf;
ceSeries->m_pPortOwner->PostMessage(WM_COMMDATAEVENT, 
(WPARAM)ceSeries,(LPARAM)pptr);
}
else
{
free(readBuf);
}
}
}
GetCommModemStatus(ceSeries->m_hComm, &dwCommModemStatus);
}
return 0;
}


//串口写线程函数
DWORD CCESeries::WriteThreadFunc(LPVOID lparam)
{
//AfxMessageBox("写线程创建");
CCESeries *ceSeries = (CCESeries*)lparam;
MSG msg;
DWORD dwWriteLen = 0;
BYTE * buf = NULL;

//如果收到写线程退出信号,则退出线程
while (WaitForSingleObject(ceSeries->m_hWriteCloseEvent,1) == WAIT_TIMEOUT)
{
//如果捕捉到线程消息
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
if (msg.hwnd != 0 )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
continue;
}

if (msg.message == CM_THREADCOMMWRITE)
{
//向串口写
buf = (BYTE*)msg.lParam;
dwWriteLen = msg.wParam;
//向串口写
::EnterCriticalSection(&ceSeries->m_Csection);
WritePort(ceSeries->m_hComm,buf,dwWriteLen);
//删除动态分配的内存
::LeaveCriticalSection(&ceSeries->m_Csection);
free(buf);
Sleep(1);
}
}
}
//AfxMessageBox("写线程退出");
return 0;
}




//私用方法,用于向串口写数据,被写线程调用
BOOL CCESeries::WritePort(HANDLE hComm,const BYTE *buf,DWORD bufLen)
{
DWORD dwNumBytesWritten;
DWORD dwHaveNumWritten =0 ; //已经写入多少

ASSERT(hComm != INVALID_HANDLE_VALUE);
BOOL bResult=FALSE,bExitWhile=FALSE; 
OVERLAPPED ovn;
ZeroMemory(&ovn,sizeof(ovn));
ovn.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);

do
{
BOOL bret = WriteFile (hComm, //串口句柄 
buf+dwHaveNumWritten, //被写数据缓冲区 
bufLen - dwHaveNumWritten,          //被写数据缓冲区大小
&dwNumBytesWritten, //函数执行成功后,返回实际向串口写的个数
&ovn);
if(!bret)
{
if(GetLastError()==ERROR_IO_PENDING)
{
if(!GetOverlappedResult(hComm,&ovn,&dwNumBytesWritten,TRUE))
break;
}else  //这里上下两处跳出,是发送出现了错误的含义
break;
}
dwHaveNumWritten = dwHaveNumWritten + dwNumBytesWritten;
//写入完成
if (dwHaveNumWritten == bufLen)
{
bResult=TRUE;
bExitWhile=TRUE;
}
}while (!bExitWhile);
CloseHandle(ovn.hEvent);
return bResult;
}






//关闭读线程
void CCESeries::CloseReadThread()
{
SetEvent(m_hReadCloseEvent);
//设置所有事件无效无效
SetCommMask(m_hComm, 0);
//清空所有将要读的数据
    PurgeComm( m_hComm,  PURGE_RXCLEAR );
    //等待10秒,如果读线程没有退出,则强制退出
    if (WaitForSingleObject(m_hReadThread,1000) == WAIT_TIMEOUT)
{
TerminateThread(m_hReadThread,10);
DWORD dwExit;

while(1)
{
GetExitCodeThread(m_hReadThread,&dwExit);
if(dwExit!=STILL_ACTIVE) 
break;
Sleep(10);
}
}
m_hReadThread = NULL;
}


//关闭写线程
void CCESeries::CloseWriteThread()
{
SetEvent(m_hWriteCloseEvent);

//清空所有将要写的数据
    PurgeComm( m_hComm,  PURGE_TXCLEAR );

    //等待10秒,如果读线程没有退出,则强制退出
    if (WaitForSingleObject(m_hWriteThread,1000) == WAIT_TIMEOUT)
{
TerminateThread(m_hWriteThread,10);
DWORD dwExit;

while(1)
{
GetExitCodeThread(m_hWriteThread,&dwExit);
if(dwExit!=STILL_ACTIVE) 
break;
Sleep(10);
}
}
m_hWriteThread = NULL;
}






BOOL CCESeries::GetCommObjState()
{
if( m_hComm == INVALID_HANDLE_VALUE)
return FALSE;
else 
return TRUE;
}





#endif // !defined(AFX_CESERIES_H__4040241A_FA58_4655_88BA_8D8DF018446D__INCLUDED_)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CSerialPort First Version by Remon Spekreijse on 2000-02-08 http://www.codeguru.com/cpp/i-n/network/serialcommunications/article.php/c2483/A-communication-class-for-serial-port.htm Second Version by mrlong on 2007-12-25 https://code.google.com/p/mycom/ 增加 ClosePort 增加 WriteToPort 两个方法 增加 SendData 与 RecvData 方法 by liquanhai on 2011-11-04 http://blog.csdn.net/liquanhai/article/details/4955253 增加 ClosePort 中交出控制权,防止死锁问题 by liquanhai on 2011-11-06 http://blog.csdn.net/liquanhai/article/details/6941574 增加 ReceiveChar 中防止线程死锁 by viruscamp on 2013-12-04 https://github.com/viruscamp/CSerialPort 增加 IsOpen 判断是否打开 修正 InitPort 中 parity Odd Even 参数取值错误 修改 InitPortportnr 取值范围,portnr>9 时特殊处理 取消对 MFC 的依赖,使用 HWND 替代 CWnd,使用 win32 thread 函数而不是 MFC 的 增加用户消息编号自定义,方法来自 CnComm by itas109 on 2014-01-10 http://blog.csdn.net/itas109/article/details/18358297 解决COM10以上端口无法显示的问题 扩展可选择端口,最大值MaxSerialPortNum可以自定义 添加QueryKey()和Hkey2ComboBox两个方法,用于自动查询当前有效的串口号。 by liquanhai on 2014-12-18 增加一些处理措施,主要是对减少CPU占用率 by itas109 on 2016-05-07 http://blog.csdn.net/itas109 修复每次打开串口发送一次,当串口无应答时,需要关闭再打开或者接收完数据才能发送的问题。 解决办法:在m_hEventArray中调整m_hWriteEvent的优先级高于读的优先级。CommThread(LPVOID pParam)函数中读写的位置也调换。 参考:http://zhidao.baidu.com/link?url=RSrbPcfTZRULFFd2ziHZPBwnoXv1iCSu_Nmycb_yEw1mklT8gkoNZAkWpl3UDhk8L35DtRPo5VV5kEGpOx-Gea 修复停止位在头文件中定义成1导致SetCommState报错的问题,应为1对应的停止位是1.5。UINT stopsbits = ONESTOPBIT switch(stopbits)和switch(parity)增加默认情况,增强程序健壮性 by itas109 on 2016-06-22 http://blog.csdn.net/itas109 增加ReceiveStr方法,用于接收字符串(接收缓冲区有多少字符就接收多少字符)。 解决ReceiveChar只能接收单个字符的问题。 by itas109 on 2016-06-29 http://blog.csdn.net/itas109 解决RestartMonitoring方法和StopMonitoring方法命令不准确引起的歧义,根据实际作用。 将RestartMonitoring更改为ResumeMonitoring,将StopMonitoring更改为SuspendMonitoring。 增加IsThreadSuspend方法,用于判断线程是否挂起。 改进ClosePort方法,增加线程挂起判断,解决由于线程挂起导致串口关闭死锁的问题。 增加IsReceiveString宏定义,用于接收时采用单字节接收还是多字节接收 by itas109 on 2016-08-02 http://blog.csdn.net/itas109 https://github.com/itas109 改进IsOpen方法,m_hComm增加INVALID_HANDLE_VALUE的情况,因为CreateFile方法失败返回的是INVALID_HANDLE_VALUE,不是NULL 改进ClosePort方法:增加串口句柄无效的判断(防止关闭死锁);m_hWriteEvent不使用CloseHandle关闭 改进CommThread、ReceiveChar、ReceiveStr和WriteChar方法中异常处理的判断,增加三种判断:串口打开失败(error code:ERROR_INVALID_HANDLE)、连接过程中非法断开(error code:ERROR_BAD_COMMAND)和拒绝访问(error code:ERROR_ACCESS_DENIED) 采用安全函数sprintf_s和strcpy_s函数替换掉sprintf和strcpy 改进QueryKey方法,用于查询注册表的可用串口值,可以搜索到任意的可用串口 改进InitPort方法,串口打开失败,增加提示信息:串口不存在(error code:ERROR_FILE_NOT_FOUND)和串口拒绝访问(error code:ERROR_ACCESS_DENIED) 加入viruscamp 取消对 MFC 的依赖 改进InitPort方法,如果上次串口是打开,再次调用InitPort方法,关闭串口需要做一定的延时,否则有几率导致ERROR_ACCESS_DENIED拒绝访问,也就是串口占用问题 初始化默认波特率修改为9600 修复一些释放的BUG 规范了一些错误信息,参考winerror.h -- error code definitions for the Win32 API functions 删除SendData和RecvData方法 by itas109 on 2016-08-10 http://blog.csdn.net/itas109 https://github.com/itas109 改进ReceiveStr方法,comstat.cbInQue = 0xcccccccc的情况(如串口异常断开),会导致RXBuff初始化失败 by itas109 on 2017-02-14 http://blog.csdn.net/itas109 https://github.com/itas109 兼容ASCII和UNICODE编码 ReceiveStr函数中发送函数SendMessage的第二个参数采用结构体形式,包括portNr串口号和bytesRead读取的字节数,可以处理16进制的时候0x00截断问题 精简不必要的函数SendData和RecvData 尽量的取消对 MFC 的依赖,Hkey2ComboBox函数暂时保留 其他小问题修改 博客:blog.csdn.net/itas109 Email:itas109@qq.com

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值