勘誤
1、2009-5-14 據(jù)shunruo網(wǎng)友發(fā)現(xiàn)了一個(gè)重要的BUG,在此感謝shunruo網(wǎng)友,還有張冬冬網(wǎng)友
CnComm1.5第1180行 函數(shù):DWORD Write(LPCVOID lpBuf, DWORD dwSize)
原有代碼:
DWORD Write(LPCVOID lpBuf, DWORD dwSize)
{
DWORD dwTemp = dwSize, dwFree = FreeSize();
if (dwFree)//! 首先查找末尾空閑,并寫入數(shù)據(jù)
{
DWORD dwCopy = dwFree > dwSize ? dwSize : dwFree;
memcpy(L_->P_ + L_->E_, lpBuf, dwCopy);
dwTemp -= dwCopy, L_->E_ += dwCopy;
}
if (dwTemp)//! 剩余的數(shù)據(jù)分配新的空間并寫入
{
memcpy(NewBlock(dwSize)->P_, lpBuf, dwTemp);//重要的BUG
L_->E_ += dwTemp;
}
S_ += dwSize;
return dwSize;
}
請(qǐng)將該函數(shù)內(nèi)容修正如下:
DWORD Write(LPCVOID lpBuf, DWORD dwSize)
{
DWORD dwTemp = dwSize, dwFree = FreeSize(), dwCopy = 0;
if (dwFree)//! 首先查找末尾空閑,并寫入數(shù)據(jù)
{
dwCopy = dwFree > dwSize ? dwSize : dwFree;
memcpy(L_->P_ + L_->E_, lpBuf, dwCopy);
dwTemp -= dwCopy, L_->E_ += dwCopy;
}
if (dwTemp)//! 剩余的數(shù)據(jù)分配新的空間并寫入
{
memcpy(NewBlock(dwSize)->P_, ((LPBYTE)lpBuf )+ dwCopy, dwTemp);//該處原代碼未作偏移值修正,由于緩沖區(qū)一般較大,這一塊在測(cè)試中忽略了
L_->E_ += dwTemp;
}
S_ += dwSize;
return dwSize;
}
2、2009-4-29 根據(jù)網(wǎng)友的提醒,發(fā)現(xiàn)一個(gè)CnComm在WinCE下使用一個(gè)潛在的缺陷
CnComm1.5第1700行 函數(shù): virtual bool SetupPort()
原有代碼:
virtual bool SetupPort()
{
if(!CN_ASSERT(IsOpen()))
return false;
if(!CN_ASSERT(::SetupComm(hComm_, 4096, 4096)))//! 配置端口發(fā)送接收隊(duì)列大小, 讀4096字節(jié), 寫4096字節(jié), 阻塞I/O模式發(fā)送隊(duì)列無意義
return false; //此處有些硬件不支持
if(!CN_ASSERT(::GetCommTimeouts(hComm_, &CO_)))
return false;
CO_.ReadIntervalTimeout = 100;//! 配置超時(shí)結(jié)構(gòu) 字符最小間隔100ms
CO_.ReadTotalTimeoutMultiplier = 0;
CO_.ReadTotalTimeoutConstant = IsOverlappedMode() ? 500 : 250;//! 讀超時(shí) 重疊I/O模式下500毫秒 阻塞I/O模式下250毫秒
CO_.WriteTotalTimeoutMultiplier = IsOverlappedMode() ? 1 : 0;
CO_.WriteTotalTimeoutConstant = IsOverlappedMode() ? 10000 : 250;//! 寫超時(shí) 重疊I/O模式下(10000+1×字節(jié)數(shù))毫秒 阻塞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;
}
將該函數(shù)修正如下:
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)))//! 配置端口發(fā)送接收隊(duì)列大小, 讀4096字節(jié), 寫4096字節(jié), 阻塞I/O模式發(fā)送隊(duì)列無意義
return false;
#endif
if(!CN_ASSERT(::GetCommTimeouts(hComm_, &CO_)))
return false;
CO_.ReadIntervalTimeout = 100;//! 配置超時(shí)結(jié)構(gòu) 字符最小間隔100ms
CO_.ReadTotalTimeoutMultiplier = 0;
CO_.ReadTotalTimeoutConstant = IsOverlappedMode() ? 500 : 250;//! 讀超時(shí) 重疊I/O模式下500毫秒 阻塞I/O模式下250毫秒
CO_.WriteTotalTimeoutMultiplier = IsOverlappedMode() ? 1 : 0;
CO_.WriteTotalTimeoutConstant = IsOverlappedMode() ? 10000 : 250;//! 寫超時(shí) 重疊I/O模式下(10000+1×字節(jié)數(shù))毫秒 阻塞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 根據(jù)網(wǎng)友騰空的提示,發(fā)現(xiàn)一個(gè)關(guān)于內(nèi)置緩沖區(qū)的BUG
CnComm1.5第1726行 函數(shù): DWORD PortToBuffer(DWORD dwPortByteNum)
原有代碼:
//! 將端口數(shù)據(jù)讀入緩沖區(qū)的
DWORD PortToBuffer(DWORD dwPortByteNum)
{
return dwPortByteNum ? I_.Release(ReadPort(I_.GetFreePtr(dwPortByteNum), dwPortByteNum)) : 0;
}
將該函數(shù)修正如下:
//! 將端口數(shù)據(jù)讀入緩沖區(qū)的
DWORD PortToBuffer(DWORD dwPortByteNum)
{
BlockBuffer::InnerLock locker(&I_);
return dwPortByteNum ? I_.Release(ReadPort(I_.GetFreePtr(dwPortByteNum), dwPortByteNum)) : 0;
}
4、2009-6-11 根據(jù)網(wǎng)友deke_chen的提示,修改兩個(gè)BUG
CnComm1.5 第487行 wchar_t * ReadString(wchar_t *szBuffer, DWORD dwLength, DWORD dwWaitTime = INFINITE)
原有代碼:
//! 讀取串口 dwLength - 1 個(gè)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;
}
將該函數(shù)修正如下:
//! 讀取串口 dwLength - 1 個(gè)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字符串通訊,因?yàn)榇谑亲址ㄓ崳?span style="COLOR: #ff0000">不能保障2個(gè)字節(jié)的UNICODE字符一次性被接收到。
CnComm1.5 第1307行 wchar_t* ReadString(wchar_t* lpBuf, DWORD dMaxSize)
原有代碼:
//! 讀入U(xiǎn)NICODE字符串緩沖區(qū)
wchar_t* ReadString(wchar_t* lpBuf, DWORD dMaxSize)
{
lpBuf[Read(lpBuf, dMaxSize)] = L'\0';
return lpBuf;
}
將該函數(shù)修正如下:
//! 讀入U(xiǎn)NICODE字符串緩沖區(qū)
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時(shí)釋放所有內(nèi)存, 否則保留一個(gè)內(nèi)存塊以提高效率
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;
}
}
將該函數(shù)修正如下:
//! 清除 \param bDeleteAll 為true時(shí)釋放所有內(nèi)存, 否則保留一個(gè)內(nèi)存塊以提高效率
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寫的程序發(fā)送不正常,但用其他程序打開端口后,再用CnComm寫的程序就好了?
分析:
具體原因是配置參數(shù)失敗。并且該網(wǎng)友沒有檢查Open()或者SetState()的返回值, 所以沒有發(fā)現(xiàn)配置參數(shù)失敗造成的。
比如設(shè)置波特率"9600,O,7,1",不小心把配置字符串寫成了"9600,7,O,1"。
使用CnComm配置后失敗,并沒有檢查返回值,發(fā)送數(shù)據(jù)就會(huì)是亂碼,或者不能正確接收。
這時(shí)很自然的,你會(huì)用其他工具程序打開端口,由于有可視界面,打開參數(shù)正確,又能正常發(fā)送。
然后你又想用CnComm再打開試試,結(jié)果發(fā)現(xiàn)又行了。
實(shí)際上配置串口失敗,會(huì)采用上一次正確配置,所以看上去正常了,實(shí)際上單獨(dú)運(yùn)行就不行了。
這和我的筆誤,即有一回把"9600,N,8,1"誤寫成了"9600,8,N,1",客觀上造成去多網(wǎng)友照搬出錯(cuò),對(duì)于這一部分網(wǎng)友,真誠(chéng)表示歉意。
不過我還有看到更夸張的寫法,但部分網(wǎng)友不檢查返回值,是一個(gè)很不好的現(xiàn)象。
if (!Com.Open(1, 9600))
assert(0);
if (!Com.Open(1, "9600,N,8,1"))
assert(0);
最起碼也要像上面這樣寫,當(dāng)然這只是舉例,實(shí)際應(yīng)用應(yīng)該更嚴(yán)謹(jǐn)才對(duì)。
另外在CnComm1.5版本以后打開和配置出錯(cuò),調(diào)試版中都會(huì)產(chǎn)生錯(cuò)誤提示,即便是你不檢查返回值。
CnComm配置字符串的標(biāo)準(zhǔn)寫法:
"BBBB,P,D,S" BBBB 為波特率,P 為奇偶校驗(yàn),D 為數(shù)據(jù)位數(shù),S 為停止位數(shù)。
合法的奇偶校驗(yàn)值:
E 偶數(shù) (Even)
M 標(biāo)記 (Mark)
N 缺省 (Default)
None
O 奇數(shù) (Odd)
S 空格 (Space)
合法的數(shù)據(jù)位:
4
5
6
7
8 (缺省)
合法的停止位值:
1 (缺省)
1.5
2
2、發(fā)送10個(gè)字節(jié)數(shù)據(jù),每次都分成兩部分,比如"0123456789", 被分成"01234"和"56789", 能不能一次讀取10個(gè)字節(jié)?
分析:這是個(gè)通訊的基本問題。
由于是10個(gè)字節(jié),你才會(huì)問這個(gè)問題,如果是1百萬個(gè)字節(jié),你就不會(huì)這樣問我了。
你肯定不敢開口要求一次性接受1百萬個(gè)字節(jié)。
通訊歸根結(jié)底是看似連續(xù),實(shí)則斷續(xù)。
在CnComm1.5已經(jīng)針對(duì)這個(gè)問題作了修改,對(duì)于百千個(gè)字節(jié)可以做到連續(xù)一次性接受
CnComm1.3可以采用Read(buf, 1000, false);
最好升級(jí)到CnComm1.5。這里參見我在CSDN博客中接收處理兩種用法
最規(guī)范的做法,是利用緩沖區(qū)緩存數(shù)據(jù),然后再做處理。
3、調(diào)用發(fā)送語句后,馬上關(guān)閉串口,常常數(shù)據(jù)發(fā)不出。
分析:如果你采用重疊IO方式即CnComm默認(rèn)方式打開串口,發(fā)送數(shù)據(jù)并不是在Write語句調(diào)用后馬上發(fā)生,而是系統(tǒng)在后臺(tái)幫你發(fā)送。
而你立即調(diào)用關(guān)閉,關(guān)閉里有清空隊(duì)列的代碼,就沒有系統(tǒng)運(yùn)行的機(jī)會(huì)。
解決辦法:同步IO,不存在這個(gè)問題。
使用重疊IO,在CnComm1.5 ,可以按如下做法
CnComm com;
com.Open(1);
com.Write("test");
com.Flush();
com.Close();