本文着重講述瞭如果用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幫助裏面有該消息的例子,說的也很清楚。