进程之间通讯:M_COPYDATA消息来实现两个进程之间传递数据.

文着重讲述了如果用WM_COPYDATA消息来实现两个进程之间传递数据.


进程之间通讯的几种方法:


在Windows程序中,各个进程之间常常需要交换数据,进行数据通讯。常用的方法有


  使用内存映射文件 
  通过共享内存DLL共享内存 
  使用SendMessage向另一进程发送WM_COPYDATA消息


比起前两种的复杂实现来,WM_COPYDATA消息无疑是一种经济实惠的一中方法.


WM_COPYDATA消息的主要目的是允许在进程间传递只读数据。Windows在通过WM_COPYDATA消息传递期间,不提供继承同步方式。SDK文档推荐用户使用SendMessage函数,接受方在数据拷贝完成前不返回,这样发送方就不可能删除和修改数据:


这个函数的原型及其要用到的结构如下:


SendMessage(hwnd,WM_COPYDATA,wParam,lParam); 
其中,WM_COPYDATA对应的十六进制数为0x004A


wParam设置为包含数据的窗口的句柄。lParam指向一个COPYDATASTRUCT的结构: 
typedef struct tagCOPYDATASTRUCT{ 
    DWORD dwData;//用户定义数据 
    DWORD cbData;//数据大小 
    PVOID lpData;//指向数据的指针 
}COPYDATASTRUCT; 
该结构用来定义用户数据。


具体过程如下:




首先,在发送方,用FindWindow找到接受方的句柄,然后向接受方发送WM_COPYDATA消息.


接受方在DefWndProc事件中,来处理这条消息.由于中文编码是两个字节,所以传递中文时候字节长度要搞清楚.


代码中有适量的解释,大家请自己看吧.


用WM_COPYDATA的前提:


1,知道接收消息进程的句柄。


2,接收消息进程重载了WM_COPYDATA消息映射,能对其做出反应(否则不是发送端自作多情了?)


看过前提,的出结论:在自己写的两个进程间用WM_COPYDATA再好不过。


下面CODE几行就说明了一切。


获得句柄的方法,最简单的方法就是使用FindWindow,找窗口类,或者名,如果你觉得这样不把握,那就利用SetProp个窗口做个记号....(不说这些,跑踢儿了都)


OK,开始写发送端代码:


HWND hWnd = FindWindow(NULL,"MyApp");


if(hWnd!=NULL)


{


      COPYDATASTRUCT cpd; /*给COPYDATASTRUCT结构赋值*/


      cpd.dwData = 0;


      cpd.cbData = strlen("字符串");


      cpd.lpData = (void*)"字符串";


      ::SendMessage(hWnd,WM_COPYDATANULL,(LPARAM)&cpd);//发送!


      /*完事儿了!!*/


}


接收端重载ON_WM_COPYDATA消息映射函数(下面是手工所要加的,你最好还是用ClassWizard)


afx_msg BOOL OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct);


ON_WM_COPYDATA()/*消息映射*/


