勘误
1、2009-5-14 据shunruo网友发现了一个重要的BUG,在此感谢shunruo网友,还有张冬冬网友
CnComm1.5第1180行 函数:DWORD Write(LPCVOID lpBuf, DWORD dwSize)
原有代码:
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);//重要的BUG
L_->E_ += dwTemp;
}
S_ += dwSize;
return dwSize;
}
请将该函数内容修正如下:
DWORD Write(LPCVOID lpBuf, DWORD dwSize)
{
DWORD dwTemp = dwSize, dwFree = FreeSize(), dwCopy = 0;
if (dwFree)//! 首先查找末尾空闲,并写入数据
{
dwCopy = dwFree > dwSize ? dwSize : dwFree;
memcpy(L_->P_ + L_->E_, lpBuf, dwCopy);
dwTemp -= dwCopy, L_->E_ += dwCopy;
}
if (dwTemp)//! 剩余的数据分配新的空间并写入
{
memcpy(NewBlock(dwSize)->P_, ((LPBYTE)lpBuf )+ dwCopy, dwTemp);//该处原代码未作偏移值修正,由于缓冲区一般较大,这一块在测试中忽略了
L_->E_ += dwTemp;
}
S_ += dwSize;
return dwSize;
}
2、2009-4-29 根据网友的提醒,发现一个CnComm在WinCE下使用一个潜在的缺陷
CnComm1.5第1700行 函数: virtual bool SetupPort()
原有代码:
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;
}
将该函数修正如下:
virtual bool SetupPort()
{
if(!CN_ASSERT(IsOpen()))
return false;
#if defined(CN_COMM_FOR_CE)
::SetupComm(hComm_, 4096, 4096);
#else
if(!CN_ASSERT(::SetupComm(hComm_, 4096, 4096)))//! 配置端口发送接收队列大小, 读4096字节, 写4096字节, 阻塞I/O模式发送队列无意义
return false;
#endif
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;
}
3 、2009-5-18 根据网友腾空的提示,发现一个关于内置缓冲区的BUG
CnComm1.5第1726行 函数: DWORD PortToBuffer(DWORD dwPortByteNum)
原有代码:
//! 将端口数据读入缓冲区的
DWORD PortToBuffer(DWORD dwPortByteNum)
{
return dwPortByteNum ? I_.Release(ReadPort(I_.GetFreePtr(dwPortByteNum), dwPortByteNum)) : 0;
}
将该函数修正如下:
//! 将端口数据读入缓冲区的
DWORD PortToBuffer(DWORD dwPortByteNum)
{
BlockBuffer::InnerLock locker(&I_);
return dwPortByteNum ? I_.Release(ReadPort(I_.GetFreePtr(dwPortByteNum), dwPortByteNum)) : 0;
}
4、2009-6-11 根据网友deke_chen的提示,修改两个BUG
CnComm1.5 第487行 wchar_t * ReadString(wchar_t *szBuffer, DWORD dwLength, DWORD dwWaitTime = INFINITE)
原有代码:
//! 读取串口 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;
}
将该函数修正如下:
//! 读取串口 dwLength - 1 个UNICODE字符到 szBuffer 返回 C 模式字符串指针 适合一般字符通讯
wchar_t * ReadString(wchar_t *szBuffer, DWORD dwLength, DWORD dwWaitTime = INFINITE)
{
CN_ASSERT(szBuffer);
szBuffer[(Read(szBuffer, (dwLength - 1)*sizeof(wchar_t), dwWaitTime) +1)/ sizeof(wchar_t)] = L'\0';
return szBuffer;
}
注:不推荐使用UNICODE字符串通讯,因为串口是字符通讯,不能保障2个字节的UNICODE字符一次性被接收到。
CnComm1.5 第1307行 wchar_t* ReadString(wchar_t* lpBuf, DWORD dMaxSize)
原有代码:
//! 读入UNICODE字符串缓冲区
wchar_t* ReadString(wchar_t* lpBuf, DWORD dMaxSize)
{
lpBuf[Read(lpBuf, dMaxSize)] = L'\0';
return lpBuf;
}
将该函数修正如下:
//! 读入UNICODE字符串缓冲区
wchar_t* ReadString(wchar_t* lpBuf, DWORD dMaxSize)
{
lpBuf[(Read(lpBuf, dMaxSize*sizeof(wchar_t))+1) / sizeof(wchar_t)] = L'\0';
return lpBuf;
}
CnComm1.5 第1331行 void Clear(bool bDeleteAll = false)
原有代码:
//! 清除 \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;
}
}
将该函数修正如下:
//! 清除 \param bDeleteAll 为true时释放所有内存, 否则保留一个内存块以提高效率
void Clear(bool bDeleteAll = false)
{
if (F_ && (F_==L_) && F_->S_>(M_<<2))
{
DWORD S = F_->S_;
memset(F_, 0, sizeof(Block)), F_->S_ = S;
}
else
{
for (Block* t = F_; t; delete F_)
F_ = t, t = t->N_;
F_ = L_ = NULL, S_ = 0;
}
}
Faq
1、用CnComm写的程序发送不正常,但用其他程序打开端口后,再用CnComm写的程序就好了?
分析:
具体原因是配置参数失败。并且该网友没有检查Open()或者SetState()的返回值, 所以没有发现配置参数失败造成的。
比如设置波特率"9600,O,7,1",不小心把配置字符串写成了"9600,7,O,1"。
使用CnComm配置后失败,并没有检查返回值,发送数据就会是乱码,或者不能正确接收。
这时很自然的,你会用其他工具程序打开端口,由于有可视界面,打开参数正确,又能正常发送。
然后你又想用CnComm再打开试试,结果发现又行了。
实际上配置串口失败,会采用上一次正确配置,所以看上去正常了,实际上单独运行就不行了。
这和我的笔误,即有一回把"9600,N,8,1"误写成了"9600,8,N,1",客观上造成去多网友照搬出错,对于这一部分网友,真诚表示歉意。
不过我还有看到更夸张的写法,但部分网友不检查返回值,是一个很不好的现象。
if (!Com.Open(1, 9600))
assert(0);
if (!Com.Open(1, "9600,N,8,1"))
assert(0);
最起码也要像上面这样写,当然这只是举例,实际应用应该更严谨才对。
另外在CnComm1.5版本以后打开和配置出错,调试版中都会产生错误提示,即便是你不检查返回值。
CnComm配置字符串的标准写法:
"BBBB,P,D,S" BBBB 为波特率,P 为奇偶校验,D 为数据位数,S 为停止位数。
合法的奇偶校验值:
E 偶数 (Even)
M 标记 (Mark)
N 缺省 (Default)
None
O 奇数 (Odd)
S 空格 (Space)
合法的数据位:
4
5
6
7
8 (缺省)
合法的停止位值:
1 (缺省)
1.5
2
2、发送10个字节数据,每次都分成两部分,比如"0123456789", 被分成"01234"和"56789", 能不能一次读取10个字节?
分析:这是个通讯的基本问题。
由于是10个字节,你才会问这个问题,如果是1百万个字节,你就不会这样问我了。
你肯定不敢开口要求一次性接受1百万个字节。
通讯归根结底是看似连续,实则断续。
在CnComm1.5已经针对这个问题作了修改,对于百千个字节可以做到连续一次性接受
CnComm1.3可以采用Read(buf, 1000, false);
最好升级到CnComm1.5。这里参见我在CSDN博客中接收处理两种用法
最规范的做法,是利用缓冲区缓存数据,然后再做处理。
3、调用发送语句后,马上关闭串口,常常数据发不出。
分析:如果你采用重叠IO方式即CnComm默认方式打开串口,发送数据并不是在Write语句调用后马上发生,而是系统在后台帮你发送。
而你立即调用关闭,关闭里有清空队列的代码,就没有系统运行的机会。
解决办法:同步IO,不存在这个问题。
使用重叠IO,在CnComm1.5 ,可以按如下做法
CnComm com;
com.Open(1);
com.Write("test");
com.Flush();
com.Close();