C++ 黑客编程揭秘与防范(第3版)
上QQ阅读APP看书,第一时间看更新

1.4.2 通过WM_COPYDATA消息进行进程通信

自定义消息传递的数据类型过于简单,而通过WM_COPYDATA消息进行进程间的通信会更加灵活。但是由于SendMessage()函数在发送消息时的阻塞机制,在使用WM_COPYDATA时传递的消息也不宜过多。

1.WM_COPYDATA消息介绍

应用程序发送WM_COPYDATA消息可以将数据传递给其他应用程序。WM_COPYDATA消息需要使用SendMessage()函数进行发送,而不能使用PostMessage()消息。通过SendMessage()函数发送WM_COPYDATA消息的形式如下:

        SendMessage(
          (HWND) hWnd,
          WM_COPYDATA,
          (WPARAM) wParam,
          (LPARAM) lParam
        );

第1个参数hWnd是接收消息的目标窗口句柄;第2个参数是消息的类型,也就是当前正在介绍的消息WM_COPYDATA;第3个参数是发送消息的窗口句柄;第4个参数是一个COPYDATASTRUCT结构体的指针。

COPYDATASTRUCT结构体的定义如下:

        typedef struct tagCOPYDATASTRUCT {
            ULONG_PTR dwData;
            DWORD     cbData;
            PVOID     lpData;
        } COPYDATASTRUCT, *PCOPYDATASTRUCT;

其中,dwData是自定义的数据,cbData用来指定lpData指向的数据的大小,lpData是指向数据的指针。

在程序中,发送WM_COPYDATA消息方仍然会通过调用FindWindow()函数来查找目标窗口的句柄,而接收消息方需要响应对WM_COPYDATA消息的处理。WM_COPYDATA不是自定义消息,在编程时不必像自定义消息那样需要自己定义消息和添加消息映射,这部分工作可以直接通过MFC辅助进行。

MFC添加WM_COPYDATA消息响应的方法如下:

首先在要响应WM_COPYDATA消息的窗口对应的类上单击鼠标右键,在弹出的快捷菜单中选择“Add Windows Message Handler”,如图1-14所示。选择该菜单项后会出现如图1-15所示的添加消息响应函数对话框。

图1-14 选择“Add Windows Message Handler”

图1-15 添加消息响应函数对话框

在“New Windows messages/events:”列中找到WM_COPYDATA消息,然后双击将它添加到“Existing message/event handlers:”列中。最后单击“Add Handler”按钮,MFC就自动生成了WM_COPYDATA的消息映射及消息响应函数。Windows其他常用的消息都可以通过该对话框辅助生成消息映射及消息响应函数。

2.WM_COPYDATA程序界面及介绍

对于WM_COPYDATA消息,前面已经介绍了,程序同样分为客户端程序和服务端程序。首先来看程序运行的效果,如图1-16所示。

图1-16 WM_COPYDATA的服务端与客户端界面

WM_COPYDATA的服务端会接收WM_COPYDATA消息,在接收到WM_COPYDATA消息进行处理后同样会发送一个WM_COPYDATA消息给客户端进行消息反馈。WM_COPYDATA的客户端会通过FindWindow()函数来查找WM_COPYDATA的服务端,并发送WM_COPYDATA消息,同样也会接收服务端发来的WM_COPYDATA消息并进行处理。

3.WM_COPYDATA客户端程序的实现

有了前面的介绍,现在我们就来完成程序的编码工作,首先来看WM_COPYDATA客户端。客户端的界面中有3个控件,分别是一个按钮控件、一个编辑框控件和一个列表框控件(为列表框控件定义一个控件变量:CListBox m_ListRec;)。

WM_COPYDATA客户端的代码如下:

          void CCopyDataCDlg::OnBtnSend()
          {
              // 在此处添加处理程序代码
              // 查找接收WM_COPYDATA消息的窗口句柄
              HWND hWnd = ::FindWindow(NULL, "COPYDATA服务端");

              CString strText;
            GetDlgItemText(IDC_EDIT_SENDDATA, strText);

            // 设置COPYDATASTRUCT结构体
            COPYDATASTRUCT cds;
            cds.dwData = 0;
            cds.cbData = strText.GetLength() + 1;
            cds.lpData = strText.GetBuffer(cds.cbData);

            // m_hWnd是CWnd类中的一个成员函数
            // 表示该窗口的句柄
            ::SendMessage(hWnd, WM_COPYDATA, (WPARAM)m_hWnd, (LPARAM)&cds);
        }

        BOOL CCopyDataCDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
        {
              // 在此处添加处理程序代码或者调用默认方法

            // 处理服务端发来的WM_COPYDATA消息
            CString strText;
            strText.Format("服务端在[%s]接收到该消息", pCopyDataStruct->lpData);

            m_ListRec.AddString(strText);

              return CDialog::OnCopyData(pWnd, pCopyDataStruct);
        }

4.WM_COPYDATA服务端程序的实现

WM_COPYDATA服务端有两个控件,分别是一个列表框控件和一个按钮控件。为列表框控件定义一个控件变量:CListBox m_ListData。

WM_COPYDATA服务端的代码如下:

        BOOL CCopyDataSDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
        {
            // 在此处添加处理程序代码或者调用默认方法
            CString strText;

            // 通过发送消息的窗口句柄获得窗口对应的进程号,即PID
            DWORD dwPid = 0;
            ::GetWindowThreadProcessId(pWnd->m_hWnd, &dwPid);

            // 格式化字符串并添加至列表框中
            strText.Format("PID=[%d]的进程发来的消息为:%s",
                      dwPid, pCopyDataStruct->lpData);
            m_ListData.AddString(strText);

            // 获取本地时间
            SYSTEMTIME st;
            GetLocalTime(&st);

            CString strTime;
            strTime.Format("%02d:%02d:%02d", st.wHour, st.wMinute, st.wSecond);

            // 将本地时间发送给客户端程序
            COPYDATASTRUCT cds;
            cds.dwData = 0;
            cds.cbData = strTime.GetLength() + 1;
            cds.lpData = strTime.GetBuffer(cds.cbData);

            // 注意SendMessage()函数的第3个参数为NULL
            ::SendMessage(pWnd->m_hWnd, WM_COPYDATA, NULL, (LPARAM)&cds);

            return CDialog::OnCopyData(pWnd, pCopyDataStruct);
        }

        void CCopyDataSDlg::OnBtnDelall()
        {
              // 在此处添加处理程序代码

            // 清空列表框内容
            while ( m_ListData.GetCount() )
            {
                  m_ListData.DeleteString(0);
            }
          }

在接收消息的服务端调用GetWindowThreadProcessId()通过发送消息的窗口得到了发送消息的进程PID号,并将接收消息的时间反馈给了发送消息的客户端。

5.小结

关于WM_COPYDATA的服务端和客户端的代码都有比较详细的注释,因此没有过多解释。这里需要强调一点,WM_COPYDATA消息需要两个附加消息,也就是SendMessage()函数的wParam和lParam参数都需要使用。wParam参数表示发送消息的窗口句柄,但是该参数可以省略,还可以通过类型转换传递其他数值型的数据。lParam参数是COPYDATASTRUCT结构体指针类型,不可以省略,否则接收WM_COPYDATA消息的服务端会无法响应。