重启PC后WM_COPYDATA结构体传递数据丢失问题求助
WM_COPYDATA消息重启后携带数据为空Bug排查
故障现象
- 每次计算机重启后,接收端可正常识别到dwData=1002的WM_COPYDATA消息,但消息携带的字符串数据为空
- 手动结束所有关联进程后重新启动,功能恢复正常,下次PC重启后故障复现
- 发送逻辑封装在DLL内由外部EXE调用,接收逻辑位于V4_APP程序中,故障时程序可正常进入对应消息处理分支,但解析得到的strBatchValues为空值
相关代码
DLL内导出发送函数(外部EXE调用)
int TSECSData::NewBatch(CString strBatchValues) { HWND hWnd = ::FindWindow(NULL, V4_APP); if(hWnd) { COPYDATASTRUCT cpd; char szDataToSend[256]; sprintf_s(szDataToSend, "%S", strBatchValues); cpd.dwData = 1002; cpd.cbData = sizeof(szDataToSend); cpd.lpData = (void*)&szDataToSend; ::SendMessage(hWnd, WM_COPYDATA, NULL,(LPARAM)&cpd); } return 1; }
V4_APP内消息接收处理逻辑
BOOL CMainFrame::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct) { int nMsg = (int)(pCopyDataStruct->dwData); if(nMsg==1000) { CString strRecipe; strRecipe.Format(_T("%S"), pCopyDataStruct->lpData); theApp.OnAutoLoadRecipe(strRecipe); } else if(nMsg == 1002) { CString strBatchValues; strBatchValues.Format(_T("%S"), pCopyDataStruct->lpData); CWnd* ptr = CWnd::GetLastActivePopup(); if(ptr == m_StartupPointer) // 校验无打开的对话框 { m_MainPane.NewEndLot(strBatchValues); } else { BOOL bIsItModeless = m_StartupPointer->IsWindowEnabled(); if(bIsItModeless) m_MainPane.NewEndLot(strBatchValues); // 非模态弹窗时也执行批次逻辑 } } return CFrameWndEx::OnCopyData(pWnd, pCopyDataStruct); }
根因分析
- 核心触发原因:窗口查找逻辑错误
代码中仅通过窗口标题调用FindWindow查找接收端句柄,未指定窗口类名、未校验句柄所属进程。系统重启后的开机自启动阶段,会存在大量预加载进程、临时隐藏窗口,很可能出现窗口标题与V4_APP完全一致的非目标窗口,此时FindWindow拿到的是错误窗口的句柄:
- 错误窗口不会按业务逻辑解析WM_COPYDATA携带的数据,甚至会直接清空消息缓冲区,导致后续真正的V4_APP主窗口收不到有效数据
- 手动结束所有进程后,抢占标题的异常窗口被同步关闭,
FindWindow能拿到正确的V4_APP窗口句柄,功能恢复,与故障表现完全吻合。
- 编码与长度设置的潜在问题
- 字符格式转换逻辑混乱:MFC工程默认使用Unicode编码,CString内部存储为wchar_t宽字符,发送端用大写
%S格式化到char类型缓冲区,接收端又用_T("%S")从char*解析为CString,两次转换只要遇到非ASCII字符、长度不匹配就会出现读空、乱码问题。 - 数据长度写死:发送端固定设置
cbData = 256,未按实际字符串长度赋值,接收端读取时会带入缓冲区尾部的未初始化脏数据,极易触发字符串截断、解析失败。
修复方案
- 修正窗口查找逻辑:通过Spy++获取V4_APP主窗口的真实窗口类名,调用
FindWindow时同时传入类名与窗口标题;拿到句柄后额外通过GetWindowThreadProcessId校验所属进程路径,确保消息发送到正确的目标窗口。 - 统一编码与长度计算:两边统一使用TCHAR类型处理字符串,避免不必要的宽/单字节转换,按字符串实际长度设置COPYDATASTRUCT的cbData字段,不要写死固定长度。
- 修正后参考代码:
发送端:
接收端:int TSECSData::NewBatch(CString strBatchValues) { // 替换为实际查到的V4_APP主窗口类名 HWND hWnd = ::FindWindow(_T("V4_APP_MAIN_CLASS"), _T("V4_APP")); if(!hWnd) return 0; // 校验句柄所属进程是否为V4_APP,避免发错窗口 DWORD dwProcId = 0; GetWindowThreadProcessId(hWnd, &dwProcId); HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcId); if(hProc) { TCHAR szProcPath[MAX_PATH] = {0}; DWORD dwSize = MAX_PATH; QueryFullProcessImageName(hProc, 0, szProcPath, &dwSize); CloseHandle(hProc); // 校验进程路径是否匹配V4_APP实际路径,不匹配直接返回 if(_tcsstr(szProcPath, _T("V4_APP.exe")) == nullptr) return 0; } COPYDATASTRUCT cpd = {0}; cpd.dwData = 1002; cpd.cbData = (strBatchValues.GetLength() + 1) * sizeof(TCHAR); // 算上末尾终止符 cpd.lpData = (LPVOID)(LPCTSTR)strBatchValues; ::SendMessage(hWnd, WM_COPYDATA, NULL, (LPARAM)&cpd); return 1; }BOOL CMainFrame::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct) { if(!pCopyDataStruct || !pCopyDataStruct->lpData) return CFrameWndEx::OnCopyData(pWnd, pCopyDataStruct); int nMsg = (int)(pCopyDataStruct->dwData); if(nMsg==1000) { CString strRecipe((LPCTSTR)pCopyDataStruct->lpData, pCopyDataStruct->cbData / sizeof(TCHAR)); theApp.OnAutoLoadRecipe(strRecipe); } else if(nMsg == 1002) { CString strBatchValues((LPCTSTR)pCopyDataStruct->lpData, pCopyDataStruct->cbData / sizeof(TCHAR)); CWnd* ptr = CWnd::GetLastActivePopup(); if(ptr == m_StartupPointer) { m_MainPane.NewEndLot(strBatchValues); } else { BOOL bIsItModeless = m_StartupPointer->IsWindowEnabled(); if(bIsItModeless) m_MainPane.NewEndLot(strBatchValues); } } return CFrameWndEx::OnCopyData(pWnd, pCopyDataStruct); }
内容的提问来源于stack exchange,提问作者Sin Han Jinn
相关产品推荐
相关产品推荐

