今天寫的程序用到了MFC的CSocket類 。
首先在一個自己的線程中調用這個CSocket類對象的創建函數CSocket::Create(),這個線程用來執行ReSipRocate的協議棧。
然後當點擊程序窗口的菜單時,程序的主線程(UI線程)調用銷毀函數CSocket::Close()。當程序是Debug版本的時候,會報告一個斷言錯誤“Debug Assertion Failed. File: sockcore.cpp. Line: 544”。(我用的是VC.Net 2003。)
打開文件看到這個斷言錯誤發生在CSocket的積累CAsyncSocket的處理中。我來回的分析我的CSocket對象的各種操作,確認各個操作均沒有錯誤。好在能單步調試sockcore.cpp文件裡的函數。可以看到斷言的位置為(標為紅色字體):
void PASCAL CAsyncSocket::KillSocket(SOCKET hSocket, CAsyncSocket* pSocket)
{
ASSERT(CAsyncSocket::LookupHandle(hSocket, FALSE) != NULL);
_AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
CAsyncSocket::DetachHandle(hSocket, FALSE);
if (pState->m_hSocketWindow != NULL)
{
::PostMessage(pState->m_hSocketWindow, WM_SOCKET_DEAD,
(WPARAM)hSocket, 0L);
CAsyncSocket::AttachHandle(hSocket, pSocket, TRUE);
}
}
於是找到LookupHandle()的定義,如下:
CAsyncSocket* PASCAL CAsyncSocket::LookupHandle(SOCKET hSocket, BOOL bDead)
{
CAsyncSocket* pSocket;
_AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
if (!bDead)
{
pSocket = (CAsyncSocket*)
pState->m_pmapSocketHandle->GetValueAt((void*)hSocket);
if (pSocket != NULL)
return pSocket;
}
。。。
}
看出來問題出在pState->m_pmapSocketHandle->GetValueAt((void*)hSocket),在根據SOCKET hSocket查找對應的CAsyncSocket對象指針時失敗了。於是跟蹤m_pmapSocketHandle這個結構的所有操作的地方。在程序運行的全過程中,只看到將hSocket放入到m_pmapSocketHandle中的操作(pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket))。沒有看到從m_pmapSocketHandle中移除hSocket的操作(pState->m_pmapSocketHandle->RemoveKey((void*)hSocket))。然而為什麼使用這個hSocket來查找的時候會失敗呢(pState->m_pmapSocketHandle->GetValueAt((void*)hSocket))?
開始分析的時候覺得是不是程序哪裡越界了,導致這個數據結構被破壞了?太可怕了,不願意相信,還是再看看有沒有別的可能。再看看 _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState這句語句。發現_afxSockThreadState不是一個全局變量,而是一個宏定義。展開來是:
#define _afxSockThreadState AfxGetModuleThreadState()
進一步跟進:
AFX_MODULE_THREAD_STATE* AFXAPI AfxGetModuleThreadState()
{
return AfxGetModuleState
()->m_thread.GetData();
}
再往下就發現這個這個pState是線程相關的,不同的線程擁有各自不同的pState。當調用CSocket::Create()創建socket時,SIP協議棧線程把這個socket記錄到自己的pState->m_pmapSocketHandle中。等調用CSocket::Close()關閉這個socket時,UI線程在關閉前先去自己的pState->m_pmapSocketHandle中查找這個socket。(當然查不到),所以斷言失敗。
不知道這是一個Bug還是故意這麼設計的。