程序通訊之一 使用WM_COPYDATA C++及C#實現
程序間通訊最簡單的方式就是傳送WM_COPYDATA訊息。本文提供C++及C#程式相互通訊的二種實現方式。這樣訊息的接收端可以用C++實現,傳送端可以用C++或C#實現。
傳送WM_COPYDATA訊息:
SendMessage(接收視窗控制代碼, WM_COPYDATA, (WPARAM)傳送視窗控制代碼, (LPARAM)&CopyData);
其中的CopyData為COPYDATASTRUCT結構型別,該結構定義如下:
typedef struct tagCOPYDATASTRUCT {
DWORD dwData; // Specifies data to be passed to the receiving application.
DWORD cbData; //Specifies the size, in bytes, of the data pointed to by the lpData member.
PVOID lpData; // Pointer to data to be passed to the receiving application. can be NULL.
} COPYDATASTRUCT, *PCOPYDATASTRUCT;
注意:該訊息只能由SendMessage()來發送,而不能使用PostMessage()。因為系統必須管理用以傳遞資料的緩衝區的生命期,如果使用了PostMessage(),資料緩衝區會在接收方(執行緒)有機會處理該資料之前,就被系統清除和回收。此外如果lpData指向一個帶有指標或某一擁有虛擬函式的物件時,也要小心處理。
如果傳入的控制代碼不是一個有效的視窗或當接收方程序意外終止時,SendMessage()會立即返回,因此傳送方在這種情況下不會陷入一個無窮的等待狀態中。
返回值問題,MSDN上說如果接收方處理了,返回TRUE,否則返回FALSE,但是本人在實驗時,都是返回0(接收方已經處理)。
接收WM_COPYDATA訊息:
只要用COPYDATASTRUCT *pCopyData = (COPYDATASTRUCT*)lParam;就可以了。接收方應認為這些資料是隻讀的。
由於傳送方在接收方處理WM_COPYDATA訊息完畢前都是處於等待中,所以接收方應當儘快處理WM_COPYDATA訊息。
以一個簡單的例子來說明如何使用WM_COPYDATA訊息,有二個程式,一個用來發送表示當前時間資訊的字串,另一個接收資料後顯示到編輯框中。例子中有幾點要注意:
1.如何得到當前控制檯視窗控制代碼?VS2008下可以直接使用HWND GetConsoleWindow(void);函式。
2.使用char *ctime(const time_t *timer);將一個time_t型別轉化成一個字串時,函式會在字串末尾加下'\n',因為傳送前要將這個'\n'去掉。
傳送訊息的程式程式碼(VS2008下編譯通過):
#include <windows.h>#include <time.h>#include <conio.h>#include <stdio.h>int main(){ const char szDlgTitle[] = "RecvMessage"; HWND hSendWindow = GetConsoleWindow (); if (hSendWindow == NULL) return -1; HWND hRecvWindow = FindWindow(NULL, szDlgTitle); if (hRecvWindow == NULL) return -1; char szSendBuf[100]; time_t timenow; COPYDATASTRUCT CopyData; for (int i = 0; i < 10; i++) { time(&timenow); sprintf(szSendBuf, "%s", ctime(&timenow));//注意,ctime()返回的字串後面帶了'\n' CopyData.dwData = i; CopyData.cbData = strlen(szSendBuf); szSendBuf[CopyData.cbData - 1] = '\0'; CopyData.lpData = szSendBuf; SendMessage(hRecvWindow, WM_COPYDATA, (WPARAM)hSendWindow, (LPARAM)&CopyData); printf("%s\n", szSendBuf); Sleep(1000); } return 0;}
接收訊息程式程式碼(VC6.0下編譯通過):
程式中的IDC_EDIT_RECVMESSAGE為編輯框的ID。
#include "stdafx.h"#include "resource.h"#include <stdio.h>BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){ // TODO: Place code here. DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc); return 0;}BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam){ const char szDlgTitle[] = "RecvMessage"; static HWND s_hEditShowRecv; switch (message) { case WM_INITDIALOG: SetWindowText(hDlg, szDlgTitle); s_hEditShowRecv = GetDlgItem(hDlg, IDC_EDIT_RECVMESSAGE); return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: case IDCANCEL: EndDialog(hDlg, LOWORD(wParam)); return TRUE; } break; case WM_COPYDATA: { COPYDATASTRUCT *pCopyData = (COPYDATASTRUCT*)lParam; char szBuffer[300]; memset(szBuffer, 0, sizeof(szBuffer)); sprintf(szBuffer, "dwData:%d cbData:%d\r\nlpData:0x%08x = %s\r\n\r\n", pCopyData->dwData, pCopyData->cbData, (PVOID)pCopyData->lpData, (char*)pCopyData->lpData); //在編輯框中追加資料 SendMessage(s_hEditShowRecv, EM_SETSEL, (WPARAM)-1, (LPARAM)-1); // (0, -1)表示全選, (-1,任意)表示全不選 SendMessage(s_hEditShowRecv, EM_REPLACESEL, FALSE, (LPARAM)szBuffer); SendMessage(s_hEditShowRecv, EM_SCROLLCARET, 0, 0); } return TRUE; } return FALSE;}
執行結果如下 (先啟動接收訊息程式再執行傳送訊息程式):
有的時候,傳送訊息程式用C#實現起來更加方便,因此在這也提供了用C#實現的例子傳送訊息程式供大家參考:
using System;using System.Collections.Generic;using System.Text;using System.Threading;using System.Runtime.InteropServices; //[DllImport("user32.dll")]中DllImport的名稱空間namespace UseWMCOPYDATA{ class Program { static void Main(string[] args) { string strDlgTitle = "RecvMessage"; //接收端的視窗控制代碼 IntPtr hwndRecvWindow = ImportFromDLL.FindWindow(null, strDlgTitle); if (hwndRecvWindow == IntPtr.Zero) { Console.WriteLine("請先啟動接收訊息程式"); return; } //自己的視窗控制代碼 IntPtr hwndSendWindow = ImportFromDLL.GetConsoleWindow(); if (hwndSendWindow == IntPtr.Zero) { Console.WriteLine("獲取自己的視窗控制代碼失敗,請重試"); return; } for (int i = 0; i < 10; i++) { string strText = DateTime.Now.ToString(); //填充COPYDATA結構 ImportFromDLL.COPYDATASTRUCT copydata = new ImportFromDLL.COPYDATASTRUCT(); copydata.cbData = Encoding.Default.GetBytes(strText).Length; //長度 注意不要用strText.Length; copydata.lpData = strText; //內容 ImportFromDLL.SendMessage(hwndRecvWindow, ImportFromDLL.WM_COPYDATA, hwndSendWindow, ref copydata); Console.WriteLine(strText); Thread.Sleep(1000); } } } public class ImportFromDLL { public const int WM_COPYDATA = 0x004A; //啟用非託管程式碼 [StructLayout(LayoutKind.Sequential)] public struct COPYDATASTRUCT { public int dwData; //not used public int cbData; //長度 [MarshalAs(UnmanagedType.LPStr)] public string lpData; } [DllImport("User32.dll")] public static extern int SendMessage( IntPtr hWnd, // handle to destination window int Msg, // message IntPtr wParam, // first message parameter ref COPYDATASTRUCT pcd // second message parameter ); [DllImport("User32.dll", EntryPoint = "FindWindow")] public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("Kernel32.dll", EntryPoint = "GetConsoleWindow")] public static extern IntPtr GetConsoleWindow(); }}
執行結果如下 (先啟動接收訊息程式再執行傳送訊息程式):
下一篇《程序通訊之二 管道技術第一篇 輸入輸出的重定向》示範了程式輸入輸出的重定向,以及如何用管道來完成程序之間的通訊。