用WM_COPYDATA訊息來實現兩個進程之間傳遞資料

來源:互聯網
上載者:User

進程之間通訊的幾種方法:

在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協助裡面有該訊息的例子,說的也很清楚。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.