You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

重启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);
}

根因分析

  1. 核心触发原因:窗口查找逻辑错误
    代码中仅通过窗口标题调用FindWindow查找接收端句柄,未指定窗口类名、未校验句柄所属进程。系统重启后的开机自启动阶段,会存在大量预加载进程、临时隐藏窗口,很可能出现窗口标题与V4_APP完全一致的非目标窗口,此时FindWindow拿到的是错误窗口的句柄:
  • 错误窗口不会按业务逻辑解析WM_COPYDATA携带的数据,甚至会直接清空消息缓冲区,导致后续真正的V4_APP主窗口收不到有效数据
  • 手动结束所有进程后,抢占标题的异常窗口被同步关闭,FindWindow能拿到正确的V4_APP窗口句柄,功能恢复,与故障表现完全吻合。
  1. 编码与长度设置的潜在问题
  • 字符格式转换逻辑混乱: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 12:16:01