BOOL CMainFrame::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct) 
{
        AfxMessageBox((LPCSTR)(pCopyDataStruct->lpData));/*利用对话框表示收到消息*/


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


进程通信还有其他一些手段,相对来说比较麻烦,但局限性要比WM_COPYDATA小。当然你也可以两端都注册一个消息来通信。


使用WM_COPYDATA进行进程间通信的一个问题


 


开发中有时需要进程间传递数据,比如对于只允许单实例运行的程序,当已有实例运行时,再次打开程序,可能需要向当前运行的实例传递信息进行特殊处理。对于传递少量数据的情况,最简单的就是用SendMessage发送WM_COPYDATA消息,所带参数wParam和lParam可以携带相关数据。由于SendMessage是阻塞的,在接收数据进程处理完数据之前不会返回,发送方不会删除或修改数据,因此这种方法是简单且安全的,不过数据量不能太大,否则会由于处理时间过长造成阻塞假死。


    用SendMessage发送WM_COPYDATA的方法如下:


   


 


 


    lResult = SendMessage(	    // returns LRESULT in lResult
       (HWND) hWndControl,	    // handle to destination control
       (UINT) WM_COPYDATA,	    // message ID
       (WPARAM) wParam,	    // = (WPARAM) () wParam;
       (LPARAM) lParam	    // = (LPARAM) () lParam;
    );


   


 


    其中,wParam为发送数据方的窗口句柄,lParam为指向一个COPYDATASTRUCT类型结构体的指针,该结构体中包含了传递的数据信息。COPYDATASTRUCT定义如下:


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


    其中,dwData为自定义的数据,cbData指定lpData指向数据的大小,lpData为指向数据的指针。按照前面所说,在使用WM_COPYDATA时要保证数据的只读属性,即不能有发送方的其他线程对传递数据进行改写。(这也解释了为什么不允许用PostMessage发送WM_COPYDATA,因为PostMessage函数是异步的。还有一点需要注意的是由于SendMessage是阻塞的,所以容易引起死锁,可以考虑用SendMessageTimeout代替。)另外,如果传递数据中涉及到对象或系统资源,必须确保接收方可以对其进行处理,比如HDC、HBITMAP之类的资源是无效的,他们属于不同的进程。


    在使用的时候,要用FindWindow等API找到接收方的窗口句柄;接收方的程序中要添加对WM_COPYDATA消息的响应。


   


    前几天写程序用到WM_COPYDATA进行进程间通信,但是接收方怎么也收不到消息。调试发现找到的窗口句柄是没有问题的,查看MSDN也没有什么提示,百思不得其解。


    后来看了一些示例代码,发现不同之处是我的SendMessage调用中wParam和lParam参数都是0,因为我只是需要通过WM_COPYDATA消息通知一下接收程序即可,不用传递任何数据。试着将这两个参数改为非空,接收方就可以收到消息了。总结结论为:wParam参数是否为0没有影响,但是lParam参数必须为非空,即必须指向一个有效的COPYDATASTRUCT结构体。


    原因是什么呢?查了一些资料发现,SendMessage(WM_COPYDATA)底层是通过文件映射(File Mapping)完成的,大概流程是发送方线程根据COPYDATASTRUCT结构体中的传递数据信息,在共享内存中进行数据复制,接收方线程则会到共享内存中读取数据进行处理。因此如果指向COPYDATASTRUCT结构的指针为空的话,流程是无法进行的,所以接收方也理所当然收不到消息。


WM_COPYDATA使用的一个例子:


//


//




进程间通信的方法有多种,其中,对于少量数据可以用WM_COPYDATA方便的实现通信(如果对于大量数据的话,由于SendMessage是阻塞的,只有接收方响应了消息,SendMessage才能返回,否则则一直阻塞,所以,对于大量数据来说,用SendMessage就容易造成窗口假死) 。


本例子分别用WM_COPYDATA 实现了两种数据类型的发送,一种为Cstring,另外一种为自定义的结构体Student:


//**********************************************************


#pragma pack(1)


struct Student {


char ID[10];


TCHAR Name[20];


UINT Age;


UINT Grade;


char Room[5];


char Tel[12];


};


#pragma pack()


//**********************************************************


因为需要在接收方的OnCopyData()函数中区分发送的两种不同类型数据。所以就定义了以下两个常量:


#define STRING 1


#define STUDENT 2


 


 


发送方:


void CSendDataDlg::OnBtSend() //实现CString类型数据的发送


{


UpdateData(TRUE);


if (m_szData.IsEmpty()) {


m_szData = _T("Hello");


UpdateData(FALSE);


}


// m_szData += '\0';


HWND hWndRcv = ::FindWindow(NULL,"Receiver");


if (hWndRcv == NULL) {


AfxMessageBox(_T("找不到接收窗口,发送不成功"));


return ;


}


COPYDATASTRUCT cpd;


cpd.dwData = STRING; //标志为CString类型


cpd.cbData = m_szData.GetLength() + 1;


//GetLength()只是取得实际字符的长度,没有包括'\0'.


cpd.lpData = (void*)m_szData.GetBuffer(cpd.cbData);


::SendMessage(hWndRcv,WM_COPYDATA,(WPARAM)this->m_hWnd,(LPARAM)&cpd);


m_szData.ReleaseBuffer();


AfxMessageBox(_T("发送成功"));


}


void CSendDataDlg::OnBtStu() //实现Student类型数据的发送


{


UpdateData();


m_szID += '\0';


m_szName += '\0';


m_szRoom += '\0';


m_szTel += '\0';


m_pStu = new Student();


strcpy(m_pStu->ID,m_szID.GetBuffer(m_szID.GetLength()));


_tcscpy(m_pStu->Name,m_szName.GetBuffer(m_szName.GetLength()));


strcpy(m_pStu->Room,m_szRoom.GetBuffer(m_szRoom.GetLength()));


strcpy(m_pStu->Tel,m_szTel.GetBuffer(m_szTel.GetLength()));


m_szID.ReleaseBuffer();m_szName.ReleaseBuffer();


m_szRoom.ReleaseBuffer();m_szTel.ReleaseBuffer();


m_pStu->Age = m_nAge;


m_pStu->Grade = m_nGrade;


HWND hWndRcv = ::FindWindow(NULL,"Receiver");


if (hWndRcv == NULL) {


AfxMessageBox(_T("找不到接收窗口,发送不成功"));


return ;


}


COPYDATASTRUCT cpd;


cpd.dwData = STUDENT; // 标志为Student类型


cpd.cbData = sizeof(Student);


cpd.lpData = (PVOID)m_pStu;


::SendMessage(hWndRcv,WM_COPYDATA,(WPARAM)this->m_hWnd,(LPARAM)&cpd);


delete m_pStu;


AfxMessageBox(_T("发送成功"));


}


接收方:


在OnInitDialog方法中:


//***************************************************************


//初始化ListCtrl控件


LVCOLUMN column;


column.mask = LVCF_TEXT | LVCF_SUBITEM | LVCF_WIDTH;


column.cx = 80;


column.iSubItem = 0;


column.pszText = _T("ID");


m_ListCtl.InsertColumn(0,&column);


column.cx = 80;


column.pszText = _T("Name");


column.iSubItem = 1;


m_ListCtl.InsertColumn(1,&column);


column.cx = 55;


column.pszText = _T("Age");


column.iSubItem = 2;


m_ListCtl.InsertColumn(2,&column);


column.cx = 55;


column.pszText = _T("Grade");


column.iSubItem = 3;


m_ListCtl.InsertColumn(3,&column);


column.cx = 55;


column.pszText = _T("Room");


column.iSubItem = 4;


m_ListCtl.InsertColumn(4,&column);


column.cx = 80;


column.pszText = _T("Tel");


column.iSubItem = 5;


m_ListCtl.InsertColumn(5,&column);


 


 


 


 


 


BOOL CReceiverDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)


