/*! /mainpage CnComm v1.50 多线程串口通讯库
* /section About 关于
*
* /n 版本: CnComm v1.50
* /n 用途: WINDOWS/WINCE 多线程串口通讯库
* /n 语言: C++ (ANSI/UNICODE)
* /n 平台: WINDOWS(WIN98/NT/2000/XP/2003/Vista); WINCE 5.0 模拟器; Pocket PC 2003 模拟器;
* /n 硬件: PC串口; 串口服务器; USB串口; 虚拟串口;
* /n 编译: BC++ 5(free tool); C++ BUILDER 4, 5, 6, X; EVC 4(sp4); G++ 3, 4; Intel C++ 7, 8, 9; VC++ 6(sp6), .NET, 2003, 2005;
* /n 作者: llbird
* /n 邮箱: wushaojian@21cn.com
* /n 博客: http://blog.csdn.net/wujian53 http://www.cppblog.com/llbird
* /n 维护: 2002.10 - 2009.4
*
* /section Announce 说明
* /n 1) 可以自由使用及传播, 请保留相关声明;
* /n 2) 不推荐直接在本代码上修改, 应通过C++继承扩展机制扩展本代码;
* /n 3) 如果您直接修改本代码, 请发一份给我,便于同网友分享您有益的改动;
* /n 4) 不兼容cnComm1.4以下版本, 有很大改动,同时也更名CnComm;
* /n 5) 还是那句老话, 水平有限, 错误在所难免, 欢迎来信指正, 收入有限, 时间有限, 不提供除CnComm内部问题外的咨询;
*
* /section Log 日志
* /n 考虑到将来的工作中可能不会再和串口打交道,对CnComm做了一次较大的改动,预计再会出一个修正版,不会再出新的版本了。
* /n 2009 v1.5 增加内置分块链表缓冲区; 增加对WINCE的支持(模拟器下测试通过);
* /n 2008 v1.4 增加对同步IO的多线程支持; 增加C++异常的支持; 改名CnComm; Cn == C Next;
* /n 2007 v1.3 细节部分修订;
* /n 2006 v1.2 细节部分修订;
* /n 2005 v1.1 细节部分修订;
* /n 2004 v1.0 采用VC命名风格(匈牙利), 在多个WINDOW平台、编译器测试通过, 首次公开发布cnComm;
* /n 2002 v0.1 因工作需要开发串口通讯基础类, 传统C++的继承机制, 传统C命名风格;
*/#ifndef _CN_COMM_H_
#define _CN_COMM_H_#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <tchar.h>#if defined(UNDER_CE) && !defined(CN_COMM_FOR_CE)
#define CN_COMM_FOR_CE UNDER_CE //!< 配置WINCE的支持
#endif#ifndef CN_COMM_FOR_CE
#include <mcx.h>
#include <process.h> // WINCE没有process.h
#endif#ifndef ON_COM_MSG_BASE
#define ON_COM_MSG_BASE WM_USER + 618 //!< 消息编号的基点
#endif// 送到窗口的消息 WPARAM COM端口号
#define ON_COM_RECEIVE ON_COM_MSG_BASE + 0 //!< EV_RXCHAR
#define ON_COM_RXCHAR ON_COM_MSG_BASE + 0 //!< EV_RXCHAR
#define ON_COM_CTS ON_COM_MSG_BASE + 1 //!< EV_CTS LPARAM==1 CTS ON
#define ON_COM_DSR ON_COM_MSG_BASE + 2 //!< EV_DSR LPARAM==1 DSR ON
#define ON_COM_RING ON_COM_MSG_BASE + 3 //!< EV_RING LPARAM==1 RING ON
#define ON_COM_RLSD ON_COM_MSG_BASE + 4 //!< EV_RLSD LPARAM==1 RLSD ON
#define ON_COM_BREAK ON_COM_MSG_BASE + 5 //!< EV_BREAK
#define ON_COM_TXEMPTY ON_COM_MSG_BASE + 6 //!< EV_TXEMPTY
#define ON_COM_ERROR ON_COM_MSG_BASE + 7 //!< EV_ERR LPARAM Error ID
#define ON_COM_RXFLAG ON_COM_MSG_BASE + 8 //!< EV_RXFLAG
#define ON_COM_POWER ON_COM_MSG_BASE + 9 //!< EV_POWER
#define ON_COM_EVENT1 ON_COM_MSG_BASE + 10//!< EV_EVENT1
#define ON_COM_EVENT2 ON_COM_MSG_BASE + 11//!< EV_EVENT2
#define ON_COM_RX80FULL ON_COM_MSG_BASE + 12//!< EV_RX80FULL
#define ON_COM_PERR ON_COM_MSG_BASE + 13//!< EV_PERR#ifndef CN_COMM_WAIT_EVENT
#ifdef CN_COMM_FOR_CE
#define CN_COMM_WAIT_EVENT EV_RXCHAR | EV_ERR | EV_CTS | EV_DSR | EV_BREAK | EV_TXEMPTY | EV_RING | EV_RLSD | EV_POWER //!< WINCE 默认的等待事件| EV_RXFLAG
#else
#define CN_COMM_WAIT_EVENT EV_RXCHAR | EV_ERR | EV_CTS | EV_DSR | EV_BREAK | EV_TXEMPTY | EV_RING | EV_RLSD //!< WIN32 默认的等待事件| EV_RXFLAG
#endif
#endif#ifndef CN_COMM_BUFFER_MIN_BLOCK_SIZE
#define CN_COMM_BUFFER_MIN_BLOCK_SIZE 1024 //!< 定义缓冲区块的最小值
#endif#if CN_COMM_BUFFER_MIN_BLOCK_SIZE < 4
#error CN_COMM_BUFFER_MIN_BLOCK_SIZE must >= 4 //!< 缓冲区块的最小值不允许小于4
#endif#ifndef CN_ASSERT
#define CN_2STR(L) _T(#L) //!< 将表达式L转换成字符串
#define CN_LINE(L) CN_2STR(L) //!< 将行号L转换成字符串
/*! 内部断言 启用异常将抛出异常 否则调试版将退出 发行版未启用异常将不做任何处理 */
#define CN_ASSERT(E) ((E) ? true : CnComm::Assert(_T("CN_ASSERT(")_T(#E)_T(") failed; CnComm(")CN_LINE(__LINE__)_T("); ")))
#endif//CN_COMM_STD_EXCEPTION CN_ASSERT 将抛出标准C++异常
#ifdef CN_COMM_STD_EXCEPTION
#include <stdexcept> //throw runtime_error(msg)
#endif//CN_COMM_VCL_EXCEPTION CN_ASSERT 将抛出VCL异常(C++ Builder)
#if defined(CN_COMM_VCL_EXCEPTION) && defined(__BORLANDC__)
#include <vcl.h> //throw new Exception(msg)
#endif//CN_COMM_MFC_EXCEPTION CN_ASSERT 将抛出MFC异常(VC++)
#ifdef CN_COMM_MFC_EXCEPTION
#include <Afx.h> //throw new MfcException(msg)
#endif/*! /class CnComm
/version 1.5
/date 2002.10-2009.4
/author llbird(wushaojian@21cn.com http://www.cppblog.com/llbird http://blog.csdn.net/wujian53)
/brief WIN32/WINCE C++ (ANSI/UNICODE) 多线程串口通讯基础库
/example doc_0.cpp 例子0 /example doc_1.cpp /example doc_2.cpp /example doc_3.cpp /example SerialDlg.cpp
*/
class CnComm
{
public:
//! 临界区
struct InnerLock;
//! 缓冲区类
class BlockBuffer;
//! MFC异常
class MfcException;
//! 用于配置模式的枚举值, 32位掩码
enum OptionEnum
{
EN_THREAD = 0x00000001, //!< 启用监视线程 伴随串口打开启动 WatchThread
EN_OVERLAPPED = 0x00000002, //!< 启用异步重叠IO方式
EN_RX_BUFFER = 0x00000004, //!< 启用读缓冲
EN_TX_BUFFER = 0x00000008, //!< 启用写缓冲
EN_RX_THREAD = 0x00000010, //!< 启动读线程 暂时未用 ReadThread
EN_TX_THREAD = 0x00000020, //!< 启动写线程 用于WINCE的双工操作 应同时启用写缓冲 伴随串口打开启动 WriteThread
EN_SUSPEND = 0x00000040, //!< 启动线程时暂停
EN_ABOVE_NORMAL = 0x00000080, //!< 启动线程优先级高一个级别
EN_FLUSH = 0x00000100, //!< 当关闭串口时输出队列未发送完的数据(端口缓冲区) 并阻塞等待
EN_FLUSH_ALL = 0x00000200 //!< 同上(包括写缓冲及端口队列) 您如果重载了写模块而又没有写好 可能导致线程挂起无法正常关闭
};
//! 构造函数 配置具体应用模式 /param[in] dwOption 根据需要由OptionEnum组合而成
#ifdef CN_COMM_FOR_CE
//! WINCE:默认打开串口时启动监视线程 启用写独立线程 启用写缓冲
CnComm(DWORD dwOption = EN_THREAD )
#else
//! WIN32:默认打开串口时启动监视线程 异步重叠方式
CnComm(DWORD dwOption = EN_THREAD | EN_OVERLAPPED)
#endif
{
Init();
SetOption(dwOption);
}
//! 另一模式构造 兼容cnComm1~1.3 /param[in] bThread 启动监视线程 /param[in] bOverlapped 启用重叠I/O
CnComm(bool bThread, bool bOverlapped)
{
DWORD dwOption = 0;if (bThread)
dwOption |= EN_THREAD;if (bOverlapped)
dwOption |= EN_OVERLAPPED;Init();
SetOption(dwOption);
}
//! 析构 自动关闭串口
virtual ~CnComm()
{
Close();
Destroy();
}
//! 判断串口是或打开
bool IsOpen()
{
return hComm_ != INVALID_HANDLE_VALUE;
}
//! 判断串口是或打开
operator bool ()
{
return hComm_ != INVALID_HANDLE_VALUE;
}
//! 获得串口句炳
HANDLE GetHandle()
{
return hComm_;
}
//! 获得串口句炳
operator HANDLE()
{
return hComm_;
}
//! 获得串口序号
DWORD GetPort()
{
return dwPort_;
}
//! 获得串口全名
LPCTSTR GetPortName()
{
return szName_;
}
//! 获得CnComm的基本配置参数 返回32位配置掩码
DWORD GetOption()
{
return dwOption_;
}
//! 设置CnComm的基本配置参数 在打开串口前设置有意义 /param[in] dwOption 32位配置掩码
void SetOption(DWORD dwOption)
{
CN_ASSERT(!IsOpen());//! 打开状态下不可以设置参数dwOption_ = dwOption;
#ifdef CN_COMM_FOR_CE
CN_ASSERT(!IsOverlappedMode()); //! WINCE不允许使用重叠IO 即EN_OVERLAPPED掩码
dwOption_ &= (~EN_OVERLAPPED);
#endif
}
//! 修改CnComm的基本配置参数 在打开串口前设置有意义 /param[in] dwRemove 删除的32位配置掩码 /param[in] dwAdd 添加的32位配置掩码
void ModifyOption(DWORD dwRemove, DWORD dwAdd)
{
CN_ASSERT(!IsOpen());//! 打开状态下不可以设置参数dwOption_ &= ~dwRemove;
dwOption_ |= dwAdd;#ifdef CN_COMM_FOR_CE
CN_ASSERT(!IsOverlappedMode()); //! WINCE不允许使用重叠IO 即EN_OVERLAPPED掩码
dwOption_ &= (~EN_OVERLAPPED);
#endif
}
//! 是否重叠IO模式
bool IsOverlappedMode()
{
return dwOption_ & EN_OVERLAPPED ? true : false;
}
//! 是否输出缓冲区模式
bool IsTxBufferMode()
{
return dwOption_ & EN_TX_BUFFER ? true : false;
}
//! 是否输入缓冲区模式
bool IsRxBufferMode()
{
return dwOption_ & EN_RX_BUFFER ? true : false;
}
//! 关联消息的窗口句柄
void SetWnd(HWND hWnd)
{
CN_ASSERT(::IsWindow(hWnd));
hNotifyWnd_ = hWnd;
}
//! 关联消息的窗口句柄
HWND GetWnd()
{
return hNotifyWnd_;
}
//! 关联消息的窗口句柄
void SetNotifyThreadId(DWORD dwId)
{
hNotifyThreadId_ = dwId;
}
//! 关联消息的窗口句柄
DWORD GetNotifyThreadId()
{
return hNotifyThreadId_;
}
//! 设定发送通知, 接受字符最小值
void SetNotifyNum(DWORD dwNum)
{
dwNotifyNum_ = dwNum;
}
//! 获得线程句柄 /param[in] iOption EN_THREAD获得hWatchThread_ EN_RX_THREAD获得hReadThread_ EN_TX_THREAD获得hWriteThread_ /return HANDLE 线程句柄
HANDLE GetThread(int iOption = EN_THREAD)
{
return iOption == EN_THREAD ? hWatchThread_ : ((iOption == EN_RX_THREAD) ? hReadThread_ : hWriteThread_);
}
//! 设置要监视的事件, 打开前设置有效
void SetWaitEvent(DWORD dwEvent = CN_COMM_WAIT_EVENT)
{
CN_ASSERT(!IsOpen());///打开状态下不可以设置参数dwWaitEvent_ = dwEvent;
}
//! 输入缓冲区
BlockBuffer& Input()
{
return I_;
}
//! 输出缓冲区
BlockBuffer& Output()
{
return O_;
}
//! 获得输入输出的计数 /param[in] bInput 为true获得输入计数,默认为true 重叠IO下输出是不精确的 因为输出由系统后台控制 假设完全输出
DWORD GetCounter(bool bInput = true)
{
return bInput ? dwInCount_ : dwOutCount_;
}
//! 输入输出计数器清零
void ResetCounter()
{
dwInCount_ = dwOutCount_ = 0;
}
//! 打开串口 请注意与cnComm1~1.3的区别 cnComm1~1.3将使用9600, n, 8, 1配置端口 而1.5将只打开端口不配置波特率等参数 /param[in] dwPort 串口序号 1~1024
bool Open(DWORD dwPort)
{
if (!CN_ASSERT(dwPort>=1 && dwPort<=1024))
return false;
BindPort(dwPort);
if(!CN_ASSERT(OpenPort()))
return false;
if(!CN_ASSERT(SetupPort()))
return Close(), false;if ((dwOption_ & (EN_THREAD|EN_RX_THREAD|EN_TX_THREAD)) && !CN_ASSERT(BeginThread()))
return Close(), false;return true;
}
/*! /param[in] dwPort 串口序号 1~1024 /param[in] dwBaudRate 波特率 /param[in] btParity 奇偶校验 /param[in] btByteSize 数据位数 /param[in] btStopBits 停止位数 */
//! 打开串口 缺省 9600, n, 8, 1 /sa bool Open(DWORD dwPort, LPCTSTR szSetStr)
bool Open(DWORD dwPort, DWORD dwBaudRate, BYTE btParity = NOPARITY, BYTE btByteSize = 8, BYTE btStopBits = ONESTOPBIT)
{
if (!CN_ASSERT(dwPort>=1 && dwPort<=1024))
return false;
BindPort(dwPort);
if (!CN_ASSERT(OpenPort()))
return false;
if (!CN_ASSERT(SetState(dwBaudRate, btParity, btByteSize, btStopBits)))
return Close(), false;if (!CN_ASSERT(SetupPort()))
return Close(), false;if ((dwOption_ & (EN_THREAD|EN_RX_THREAD|EN_TX_THREAD)) && !CN_ASSERT(BeginThread()))
return Close(), false;return true;
}
#ifndef CN_COMM_FOR_CE
/*! /param[in] dwPort 串口序号 1~1024 /param[in] szSetStr 字符串参数 "BBBB,P,D,S" 例: "9600,N,8,1" "1200,O,7,2"
BBBB为波特率 P为奇偶校验(E | M | N | O | S) D为数据位数(4 ~ 8) S为停止位数(1 | 1.5 | 2)
/code Open(1, "9600,E,8,2"); /endcode /b 请注意字符串参数的顺序 并检查返回值
不支持WINCE, 原本我自己写了一个替代函数, 考虑到WINCE硬件的复杂性, 移植性可能不好, 故从这个版本中删除了 */
//! 打开串口, 字符串设置串口
bool Open(DWORD dwPort, LPCTSTR szSetStr)
{
if (!CN_ASSERT(dwPort>=1 && dwPort<=1024))
return false;
BindPort(dwPort);
if (!CN_ASSERT(OpenPort()))
return false;
if (!CN_ASSERT(SetState(szSetStr)))
return Close(), false;if (!CN_ASSERT(SetupPort()))
return Close(), false;if ((dwOption_ & (EN_THREAD|EN_RX_THREAD|EN_TX_THREAD)) && !CN_ASSERT(BeginThread()))
return Close(), false;
return true;
}
#endif
/*! /param[in] dwPort 此时该参数没有具体意义,仅用于消息通知中辨别端口号,应注意不和其他端口号重复
/param[in] szPortName 为指定的端口名称, 就像WINDOWS下////.//COM1或者WINCE下COM1: , 有些虚拟串口可以有特殊的名称
/param[in] dwBaudRate 波特率 /param[in] btParity 奇偶校验 /param[in] btByteSize 数据位数 /param[in] btStopBits 停止位数
/code Open(9999, "COM3:", 2400); /endcode /code Open(1028, "COM3:", 9600, N, 7, ONESTOPBIT); /endcode */
//! 指定的端口名称打开串口
bool Open(DWORD dwPort, LPCTSTR szPortName, DWORD dwBaudRate, BYTE btParity = NOPARITY, BYTE btByteSize = 8, BYTE btStopBits = ONESTOPBIT)
{
if(!CN_ASSERT(_tcslen(szPortName) < 64 - 1))
return false;
_tcscpy(szName_, szPortName);
dwPort_ = dwPort;//用于通知消息
if (!CN_ASSERT(OpenPort()))
return false;
if (!CN_ASSERT(SetState(dwBaudRate, btParity, btByteSize, btStopBits)))
return Close(), false;if (!CN_ASSERT(SetupPort()))
return Close(), false;if ((dwOption_ & (EN_THREAD|EN_RX_THREAD|EN_TX_THREAD)) && !CN_ASSERT(BeginThread()))
return Close(), false;
return true;
}
//! 直接读物理端口 /param[out] pBuffer 目标缓冲区 /param[in] dwLength 读取长度 /param[in] dwWaitTime 等待时间(默认INFINITE) /return 返回实际读取字节数
DWORD ReadPort(LPVOID pBuffer, DWORD dwLength, DWORD dwWaitTime = INFINITE)
{
COMSTAT Stat;
DWORD dwError, dwReadResult = 0;
if(!CN_ASSERT(IsOpen()) || !dwLength)
return 0;
if(::ClearCommError(hComm_, &dwError, &Stat) && dwError > 0)
::PurgeComm(hComm_, PURGE_RXABORT);#ifndef CN_COMM_FOR_CE
if (IsOverlappedMode())//! 重叠I/O下 dwWaitTime==INFINITE 将等待到自然超时或任务完成 具体时间由超时结构决定 默认设定0.5秒
{
if (dwWaitTime == INFINITE)//! 如果你没有启用读反冲 直接读取端口将导致阻塞 相当于原来1.3版本 Read(pBuffer, dwLength, false);
{
if (!::ReadFile(hComm_, pBuffer, dwLength, &dwReadResult, &RO_))
{//! 在界面里使用CnComm推荐使用读缓冲区或者在dwWaitTime指定超时时间
if (CN_ASSERT(::GetLastError() == ERROR_IO_PENDING))
{
while (!CN_ASSERT(::GetOverlappedResult(hComm_, &RO_, &dwReadResult, TRUE)))
;
}
}
}
else//! 重叠I/O下 dwWaitTime!=INFINITE 将等待到超时或任务完成 具体时间由dwWaitTime决定
{ //! dwWaitTime = 0 相当于原来1.3版本的功能
DWORD dwBegin = GetTickCount(), dwEnd, dwCost, uReadLength, uReadReturn;
uReadLength = Stat.cbInQue > dwLength ? dwLength : Stat.cbInQue;
CN_ASSERT(::ReadFile(hComm_, pBuffer, uReadLength, &uReadReturn, &RO_));
dwReadResult += uReadReturn;do
{
if (!::ReadFile(hComm_, (LPBYTE)pBuffer + dwReadResult, 1, &uReadReturn, &RO_))
{
if (dwWaitTime > 5 && WaitForSingleObject(RO_.hEvent, dwWaitTime) == WAIT_OBJECT_0)
{
dwEnd = GetTickCount();
dwCost = dwEnd>=dwBegin ? dwEnd-dwBegin : DWORD(-1L)-dwBegin+dwEnd;
CN_ASSERT(::GetOverlappedResult(hComm_, &RO_, &uReadReturn, FALSE));
dwWaitTime = dwWaitTime > dwCost ? dwWaitTime-dwCost : 0;
}
else
{
CN_ASSERT(::PurgeComm(hComm_, PURGE_RXABORT));
break;
}
}
}
while (uReadReturn && ++dwReadResult < dwLength);
}
return dwInCount_ += dwReadResult, dwReadResult;
}
#endif
//! 阻塞I/O和WinCE的I/O下 dwWaitTime无意义 超时时间由超时结构决定 默认设定1/4秒
CN_ASSERT(::ReadFile(hComm_, pBuffer, dwLength, &dwReadResult, NULL));
return dwInCount_ += dwReadResult, dwReadResult;
}
//! 读取串口 dwLength个字符到 pBuffer 返回实际读到的字符数 可读任意数据
DWORD Read(LPVOID pBuffer, DWORD dwLength, DWORD dwWaitTime = INFINITE)
{
CN_ASSERT(pBuffer);if (dwOption_ & EN_RX_BUFFER)
{
BlockBuffer::InnerLock locker(&I_);
return I_.Read(pBuffer, dwLength);
}#ifdef CN_COMM_FOR_CE
return ReadPort(pBuffer, dwLength, dwWaitTime);
#else
return ReadPort(pBuffer, dwLength, dwWaitTime);
#endif
}
//! 读取串口 dwLength - 1 个ANSI字符到 szBuffer 返回 C 模式字符串指针 适合一般字符通讯
char * ReadString(char *szBuffer, DWORD dwLength, DWORD dwWaitTime = INFINITE)
{
CN_ASSERT(szBuffer);
szBuffer[Read(szBuffer, dwLength - 1, dwWaitTime)] = '/0';
return szBuffer;
}
//! 读取串口 dwLength - 1 个UNICODE字符到 szBuffer 返回 C 模式字符串指针 适合一般字符通讯
wchar_t * ReadString(wchar_t *szBuffer, DWORD dwLength, DWORD dwWaitTime = INFINITE)
{
CN_ASSERT(szBuffer);
szBuffer[Read(szBuffer, dwLength - 1, dwWaitTime)] = L'/0';
return szBuffer;
}
//! 直接写入端口
DWORD WritePort(LPCVOID pBuffer, DWORD dwLength)
{
if(!CN_ASSERT(IsOpen()) || !dwLength)
return 0;DWORD dwError;
unsigned long uWriteLength = 0;if(::ClearCommError(hComm_, &dwError, NULL) && dwError > 0)
::PurgeComm(hComm_, PURGE_TXABORT);#ifndef CN_COMM_FOR_CE
if (IsOverlappedMode())//! 使用重叠IO是返回0,写出计数直接加上待写入长度,即假设写入成功
{//! 重叠IO下超时由超时结构控制 默认为长度*1毫秒+10秒
if(!::WriteFile(hComm_, pBuffer, dwLength, &uWriteLength, &WO_)
&& !CN_ASSERT(::GetLastError() == ERROR_IO_PENDING))
uWriteLength = 0;
else
dwOutCount_ += dwLength;
}
else
#endif //! 使用阻塞IO或WINCE下是返回实际写入长度,写出计数直接加上实际写入长度, 超时默认1/4秒
::WriteFile(hComm_, pBuffer, dwLength, &uWriteLength, NULL), dwOutCount_ += uWriteLength;return uWriteLength;
}
//! 写 EN_TX_BUFFER 决定写入缓冲区还是直接写入端口
DWORD Write(LPCVOID pBuffer, DWORD dwLength)
{
if (dwLength && (dwOption_&EN_TX_BUFFER))
{
BlockBuffer::InnerLock locker(&O_);O_.Write(pBuffer, dwLength);
if (dwOption_ & EN_TX_THREAD)
SetEvent(hWatchEvent_);
else
{
#ifdef CN_COMM_FOR_CE
SetCommMask(hComm_, dwWaitEvent_);
#else
if (IsOverlappedMode())
SetCommMask(hComm_, dwWaitEvent_);
else
SetEvent(hWatchEvent_);
#endif
}
return 0;
}
#ifdef CN_COMM_FOR_CE
return WritePort(pBuffer, dwLength);
#else
return WritePort(pBuffer, dwLength);
#endif
}
//! 写串口 ANSI字符 写ANSI C 模式字符串指针
DWORD Write(const char *szBuffer)
{
CN_ASSERT(szBuffer);
return Write((LPCVOID)szBuffer, strlen(szBuffer));
}
//! 写串口 UNICODE字符 写ANSI C 模式字符串指针
DWORD Write(const wchar_t *szBuffer)
{
CN_ASSERT(szBuffer);
return Write((LPCVOID)szBuffer, wcslen(szBuffer)*sizeof(wchar_t));
}
//! 写串口 szBuffer 可以输出格式字符串 包含缓冲区长度
DWORD Write(char *szBuffer, DWORD dwLength, char * szFormat, ...)
{
va_list va;
va_start(va, szFormat);
_vsnprintf(szBuffer, dwLength, szFormat, va);
va_end(va);
return Write(szBuffer);
}
//! 写串口 UNICODE szBuffer 可以输出格式字符串 包含缓冲区长度
DWORD Write(wchar_t *szBuffer, DWORD dwLength, wchar_t * szFormat, ...)
{
va_list va;
va_start(va, szFormat);
_vsnwprintf(szBuffer, dwLength, szFormat, va);
va_end(va);
return Write(szBuffer);
}
//! 写串口 szBuffer 可以输出格式字符串 不检查缓冲区长度 小心溢出
DWORD Write(char *szBuffer, char * szFormat, ...)
{
va_list va;
va_start(va, szFormat);
vsprintf(szBuffer, szFormat, va);
va_end(va);
return Write(szBuffer);
}
//! 写串口 szBuffer 可以输出格式字符串 不检查缓冲区长度 小心溢出
DWORD Write(wchar_t *szBuffer, wchar_t * szFormat, ...)
{
va_list va;
va_start(va, szFormat);
vswprintf(szBuffer, szFormat, va);
va_end(va);
return Write(szBuffer);
}
//! 强制输出队列内数据并等待
void FlushPort()
{
if(CN_ASSERT(IsOpen()))
FlushFileBuffers(hComm_);
}
//! 强制输出写缓冲区并强制输出队列内数据并等待
void Flush()
{
if(CN_ASSERT(IsOpen()))
{
if (dwOption_ & EN_TX_BUFFER)
{
while(O_.SafeSize())
Sleep(50);
}FlushFileBuffers(hComm_);
}
}
//! 抢先输出字符
bool TransmitChar(char c)
{
if (CN_ASSERT(IsOpen()))
{
if (::TransmitCommChar(hComm_, c))
return dwOutCount_++, true;
}return false;
}
//! 启动辅助线程控制
bool BeginThread(DWORD dwThreadOption = 0)
{
DWORD dwCreationFlags;if(CN_ASSERT(!hWatchThread_ && !hReadThread_ && !hWriteThread_))
{//! 必须是没有线程在运行状态下才可以启动线程
bContinue_ = true;
dwCreationFlags = dwOption_ & EN_SUSPEND ? CREATE_SUSPENDED : 0;if (dwThreadOption)//! 根据配置创建监视线程,独立读或写线程
dwOption_ |= dwThreadOption;
#if defined(_MT) && !defined(CN_COMM_FOR_CE)
unsigned int id, rid, wid;if (dwOption_ & EN_THREAD)
hWatchThread_ = (HANDLE)_beginthreadex(NULL, 0, WatchThreadProc, this, dwCreationFlags, &id);if (dwOption_ & EN_RX_THREAD)
hReadThread_ = (HANDLE)_beginthreadex(NULL, 0, ReadThreadProc, this, dwCreationFlags, &rid);if (dwOption_ & EN_TX_THREAD)
hWriteThread_ = (HANDLE)_beginthreadex(NULL, 0, WriteThreadProc, this, dwCreationFlags, &wid);
#else
DWORD id, rid, wid;if (dwOption_ & EN_THREAD)
hWatchThread_ = ::CreateThread(NULL, 0, WatchThreadProc, this, dwCreationFlags , &id);if (dwOption_ & EN_RX_THREAD)
hReadThread_ = ::CreateThread(NULL, 0, ReadThreadProc, this, dwCreationFlags, &rid);if (dwOption_ & EN_TX_THREAD)
hWriteThread_ = ::CreateThread(NULL, 0, WriteThreadProc, this, dwCreationFlags, &wid);
#endifif (dwOption_ & EN_THREAD)
{
CN_ASSERT(hWatchThread_ != NULL);
hWatchThreadId_ = id;if (!hWatchThread_)
{
EndThread();
return false;
}
else
{
if (dwOption_ & EN_ABOVE_NORMAL)
SetThreadPriority(hWatchThread_, THREAD_PRIORITY_ABOVE_NORMAL);
}
}if (dwOption_ & EN_RX_THREAD)
{
CN_ASSERT(hReadThread_ != NULL);
hReadThreadId_ = rid;if (!hReadThreadId_)
{
EndThread();
return false;
}
else
{
if (dwOption_ & EN_ABOVE_NORMAL)
SetThreadPriority(hReadThread_, THREAD_PRIORITY_ABOVE_NORMAL);
}
}if (dwOption_ & EN_TX_THREAD)
{
CN_ASSERT(hWriteThread_ != NULL);
hWriteThreadId_ = wid;if (!hWriteThreadId_)
{
EndThread();
return false;
}
else
{
if (dwOption_ & EN_ABOVE_NORMAL)
SetThreadPriority(hWriteThread_, THREAD_PRIORITY_ABOVE_NORMAL);
}
}return true;
}return false;
}
//! 暂停线程
bool SuspendThread(int iOption = EN_THREAD)
{
return ::SuspendThread(GetThread(iOption)) != 0xFFFFFFFF;
}
//! 恢复线程
bool ResumeThread(int iOption = EN_THREAD)
{
return ::ResumeThread(GetThread(iOption)) != 0xFFFFFFFF;
}
//! 终止线程
bool EndThread(DWORD dwWaitTime = 500)
{
if(hWatchThread_ || hReadThread_ || hWriteThread_)
{
if ((dwOption_&EN_FLUSH_ALL) && (dwOption_&EN_TX_BUFFER))
{//! 如果启用EN_FLUSH_ALL,将循环等待写缓冲区清空,如果写入线程不能正常工作,将挂起
while(O_.Size())
Sleep(50);
}bContinue_ = false;
#ifdef CN_COMM_FOR_CE
::SetCommMask(hComm_, 0);
#else
if (IsOverlappedMode())
::SetCommMask(hComm_, 0);
#endifif (hWatchThread_)
{
SetEvent(hWatchEvent_);
if(::WaitForSingleObject(hWatchThread_, dwWaitTime) != WAIT_OBJECT_0)
if(!::TerminateThread(hWatchThread_, 0))
return false;::CloseHandle(hWatchThread_);
hWatchThread_ = NULL;
}if (hReadThread_)
{
SetEvent(hReadEvent_);
if(::WaitForSingleObject(hReadThread_, dwWaitTime) != WAIT_OBJECT_0)
if(!::TerminateThread(hReadThread_, 0))
return false;::CloseHandle(hReadThread_);
hReadThread_ = NULL;
}
if (hWriteThread_)
{
SetEvent(hWriteEvent_);
if(::WaitForSingleObject(hWriteThread_, dwWaitTime) != WAIT_OBJECT_0)
if(!::TerminateThread(hWriteThread_, 0))
return false;::CloseHandle(hWriteThread_);
hWriteThread_ = NULL;
}
return true;
}return false;
}
//! 关闭串口 同时也关闭关联线程
virtual void Close(DWORD dwWaitTime = 500)
{
if(IsOpen())
{
EndThread(dwWaitTime);//! 同步结束线程if (dwOption_&EN_FLUSH || dwOption_&EN_FLUSH_ALL)
FlushFileBuffers(hComm_);::PurgeComm(hComm_, PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_RXABORT);
::CloseHandle(hComm_);
hComm_ = INVALID_HANDLE_VALUE;
}
}
//! 锁定
void Lock()
{
::EnterCriticalSection(&CS_);
}
//! 解锁
void Unlock()
{
::LeaveCriticalSection(&CS_);
}
//! 自动锁 用于函数内部 利用对象的生命周期完成锁定及解锁
struct InnerLock
{
CnComm* ptr;//!< CnComm 对象指针
//! 锁定
InnerLock(CnComm* p) : ptr(p)
{
ptr->Lock();
}
//! 解锁
~InnerLock()
{
ptr->Unlock();
}
};
//! 获得串口参数 DCB
DCB *GetState(DCB *pDcb = NULL)
{
return CN_ASSERT(IsOpen()) && ::GetCommState(hComm_, pDcb == NULL ? &DCB_ : pDcb) == TRUE ? (pDcb == NULL ? &DCB_ : pDcb) : NULL;
}
//! 设置串口参数 DCB
bool SetState(DCB *pDcb = NULL)
{
return CN_ASSERT(IsOpen()) ? ::SetCommState(hComm_, pDcb == NULL ? &DCB_ : pDcb) == TRUE : false;
}
//! 设置串口参数:波特率,停止位,等 ***
bool SetState(DWORD dwBaudRate, BYTE btParity = NOPARITY, BYTE btByteSize = 8, BYTE btStopBits = ONESTOPBIT)
{
if(CN_ASSERT(IsOpen()))
{
if(::GetCommState(hComm_, &DCB_) != TRUE)
return false;DCB_.BaudRate = dwBaudRate;
DCB_.ByteSize = btByteSize;
DCB_.Parity = btParity;
DCB_.StopBits = btStopBits;
DCB_.fParity = (btParity != NOPARITY);return ::SetCommState(hComm_, &DCB_) == TRUE;
}
return false;
}
#ifndef CN_COMM_FOR_CE
//! 设置串口参数 支持设置字符串 "9600, n, 8, 1"
bool SetState(LPCTSTR szSetStr)
{
if(CN_ASSERT(IsOpen()))
{
if(!::GetCommState(hComm_, &DCB_))
return false;if(!BuildCommDCB(szSetStr, &DCB_))
return false;DCB_.fParity = (DCB_.Parity != NOPARITY);
return ::SetCommState(hComm_, &DCB_) == TRUE;
}return false;
}
#endif
//! 获得超时结构
LPCOMMTIMEOUTS GetTimeouts(LPCOMMTIMEOUTS lpCO = NULL)
{
return CN_ASSERT(IsOpen()) && ::GetCommTimeouts(hComm_, lpCO ? lpCO : &CO_) == TRUE ? (lpCO ? lpCO : &CO_) : NULL;
}
//! 设置超时
bool SetTimeouts(LPCOMMTIMEOUTS lpCO = NULL)
{
return CN_ASSERT(IsOpen()) ? ::SetCommTimeouts(hComm_, lpCO ? lpCO : &CO_) == TRUE : false;
}
//! 设置串口的I/O缓冲区大小
bool Setup(DWORD dwInputSize = 4096, DWORD dwOutputSize = 4096)
{
return CN_ASSERT(IsOpen()) ? ::SetupComm(hComm_, dwInputSize, dwOutputSize) == TRUE : false;
}
//! 调整端口功能
bool Escape(DWORD dwType)
{
return CN_ASSERT(IsOpen()) ? EscapeCommFunction(hComm_, dwType) != 0 : false;
}
//! 获得调制解调器相关信号状态
DWORD GetModemStatus()
{
DWORD dwModemStat = 0;
return CN_ASSERT(IsOpen()) && GetCommModemStatus(hComm_, &dwModemStat) ? dwModemStat : 0;
}
//! 获得端口参数 /param[in] pCP 结构指针 如果pCP==NULL, CnComm将从堆分配内存, 并由CnComm负责释放, 用户不需要自己释放内存
LPCOMMPROP GetProperties(LPCOMMPROP pCP = NULL)
{
if (CN_ASSERT(IsOpen()))
{
if (!pCP)
{
#ifdef CN_COMM_FOR_CE
USHORT dwSize = sizeof(COMMPROP);
#else
USHORT dwSize = sizeof(COMMPROP) + sizeof(MODEMDEVCAPS);
#endifif (!pCP_)
pCP_ = (LPCOMMPROP) new BYTE[dwSize];if (pCP_)
{
memset(pCP_, 0, dwSize);pCP_->wPacketLength = dwSize;
#ifndef CN_COMM_FOR_CE
pCP_->dwProvSubType = PST_MODEM;
#endif
pCP_->dwProvSpec1 = COMMPROP_INITIALIZED;
pCP = pCP_;
}
}
}return pCP && GetCommProperties(hComm_, pCP) ? pCP : NULL;
}
//! 获取事件标识
DWORD GetMask()
{
DWORD dwMask;
return CN_ASSERT(IsOpen()) && GetCommMask(hComm_, &dwMask) ? dwMask : 0;
}
//! 清除端口缓冲区
bool Purge(DWORD dwPara = PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_RXCLEAR)
{
return CN_ASSERT(IsOpen()) ? ::PurgeComm(hComm_, dwPara)==TRUE : false;
}
//! 获得错误代码
DWORD ClearError()
{
DWORD dwError;
return CN_ASSERT(IsOpen()) && ::ClearCommError(hComm_, &dwError, NULL) ? dwError : 0;
}
//! 获得读物理缓冲区的字符数
DWORD GetQueueCount(bool bInput = true)
{
COMSTAT Stat;
DWORD dwError;
return CN_ASSERT(IsOpen()) && ::ClearCommError(hComm_, &dwError, &Stat) ? (bInput ? Stat.cbInQue : Stat.cbOutQue) : (DWORD)-1L;
}
//! 调制解调器 CTS ON
bool CheckCTS()
{
return (GetModemStatus()&MS_CTS_ON) != 0;
}
//! 调制解调器 DSR ON
bool CheckDSR()
{
return (GetModemStatus()&MS_DSR_ON) != 0;
}
//! 调制解调器 Ring ON
bool CheckRING()
{
return (GetModemStatus()&MS_RING_ON) != 0;
}
//! 调制解调器 RLSD ON
bool CheckRLSD()
{
return (GetModemStatus()&MS_RLSD_ON) != 0;
}
//! DTR 电平控制
bool SetDTR(bool bSetOrClr = true)
{
return Escape(bSetOrClr ? SETDTR : CLRDTR);
}
//! RTS 电平控制
bool SetRTS(bool bSetOrClr = true)
{
return Escape(bSetOrClr ? SETRTS : CLRRTS);
}
//! 调制解调器
bool SetBreak(bool bSetOrClr = true)
{
return Escape(bSetOrClr ? SETBREAK : CLRBREAK);
}
#ifdef CN_COMM_FOR_CE
//! WINCE
bool SetIR(bool bSetOrClr)
{
return Escape(bSetOrClr ? SETIR : CLRIR);
}
#endif
//! 流控制 SETXON SETXOFF
bool SetX(bool bOnOrOff)
{
return Escape(bOnOrOff ? SETXON : SETXOFF);
}
//! 根据通讯特点开发的缓冲区类 单向链表内存块 有一些扩展以支持和API挂接
class BlockBuffer
{
public:
//! 缓冲区内存块
struct Block
{
DWORD B_; //!< 开始偏移
DWORD E_; //!< 结束偏移
DWORD S_; //!< 块大小 内存块最大值不限 内存块最小值由CN_COMM_BUFFER_MIN_BLOCK_SIZE决定
Block* N_; //!< 下一个块指针
BYTE P_[4]; //!< 缓冲区指针 实际大小由S_决定
//! 容量
DWORD Capacity() { return S_; }
//! 实际大小
DWORD Size() { return E_ - B_; }
//! 开始缓冲区指针
BYTE* Begin() { return P_ + B_; }
//! 末端缓冲区指针
BYTE* End() { return P_ + E_; }
//! 下一个块
Block* Next() { return N_; }
//! 是否空
bool IsEmpty() { return B_ == E_; }
//! 空闲大小
DWORD FreeSize() { return S_ - E_; }
};//! 用于缓冲区单向迭代子
struct Iterator
{
BlockBuffer* P_; //!< 对象指针
Block* B_; //!< 当前块指针
DWORD I_; //!< 当前块偏移
DWORD O_; //!< 全缓冲区偏移//! 构造
Iterator(BlockBuffer *P = NULL, Block* B = NULL, DWORD I = 0, DWORD O = 0)
: P_(P), B_(B), I_(I), O_(O)
{
}
//! 是否有效
operator bool()
{
return B_ && I_<B_->E_&&I_>=B_->B_;
}
//! 是否可写
bool CanWrite()
{
return B_ && I_ < B_->S_;
}
//! 取值引用
BYTE& operator *()
{
CN_ASSERT( P_ && B_ && I_<B_->E_&&I_>=B_->B_);
return B_->P_[I_];
}
//! 移动迭代子 ++Iter;
Iterator& operator ++ ()
{
return operator +=(1);
}
//! 移动迭代子 Iter++;
Iterator& operator ++ (int)
{
return operator +=(1);
}
//! 移动迭代子 /param dwOffset 为偏移量
Iterator& operator += (DWORD dwOffset)
{
while (dwOffset)
{
if (I_+dwOffset < B_->E_)
I_ += dwOffset, O_ += dwOffset, dwOffset = 0;
else
dwOffset -= B_->E_-I_, I_ += B_->E_-I_, O_ += B_->E_-I_, B_ = B_->N_, I_ = 0;
}return *this;
}
//! 比较
bool operator == (const Iterator& iter)
{
return (P_ == iter.P_) && (B_ == iter.B_) && (I_ == iter.I_);
}
};
//! 友元
friend struct Iterator;
//! 锁定
void Lock()
{
::EnterCriticalSection(&C_);
}
//! 解锁
void Unlock()
{
::LeaveCriticalSection(&C_);
}
//! 自动锁
struct InnerLock
{
BlockBuffer* ptr;//!<对象指针
///锁定
InnerLock(BlockBuffer* p) : ptr(p)
{
if (ptr)
ptr->Lock();
}
///解锁
~InnerLock()
{
if (ptr)
ptr->Unlock();
}
};
//! 构造
BlockBuffer()
{
::InitializeCriticalSection(&C_);
S_ = 0, F_ = L_ = NULL, M_ = CN_COMM_BUFFER_MIN_BLOCK_SIZE;
}
//! 析构自动释放空间
virtual ~BlockBuffer()
{
Clear();
::DeleteCriticalSection(&C_);
}
//! 获得起始迭代子
Iterator Begin()
{
return Iterator(this, F_, F_? F_->B_ : 0, 0);
}
//! 设置块的最小长度
void SetMinBlockSize(DWORD dwMinSize)
{
M_ = dwMinSize;
}
//! 获得块的最小长度
DWORD GetMinBlockSize()
{
return M_;
}
//! 缓冲区内数据字节数
DWORD Size()
{
return S_;
}
//! 缓冲区大小
DWORD SafeSize()
{
InnerLock lock(this);
return S_;
}
//! 写入ANSI字符串缓冲区 /sa Write(LPCVOID lpBuf, DWORD dwSize)
DWORD Write(const char* lpBuf)
{
return Write(lpBuf, strlen(lpBuf));
}
//! 写入UNICODE字符串缓冲区 /sa Write(LPCVOID lpBuf, DWORD dwSize)
DWORD Write(const wchar_t* lpBuf)
{
return Write(lpBuf, wcslen(lpBuf)*sizeof(wchar_t));
}
//! 写入缓冲区 /param[out] lpBuf 目标缓冲区 /param[in] dwSize 数据字节数 /return 实际复制数据字节数
DWORD Write(LPCVOID lpBuf, DWORD dwSize)
{
DWORD dwTemp = dwSize, dwFree = FreeSize();
if (dwFree)//! 首先查找末尾空闲,并写入数据
{
DWORD dwCopy = dwFree > dwSize ? dwSize : dwFree;
memcpy(L_->P_ + L_->E_, lpBuf, dwCopy);
dwTemp -= dwCopy, L_->E_ += dwCopy;
}if (dwTemp)//! 剩余的数据分配新的空间并写入
{
memcpy(NewBlock(dwSize)->P_, lpBuf, dwTemp);
L_->E_ += dwTemp;
}S_ += dwSize;
return dwSize;
}
//! 线程安全写入缓冲区 /sa Write(LPCVOID lpBuf, DWORD dwSize)
DWORD SafeWrite(LPCVOID lpBuf, DWORD dwSize)
{
InnerLock lock(this);
return Write(lpBuf, dwSize);
}
//! 线程安全写入ANSI字符串缓冲区 /sa Write(LPCVOID lpBuf, DWORD dwSize)
DWORD SafeWrite(const char* lpBuf)
{
InnerLock lock(this);
return Write(lpBuf, strlen(lpBuf));
}
//! 线程安全写入UNICODE字符串缓冲区 /sa Write(LPCVOID lpBuf, DWORD dwSize)
DWORD SafeWrite(const wchar_t* lpBuf)
{
InnerLock lock(this);
return Write(lpBuf, wcslen(lpBuf)*sizeof(wchar_t));
}
//! 复制数据 /param[out] lpBuf 目标缓冲区 /param[in] dwSize 数据字节数 /param[in] dwStart 源缓冲区开始偏移值 /return 实际复制数据字节数
DWORD Copy(LPVOID lpBuf, DWORD dwSize, DWORD dwStart = 0)
{
CN_ASSERT(lpBuf);if (!F_ || !dwSize || dwStart >= S_)
return 0;DWORD dwTemp, dwIndex;
Block * pCur = F_, *pNext = F_->N_;for (dwTemp = 0, dwIndex = pCur->B_; dwTemp < dwStart; pCur = pCur->N_, dwIndex = pCur ? pCur->B_ : 0)
{
if (dwStart - dwTemp < pCur->E_ - pCur->B_)
{
dwIndex = pCur->B_ + dwStart - dwTemp;
break;
}dwTemp += pCur->E_ - pCur->B_;
}for (dwTemp = 0; dwTemp < dwSize && pCur; pCur = pCur->N_, dwIndex = pCur ? pCur->B_ : 0)
{
if (dwSize - dwTemp < pCur->E_ - dwIndex)
{
memcpy((LPBYTE)lpBuf+dwTemp, pCur->P_ + dwIndex, dwSize - dwTemp );
dwTemp = dwSize;
break;
}
else
{
memcpy((LPBYTE)lpBuf+dwTemp, pCur->P_ + dwIndex, pCur->E_ - dwIndex);
dwTemp += pCur->E_ - dwIndex;
}
}return dwTemp;
}
//! 线程安全复制数据 /sa Copy(LPVOID lpBuf, DWORD dwSize, DWORD dwStart=0)
DWORD SafeCopy(LPVOID lpBuf, DWORD dwSize, DWORD dwStart = 0)
{
InnerLock lock(this);
return Copy(lpBuf, dwSize, dwStart);
}
/*! /param[out] lpBuf 目标缓冲区 为NULL时仅删除不复制数据 /param[in] dwSize 操作数据字节数 /return 实际复制或删除字节数 */
//! 复制并删除缓冲区的数据到lpBuf
DWORD Read(LPVOID lpBuf, DWORD dwSize)
{
DWORD dwTemp = 0, dwCopy;for (Block * pCur = F_, *pNext = NULL; dwTemp<dwSize && pCur; pCur = pNext)
{
if (dwSize-dwTemp > pCur->E_-pCur->B_ )
dwCopy = pCur->E_ - pCur->B_;
else
dwCopy = dwSize - dwTemp;if (lpBuf)
memcpy((LPBYTE)lpBuf+dwTemp, pCur->P_ + pCur->B_, dwCopy);pNext = pCur->N_, dwTemp += dwCopy;
if (dwCopy == pCur->E_-pCur->B_)
{//! 删除回收完全空闲块, 当一般保留1个块, 在小于4*CN_COMM_BUFFER_MIN_BLOCK_SIZE的情况下
if (pNext || pCur->S_>(M_<<2))
{
delete[] (BYTE*)pCur;
F_ = pNext;
if (!F_)
L_ = NULL;
}
else
pCur->B_ = pCur->E_ = 0;
}
else
pCur->B_ += dwCopy;S_ -= dwCopy;
}return dwTemp;
}
//! 读入ANSI字符串缓冲区
char* ReadString(char* lpBuf, DWORD dMaxSize)
{
lpBuf[Read(lpBuf, dMaxSize)] = '/0';
return lpBuf;
}
//! 读入UNICODE字符串缓冲区
wchar_t* ReadString(wchar_t* lpBuf, DWORD dMaxSize)
{
lpBuf[Read(lpBuf, dMaxSize)] = L'/0';
return lpBuf;
}
//! 线程安全的读函数
DWORD SafeRead(LPVOID lpBuf, DWORD dwSize)
{
InnerLock lock(this);
return Read(lpBuf, dwSize);
}
//! 线程安全读入ANSI字符串缓冲区
char* SafeReadString(char* lpBuf, DWORD dMaxSize)
{
InnerLock lock(this);
return ReadString(lpBuf, dMaxSize);
}
//! 线程安全读入UNICODE字符串缓冲区
wchar_t* SafeReadString(wchar_t* lpBuf, DWORD dMaxSize)
{
InnerLock lock(this);
return ReadString(lpBuf, dMaxSize);
}
//! 清除 /param bDeleteAll 为true时释放所有内存, 否则保留一个内存块以提高效率
void Clear(bool bDeleteAll = false)
{
if (F_ && (F_==L_) && F_->S_>(M_<<2))
memset(F_, 0, sizeof(Block));
else
{
for (Block* t = F_; t; delete F_)
F_ = t, t = t->N_;F_ = L_ = NULL, S_ = 0;
}
}
//! 线程安全的清除函数 /sa void Clear(bool bDeleteAll = false)
void SafeClear(bool bDeleteAll = false)
{
InnerLock lock(this);
Clear(bDeleteAll);
}
//! 获得内存块指针 /param bFirst 为true时获得链表头部指针否则获得链表尾部指针
Block* GetBlockPtr(bool bFirst = true)
{
return bFirst ? F_ : L_;
}
//! 缓冲区尾部的空闲空间
DWORD FreeSize()
{
return L_ ? L_->S_-L_->E_ : 0 ;
}
//! 获得供API插入数据的直写缓冲区 传入所需大小dwSize 空闲不足分配新块 传入0返回尾部的空闲指针 尾部无空闲返回NULL
LPVOID GetFreePtr(DWORD dwSize = 0)
{
if (dwSize)
return FreeSize() > dwSize ? L_->P_ + L_->E_ : NewBlock(dwSize)->P_;
else
return L_ ? L_->P_ + L_->E_ : NULL;
}
/*! /param[in] dwSize 传入增量并非全量
/code strcpy(GetFreePtr(100), "test"), Release(4); //获取100字节的缓冲区 拷入四个字节 调整大小增加4个字节 /endcode
*/
//! 利用API直接写入用GetFreePtr()获得空闲指针, 同步调整缓冲区大小
DWORD Release(DWORD dwSize)
{
return (dwSize <= L_->S_) ? (L_->E_ += dwSize, S_ += dwSize, dwSize) : 0;
}
//! 获得第一个有效块的缓冲区指针
LPVOID GetPtr()
{
return F_ ? F_->P_ + F_->B_ : NULL;
}
//! 获得第一个有效块的缓冲区指针指向的数据大小
DWORD GetPtrSize()
{
return F_ ? F_->E_ - F_->B_ : 0;
}
//! 数组访问 如果块数超过1 效率很低 推荐用迭代子访问可以获得理想性能
BYTE& operator[](DWORD dwOffset)
{
CN_ASSERT(F_);if (F_ == L_)
{
CN_ASSERT(F_->S_ > dwOffset);
return F_->P_[dwOffset];
}
else
{
Iterator iter = Begin();
iter += dwOffset;
CN_ASSERT(iter);
return *iter;
}
}
protected://! 新建块 自动添加在尾部
Block* NewBlock(DWORD dwSize)
{
dwSize = dwSize < M_ ? M_ : dwSize;
Block * pNew = (Block *) new BYTE[sizeof(Block) - 4 + dwSize];if (pNew)
{
memset(pNew, 0, sizeof(Block));
pNew->S_ = dwSize;if (L_)
L_->N_ = pNew, L_ = pNew;
else
F_ = L_ = pNew;
}
return pNew;
}Block* F_;//!< 头指针
Block* L_;//!< 尾指针
DWORD S_;//!< 大小
DWORD M_;//!< 块最小长度
CRITICAL_SECTION C_;//!< 锁结构
};#if defined(CN_COMM_MFC_EXCEPTION)
/*! 需要定义宏 CN_COMM_MFC_EXCEPTION CN_ASSERT /n 将使用throw new MfcException(msg);语句抛出MFC异常(VC++) */
//! 用于MFC的异常
class MfcException : public CException
{
public:
//! 构造函数,要求CException自动析构
MfcException(LPCTSTR szMsg) : CException(TRUE)
{
lstrcpy(szMsg_, szMsg);#ifdef _DEBUG
CException::m_bReadyForDelete = TRUE;
#endif
}
//! 错误提示信息
BOOL GetErrorMessage( LPTSTR lpszError, UINT nMaxError, PUINT pnHelpContext = NULL )
{
#ifndef CN_COMM_FOR_CE
lstrcpyn(lpszError, szMsg_, nMaxError);
#else
_tcsncpy(lpszError, szMsg_, nMaxError - 1);
lpszError[nMaxError - 1] = _T('/0');
#endif
return TRUE;
}
protected:
TCHAR szMsg_[256]; //!< 错误信息 格式:CN_ASSERT(表达式) failed; CnComm(行号); Code(GetLastError()); 错误代码描述
};
#endif//! 用户启用异常 则抛出异常; 未启用异常: DEBUG版本 控制台版本输出错误信息 并退出; Release版本弹出提示框并响应用户要求
static bool Assert(LPCTSTR szMessage)
{
#if defined(_DEBUG) || defined(CN_COMM_STD_EXCEPTION) || defined(CN_COMM_VCL_EXCEPTION) || defined(CN_COMM_MFC_EXCEPTION)
TCHAR szMsg[256];
DWORD dwError, dwLength;
_tcscpy(szMsg, szMessage);dwError = GetLastError();
if (dwError)//! 错误代码(GetLastError())不为 0 输出错误描述
{
dwLength = _tcslen(szMsg);
_sntprintf(szMsg + dwLength, 256 - _tcslen(szMsg), _T("Code:%d; "), dwError);
dwLength = _tcslen(szMsg);FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dwError,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //! 错误描述采用本地语言
szMsg + dwLength - 1,
256 - dwLength - 1,
NULL
);
}
#else
;// Release版本 什么都不做 预留
#endif#if defined(CN_COMM_STD_EXCEPTION)
#ifdef _UNICODE
char szTemp[512] = {0};
WideCharToMultiByte(CP_ACP, 0, szMsg, -1, szTemp, wcslen(szMsg)*sizeof(wchar_t), NULL, NULL);
throw std::runtime_error(szTemp);
#else
throw std::runtime_error(szMsg);
#endif#elif defined(CN_COMM_VCL_EXCEPTION)
throw Exception(szMsg);
#elif defined(CN_COMM_MFC_EXCEPTION)
throw (new MfcException(szMsg));
#elif defined(_DEBUG)
OutputDebugString(szMsg);
#ifdef _CONSOLE
// 需要 setlocale(LC_ALL, "chs"); 控制台才可以正确输出UNICODE中文 这里转换ANSI 避免这样问题
#ifdef _UNICODE
char szTemp[512] = {0};
WideCharToMultiByte(CP_ACP, 0, szMsg, -1, szTemp, wcslen(szMsg)*sizeof(wchar_t), NULL, NULL);
fprintf(stderr, szTemp);
#else
fprintf(stderr, szMsg);
#endifsystem("pause");
exit(1);
#else
switch(MessageBox(NULL, szMsg, _T("CN_ASSERT"), MB_ABORTRETRYIGNORE))
{
case IDABORT:
exit(1);case IDRETRY:
DebugBreak();
break;case IDIGNORE:
break;
}
#endif#else
;// Release版本 什么都不做 预留
#endif
return false;
}protected:
DWORD dwOption_; //!< 关键模式配置 在构造函数中设定
HANDLE hComm_; //!< 串口句柄
DWORD dwPort_; //!< 串口号 0为特殊串口 -1为无效 主要终于消息通知甄别
TCHAR szName_[64]; //!< 保存串口名称 类似COM1的字符串
CRITICAL_SECTION CS_; //!< 临界互斥锁
DCB DCB_; //!< 波特率,停止位,等
COMMTIMEOUTS CO_; //!< 超时结构
COMMPROP* pCP_; //!< 串口参数BlockBuffer I_; //!< 读缓冲区
BlockBuffer O_; //!< 写缓冲区
DWORD dwInCount_; //!< 读计数
DWORD dwOutCount_; //!< 写计数bool bContinue_; //!< 线程继续运行循环标志
DWORD dwWaitEvent_; //!< WaitCommEvent 的监视事件HANDLE hWatchThread_; //!< 监视辅助线程
DWORD hWatchThreadId_; //!< 监视辅助线程 ID
HANDLE hWatchEvent_; //!< 通知监视线程HANDLE hReadThread_; //!< 接收辅助线程 实现双工提高效率
DWORD hReadThreadId_; //!< 接收辅助线程 ID
HANDLE hReadEvent_; //!< 通知接收线程HANDLE hWriteThread_; //!< 发送辅助线程 实现双工提高效率
DWORD hWriteThreadId_; //!< 发送辅助线程 ID
HANDLE hWriteEvent_; //!< 通知发送线程HWND hNotifyWnd_; //!< 通知窗口
DWORD hNotifyThreadId_; //!< 通知线程
DWORD dwNotifyNum_; //!< 接受多少字节(>=dwNotifyNum_)发送通知消息#ifndef CN_COMM_FOR_CE
OVERLAPPED RO_; //!< 重叠I/O ReadFile
OVERLAPPED WO_; //!< 重叠I/O WriteFile
OVERLAPPED EO_; //!< 重叠I/O WaitCommEvent
#endif//! 初始化
virtual void Init()
{
::InitializeCriticalSection(&CS_);memset(szName_, 0, 64*sizeof(TCHAR));
memset(&DCB_, 0, sizeof(DCB_));
DCB_.DCBlength = sizeof(DCB_);hComm_ = INVALID_HANDLE_VALUE;
dwPort_ = (DWORD)-1;
dwOutCount_ = 0;
dwInCount_ = 0;
dwWaitEvent_ = CN_COMM_WAIT_EVENT;
hWatchThread_ = NULL;
hReadThread_ = NULL;
hWriteThread_ = NULL;
hWatchThreadId_ = 0;
hReadThreadId_ = 0;
hWriteThreadId_ = 0;
hNotifyWnd_ = NULL;
dwNotifyNum_ = 0;
hNotifyThreadId_ = 0;
pCP_ = NULL;#ifndef CN_COMM_FOR_CE
memset(&RO_, 0, sizeof(RO_));
memset(&WO_, 0, sizeof(WO_));
memset(&EO_, 0, sizeof(EO_));
RO_.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
CN_ASSERT(RO_.hEvent != NULL);
WO_.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
CN_ASSERT(WO_.hEvent != NULL);
EO_.hEvent= ::CreateEvent(NULL, TRUE, FALSE, NULL);
CN_ASSERT(EO_.hEvent != NULL);
#endifhWatchEvent_ = ::CreateEvent(NULL, TRUE, FALSE, NULL);
CN_ASSERT(hWatchEvent_ != NULL);
hReadEvent_ = ::CreateEvent(NULL, TRUE, FALSE, NULL);
CN_ASSERT(hReadEvent_ != NULL);
hWriteEvent_ = ::CreateEvent(NULL, TRUE, FALSE, NULL);
CN_ASSERT(hWriteEvent_ != NULL);
}
//! 析构
virtual void Destroy()
{
#ifndef CN_COMM_FOR_CE
if(RO_.hEvent != NULL)
CloseHandle(RO_.hEvent);
if(WO_.hEvent != NULL)
CloseHandle(WO_.hEvent);
if(EO_.hEvent != NULL)
CloseHandle(EO_.hEvent);
#endifif(hWatchEvent_ != NULL)
CloseHandle(hWatchEvent_);if(hReadEvent_ != NULL)
CloseHandle(hReadEvent_);if(hWriteEvent_ != NULL)
CloseHandle(hWriteEvent_);if (pCP_)
delete[] ((BYTE*)pCP_);::DeleteCriticalSection(&CS_);
}
//! 绑定串口
void BindPort(DWORD dwPort)
{
dwPort_ = dwPort;#if defined(CN_COMM_FOR_CE)
wsprintf(szName_, _T("COM%d:"), dwPort);
#else
wsprintf(szName_, _T("////.//COM%d"), dwPort);
#endif
}
//! 打开串口
virtual bool OpenPort()
{
CN_ASSERT(!IsOpen());if(IsOpen())
Close();
hComm_ = ::CreateFile(
szName_,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | (IsOverlappedMode() ? FILE_FLAG_OVERLAPPED : 0),
NULL
);
return IsOpen();
}
//! 设置串口
virtual bool SetupPort()
{
if(!CN_ASSERT(IsOpen()))
return false;if(!CN_ASSERT(::SetupComm(hComm_, 4096, 4096)))//! 配置端口发送接收队列大小, 读4096字节, 写4096字节, 阻塞I/O模式发送队列无意义
return false;
if(!CN_ASSERT(::GetCommTimeouts(hComm_, &CO_)))
return false;CO_.ReadIntervalTimeout = 100;//! 配置超时结构 字符最小间隔100ms
CO_.ReadTotalTimeoutMultiplier = 0;
CO_.ReadTotalTimeoutConstant = IsOverlappedMode() ? 500 : 250;//! 读超时 重叠I/O模式下500毫秒 阻塞I/O模式下250毫秒
CO_.WriteTotalTimeoutMultiplier = IsOverlappedMode() ? 1 : 0;
CO_.WriteTotalTimeoutConstant = IsOverlappedMode() ? 10000 : 250;//! 写超时 重叠I/O模式下(10000+1×字节数)毫秒 阻塞I/O模式下250毫秒if(!CN_ASSERT(::SetCommTimeouts(hComm_, &CO_)))
return false;
if(!CN_ASSERT(::PurgeComm(hComm_, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR )))//! 清除端口
return false;
return true;
}
//! 将端口数据读入缓冲区的
DWORD PortToBuffer(DWORD dwPortByteNum)
{
return dwPortByteNum ? I_.Release(ReadPort(I_.GetFreePtr(dwPortByteNum), dwPortByteNum)) : 0;
}
//! 将缓冲区的数据写入端口
DWORD BufferToPort(DWORD dwMaxSize = 1024)
{
if (IsOverlappedMode())
{
BlockBuffer::InnerLock locker(&O_);if (!O_.Size())
return 0;DWORD dwWrite = dwMaxSize > O_.GetPtrSize() ? O_.GetPtrSize() : dwMaxSize;
WritePort(O_.GetPtr(), dwWrite);
return O_.Read(NULL, dwWrite);
}
else
{
BYTE btTemp[1024];
DWORD dwLength, dwIndex = 0;O_.Lock();
dwLength = O_.Read(btTemp, 1024);
O_.Unlock();while ( dwIndex < dwLength )
dwIndex += WritePort(btTemp + dwIndex, dwLength - dwIndex);return dwLength;
}
}
/*! /param uMsg 消息 /param lParam 参数 /param bPostMode 采用PostMessage或者SendMessage发送消息*/
//! 当窗口句柄或线程ID有效, 送出消息, 用于相应事件通知, WPARAM默认包含串口编号
virtual void Notify(UINT uMsg, LPARAM lParam = 0, bool bPostMode = true)
{
if(hNotifyWnd_ && ::IsWindow(hNotifyWnd_))//窗口句柄有效
{
if (bPostMode)
PostMessage(hNotifyWnd_, uMsg, WPARAM(dwPort_), lParam);
else
SendMessage(hNotifyWnd_, uMsg, WPARAM(dwPort_), lParam);
}if (hNotifyThreadId_)//线程ID有效
PostThreadMessage(hNotifyThreadId_, uMsg, WPARAM(dwPort_), lParam);
}
//---------------------------------------threads callback-----------------------------------------------------
//! 响应EV_RXCHAR事件 由线程回调, 虚函数可以在基层类中扩展(推荐方式)
virtual void OnReceive()
{
Notify(ON_COM_RECEIVE);
}
//! 响应EV_DSR事件
virtual void OnDSR()
{
Notify(ON_COM_DSR, CheckDSR() ? 1 : 0);
}
//! 响应EV_CTS事件
virtual void OnCTS()
{
Notify(ON_COM_CTS, CheckCTS() ? 1 : 0);
}
//! 响应EV_BREAK事件
virtual void OnBreak()
{
ClearCommBreak(hComm_);
Notify(ON_COM_BREAK);
}
//! 响应EV_TXEMPTY事件
virtual void OnTxEmpty()
{
Notify(ON_COM_TXEMPTY);
}
//! 响应EV_ERROR事件
virtual void OnError()
{
Notify(ON_COM_ERROR, ClearError());
}
//! 响应EV_RING事件
virtual void OnRing()
{
Notify(ON_COM_RING, CheckRING() ? 1 : 0);
}
//! 响应EV_RLSD事件
virtual void OnRLSD()
{
Notify(MS_RLSD_ON, CheckRLSD() ? 1 : 0);
}
//! 响应EV_RXFLAG事件
virtual void OnRxFlag()
{
Notify(ON_COM_RXFLAG);
}
//! 响应EV_POWER事件
virtual void OnPower()
{
Notify(ON_COM_POWER);
}
//! 响应EV_RX80FULL事件
virtual void OnRx80Full()
{
Notify(ON_COM_RX80FULL);
}
//! 响应EV_EVENT1事件
virtual void OnEvent1()
{
Notify(ON_COM_EVENT1);
}
//! 响应EV_EVENT2事件
virtual void OnEvent2()
{
Notify(ON_COM_EVENT2);
}
//! 响应EV_PERR事件
virtual void OnPrintErr()
{
Notify(ON_COM_PERR);
}
//! 通用的事件处理
virtual void HandleEvent(DWORD dwMask)
{
if ((dwMask & EV_RXCHAR) && !(dwOption_&EN_RX_THREAD))
{
DWORD dwLength = GetQueueCount(true);if (dwLength)
{
if (dwOption_ & EN_RX_BUFFER)
{
PortToBuffer((dwLength << 1) + 64);//多出的字节数 用来避免事件间隙的陷阱if (I_.Size() >= dwNotifyNum_)
OnReceive();
}
else
{
if (dwLength >= dwNotifyNum_)
OnReceive();
}
}
}if (dwMask & EV_TXEMPTY)
{
if ((dwOption_&EN_TX_BUFFER) && !(dwOption_&EN_TX_THREAD) && O_.SafeSize())
BufferToPort();OnTxEmpty();
}if (dwMask & EV_RXFLAG)
OnRxFlag();if (dwMask & EV_CTS)
OnCTS();
if (dwMask & EV_DSR)
OnDSR();
if (dwMask & EV_RING)
OnRing();
if (dwMask & EV_RLSD)
OnRLSD();
if (dwMask & EV_BREAK)
OnBreak();
if (dwMask & EV_ERR)
OnError();#ifdef CN_COMM_FOR_CE
if (dwMask & EV_POWER)
OnPower();
#endif
}
#ifndef CN_COMM_FOR_CE
//! 监视线程用于重叠I/O模型
virtual DWORD OverlappedModel()
{
if(!CN_ASSERT(IsOpen()))
return 1;if(!CN_ASSERT(::SetCommMask(hComm_, dwWaitEvent_)))
return 1;for(DWORD dwMask = 0, dwLength; bContinue_ && IsOpen(); dwMask = 0)
{
if (GetQueueCount(true))//! 等待事件前会先扫描队列是否还有未处理数据 如果有模拟一个EV_RXCHAR事件 避免事件通知的陷阱
dwMask = EV_RXCHAR, Sleep(10);
else
{
if(!::WaitCommEvent(hComm_, &dwMask, &EO_))
{
if(::GetLastError() == ERROR_IO_PENDING)
::GetOverlappedResult(hComm_, &EO_, &dwLength, TRUE);
else
{
Sleep(10);
continue;
}
}
}
if(dwMask == 0)
{
if ((dwOption_&EN_TX_BUFFER) && !(dwOption_&EN_TX_THREAD) && O_.SafeSize())
BufferToPort();continue;
}
HandleEvent(dwMask);
}//forreturn 0;
}
#endif
//! 监视线程用于阻塞I/O模型
virtual DWORD NonoverlappedModel()
{
if(!CN_ASSERT(IsOpen()))
return 1;for (DWORD dwReturn; bContinue_ && IsOpen();)
{
dwReturn = WaitForSingleObject(hWatchEvent_, 50);if (!bContinue_)
break;switch (dwReturn)
{
case WAIT_OBJECT_0:
while ((dwOption_&EN_TX_BUFFER) && O_.SafeSize())
BufferToPort();ResetEvent(hWatchEvent_);
break;
case WAIT_TIMEOUT:
DWORD dwLength = GetQueueCount(true);
if (dwLength)
{
if (dwOption_ & EN_RX_BUFFER)
{
PortToBuffer(dwLength);if (I_.Size() >= dwNotifyNum_)
OnReceive();
}
else
{
if (dwLength >= dwNotifyNum_)
OnReceive();
}
}if ( (dwOption_&EN_TX_BUFFER) && O_.SafeSize() )
BufferToPort();break;
}
}return 0;
}
#ifdef CN_COMM_FOR_CE
//! 监视线程用于WINCE的I/O模型(重叠+阻塞 也不知微软把它叫什么模式)
virtual DWORD EmbeddedModel()
{
if(!CN_ASSERT(IsOpen()))
return 1;if(!CN_ASSERT(::SetCommMask(hComm_, dwWaitEvent_)))
return 1;for(DWORD dwMask = 0; bContinue_ && IsOpen(); dwMask = 0)
{
if (GetQueueCount(true))//! 等待事件前会先扫描队列是否还有未处理数据 如果有模拟一个EV_RXCHAR事件 避免事件通知的陷阱
dwMask = EV_RXCHAR, Sleep(10);
else
{
if(!::WaitCommEvent(hComm_, &dwMask, NULL))
continue;
}
if(dwMask == 0)
{
if ( (dwOption_&EN_TX_BUFFER) && !(dwOption_&EN_TX_THREAD) && O_.SafeSize())
BufferToPort();continue;
}
HandleEvent(dwMask);}//for
return 0;
}
#endif
//! 用于双工处理当然您可以改变用途
virtual DWORD ReadModel()
{
while(bContinue_)
{
Sleep(50);DWORD dwLength = GetQueueCount(true);
if (dwLength)
{
if (dwOption_ & EN_RX_BUFFER)
{
PortToBuffer(dwLength);if (I_.Size() >= dwNotifyNum_)
OnReceive();
}
else
{
if (dwLength >= dwNotifyNum_)
OnReceive();
}
}
}return 0;
}
//! 用于双工处理当然您可以改变用途
virtual DWORD WriteModel()
{
CN_ASSERT(dwOption_ & EN_TX_BUFFER);while (bContinue_)
{
DWORD dwReturn = ::WaitForSingleObject(hWriteEvent_, 200);while(bContinue_ && O_.SafeSize())
BufferToPort();if (dwReturn == WAIT_OBJECT_0)
ResetEvent(hWatchEvent_);
}return 0;
}
private:
//! 禁止拷贝构造
CnComm(const CnComm&);
//! 禁止赋值函数
CnComm &operator = (const CnComm&);#if defined(_MT) && !defined(CN_COMM_FOR_CE)
//! 监视线程 Watch Thread 完成监视 并根据配置决定是否完成读写
static UINT APIENTRY WatchThreadProc(LPVOID lpPara)
#else
//! 监视线程 Watch Thread 完成监视 并根据配置决定是否完成读写
static DWORD WINAPI WatchThreadProc(LPVOID lpPara)
#endif
{
#ifdef CN_COMM_FOR_CE
DWORD dwCode = ((CnComm *)lpPara)->EmbeddedModel();
#else
DWORD dwCode = ((CnComm *)lpPara)->IsOverlappedMode()
? ((CnComm *)lpPara)->OverlappedModel()
: ((CnComm *)lpPara)->NonoverlappedModel();#if defined(_MT)
_endthreadex(dwCode);
#endif
#endifreturn dwCode;
}#if defined(_MT) && !defined(CN_COMM_FOR_CE)
//! 双工的线程 暂时未用 估计用不着
static UINT APIENTRY ReadThreadProc(LPVOID lpPara)
#else
//! 双工的线程 暂时未用 估计用不着
static DWORD WINAPI ReadThreadProc(LPVOID lpPara)
#endif
{
DWORD dwCode = ((CnComm *)lpPara)->ReadModel();#if defined(_MT) && !defined(CN_COMM_FOR_CE)
_endthreadex(dwCode);
#endifreturn dwCode;
}#if defined(_MT) && !defined(CN_COMM_FOR_CE)
//! 双工的线程 WINCE可以采用
static UINT APIENTRY WriteThreadProc(LPVOID lpPara)
#else
//! 双工的线程 WINCE可以采用
static DWORD WINAPI WriteThreadProc(LPVOID lpPara)
#endif
{
DWORD dwCode = ((CnComm *)lpPara)->WriteModel();#if defined(_MT) && !defined(CN_COMM_FOR_CE)
_endthreadex(dwCode);
#endifreturn dwCode;
}
};#endif //! _CN_COMM_H_
作者:wujian53