在 OnBnClickedButton1 中:
void CscanDlg::OnBnClickedButton1() {
send_revc(...); // 內部呼叫 WaitForMultipleObjects(..., INFINITE);
}
WaitForMultipleObjects(..., INFINITE) 直接阻塞了 UI 主執行緒 (Message Pump)。
WM_PAINT (介面凍結)、WM_TIMER、用戶點擊。revcfunc) 嘗試呼叫 m_list->AddString() 更新 UI,**但 MFC 控制項非執行緒安全**,跨執行緒直接操作 UI 會導致未定義行為 (崩潰/無反應/資源鎖死)。AddString 發送的 LB_ADDSTRING 訊息永遠不會被分發處理,列表框自然無內容。// 1. 定義自訂訊息 (stdafx.h 或資源.h)
#define WM_SCAN_RESULT (WM_USER + 100)
#define WM_SCAN_FINISHED (WM_USER + 101)
// 2. 對話框類成員變數
CWinThread* m_pScanThread;
bool m_bScanning;
// 3. 啟動掃描 (按鈕事件)
void CscanDlg::OnBnClickedButton1() {
if (m_bScanning) return;
m_bScanning = true;
GetDlgItem(IDC_BUTTON1)->EnableWindow(FALSE); // 禁用按鈕
m_list.ResetContent(); // 清空舊結果
// 傳遞參數結構體指針 (需 new,執行緒結束 delete)
ScanParams* pParams = new ScanParams;
pParams->ip = m_IP; pParams->startPort = _ttoi(m_star_port);
pParams->endPort = _ttoi(m_end_port); pParams->hWnd = m_hWnd;
m_pScanThread = AfxBeginThread(ScanThreadProc, pParams, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
m_pScanThread->m_bAutoDelete = TRUE; // 結束自動刪除 CWinThread 物件
m_pScanThread->ResumeThread();
}
// 4. 背景執行緒函數 (靜態/全域)
UINT ScanThreadProc(LPVOID pParam) {
ScanParams* p = (ScanParams*)pParam;
SOCKET sock = CreateRawSocket(); // 你的原始套接字建立邏輯
// ... 發送 FIN 包邏輯 (sendfunc) ...
// ... 接收迴圈 (revcfunc) ...
// 接收到開放端口時,發送訊息給主執行緒更新 UI
while (receiving) {
int port = RecvAndParse(sock);
if (port > 0) {
::PostMessage(p->hWnd, WM_SCAN_RESULT, (WPARAM)port, 0); // 跨執行緒安全!
}
}
::PostMessage(p->hWnd, WM_SCAN_FINISHED, 0, 0);
delete p;
return 0;
}
// 5. 訊息映射與處理 (對話框類)
BEGIN_MESSAGE_MAP(CscanDlg, CDialogEx)
ON_MESSAGE(WM_SCAN_RESULT, OnScanResult)
ON_MESSAGE(WM_SCAN_FINISHED, OnScanFinished)
END_MESSAGE_MAP()
LRESULT CscanDlg::OnScanResult(WPARAM wParam, LPARAM lParam) {
int port = (int)wParam;
CString str; str.Format(_T("%d"), port);
m_list.AddString(str); // 安全!在主執行緒執行
return 0;
}
LRESULT CscanDlg::OnScanFinished(WPARAM wParam, LPARAM lParam) {
m_bScanning = false;
GetDlgItem(IDC_BUTTON1)->EnableWindow(TRUE);
AfxMessageBox(_T("掃描完成"));
return 0;
}
std::thread + PostMessage (VS2015+)邏輯同上,將 AfxBeginThread 換為 std::thread,參數用 std::shared_ptr 管理生命週期,核心 **UI 更新仍必須透過 PostMessage / SendMessage 回主執行緒**。
| 錯誤做法 | 正確做法 |
|---|---|
WaitForMultipleObjects(..., INFINITE) 在 UI 執行緒 | 背景執行緒跑任務,主執行緒自由跑 Message Pump |
背景執行緒直接 m_list.AddString() | 背景執行緒 PostMessage(hWnd, WM_CUSTOM, port, 0),主執行緒處理訊息再 AddString |
CreateThread 裸 API,無 MFC 環境 | AfxBeginThread (MFC) 或 std::thread (現代),確保 MFC 模組狀態正確 |
| 原始 Socket 需管理員權限 | 程式清單加 requireAdministrator 或右鍵「以管理員身分執行」 |
| Checksum 計算可能錯誤 (偽首部協議號應為 IPPROTO_TCP=6,非 UDP=17) | psdHeader.ptcl = IPPROTO_TCP; |
**MFC 鐵律:耗時操作 (網路、檔案、計算) 必須移出 UI 執行緒;跨執行緒更新 UI 必須用 PostMessage/SendMessage。**
你的代碼邏輯 (原始 Socket、Checksum、FIN 掃描) 大方向正確,只因 WaitForMultipleObjects 卡死主執行緒 + 跨執行緒直接操控 CListBox 導致「跑完沒結果、介面假死」。套用上述 AfxBeginThread + PostMessage 模式即可完美解決。