{


switch (pCopyDataStruct->dwData) { // 接收到的是CString类型


case STRING:


m_szData += (LPCSTR)(pCopyDataStruct->lpData);


UpdateData(FALSE);


break;


case STUDENT: // 接收到的是Student类型


CString id,name,room,tel;


UINT age,grade;


CString str;


Student* pStu = (Student*)(pCopyDataStruct->lpData);


id = pStu->ID;


name = pStu->Name;


room = pStu->Room;


tel = pStu->Tel;


age = pStu->Age;


grade = pStu->Grade;


LVITEM item;


// 把接收到的数据显示到ListCtrl控件上


item.mask = LVIF_TEXT;


int n = m_ListCtl.GetItemCount();


item.iItem = n;


item.iSubItem = 0;


item.pszText = id.GetBuffer(id.GetLength());


id.ReleaseBuffer();


m_ListCtl.InsertItem(&item);


m_ListCtl.SetItemText(n,1,name);


str.Format("%d",age);


m_ListCtl.SetItemText(n,2,str);


str.Format("%d",grade);


m_ListCtl.SetItemText(n,3,str);


m_ListCtl.SetItemText(n,4,room);


m_ListCtl.SetItemText(n,5,tel);


UpdateData(FALSE);


//delete pStu;


break;


}


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


return TRUE;


}


 


MSDN帮助里面有该消息的例子,说的也很清楚。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值