筆者曾在一個項目的實施過程中,需要使用WM_COPYDATA在本地機器的兩個進程間傳輸數據。在C++中實現非常簡單,但在C#中實現時卻出現了麻煩。由於沒有指針,使用COPYDATASTRUCT結構傳遞數據時,無法正確傳遞lpData。從網上搜尋文檔,找到一個例子,是將COPYDATASTRUCT結構的lpData聲明為string。這樣雖然能傳遞字符串,但不能傳遞隨意的二進制數據。
偶然地,我查閱MSDN幫助時,發現了Marshal類。該類概述描述道:提供了一個方法集,這些方法用於分配非托管內存、復制非托管內存塊、將托管類型轉換為非托管類型,此外還提供了在與非托管代碼交互時使用的其他雜項方法。這時,我豁然開朗,覺得找到了一個托管代碼與非托管代碼交互的橋梁。
於是我聲明COPYDATASTRUCT如下:
[StructLayout(LayoutKind.Sequential)] public struct COPYDATASTRUCT { public IntPtr dwData; public int cbData; public IntPtr lpData; }
在發送數據時,我使用Marshal類分配一塊全局內存,並將數據拷入這塊內存,然后發送消息:
COPYDATASTRUCT cds; cds.dwData = (IntPtr)flag; cds.cbData = data.Length; cds.lpData = Marshal.AllocHGlobal(data.Length); Marshal.Copy(data,0,cds.lpData,data.Length); SendMessage(WINDOW_HANDLER,WM_COPYDATA,0,ref cds);
在接收數據時,我使用Marshal類將數據從這塊全局內存拷出,然后處理消息:
COPYDATASTRUCT cds = new COPYDATASTRUCT(); Type mytype = cds.GetType(); cds = (COPYDATASTRUCT)m.GetLParam(mytype); uint flag = (uint)(cds.dwData); byte[] bt = new byte[cds.cbData]; Marshal.Copy(cds.lpData,bt,0,bt.Length);
詳細源碼如下:
/// <summary> /// Windows 的COPYDATA消息封裝類。 /// </summary> public class Messager : System.Windows.Forms.Form { /// <summary> /// 必需的設計器變量。 /// </summary> private System.ComponentModel.Container components = null; //消息標識 private const int WM_COPYDATA = 0x004A; //消息數據類型(typeFlag以上二進制,typeFlag以下字符) private const uint typeFlag = 0x8000; /// <summary> /// 重載CopyDataStruct /// </summary> [StructLayout(LayoutKind.Sequential)] public struct COPYDATASTRUCT { public IntPtr dwData; public int cbData; public IntPtr lpData; } // [DllImport("User32.dll",EntryPoint="SendMessage")] private static extern int SendMessage( int hWnd, // handle to destination window int Msg, // message int wParam, // first message parameter ref COPYDATASTRUCT lParam // second message parameter ); // [DllImport("User32.dll",EntryPoint="FindWindow")] private static extern int FindWindow(string lpClassName,string lpWindowName); //接收到數據委托與事件定義 public delegate void ReceiveStringEvent(object sender,uint flag,string str); public delegate void ReceiveBytesEvent(object sender,uint flag,byte[] bt); public event ReceiveStringEvent OnReceiveString; public event ReceiveBytesEvent OnReceiveBytes; //發送數據委托與事件定義 public delegate void SendStringEvent(object sender,uint flag,string str); public delegate void SendBytesEvent(object sender,uint flag,byte[] bt); public event SendStringEvent OnSendString; public event SendBytesEvent OnSendBytes; // public Messager() { // // Windows 窗體設計器支持所必需的 // InitializeComponent(); // // TODO: 在 InitializeComponent 調用后添加任何構造函數代碼 // } /// <summary> /// 清理所有正在使用的資源。 /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows 窗體設計器生成的代碼 /// <summary> /// 設計器支持所需的方法 - 不要使用代碼編輯器修改 /// 此方法的內容。 /// </summary> private void InitializeComponent() { // // Messager // this.AutoScaleBaseSize = new System.Drawing.Size(6, 14); this.ClientSize = new System.Drawing.Size(200, 14); this.Name = "Messager"; this.ShowInTaskbar = false; this.Text = "Demo_Emluator"; this.WindowState = System.Windows.Forms.FormWindowState.Minimized; } #endregion /// <summary> ///重載窗口消息處理函數 /// </summary> /// <param name="m"></param> protected override void DefWndProc(ref System.Windows.Forms.Message m) { switch(m.Msg) { //接收CopyData消息,讀取發送過來的數據 case WM_COPYDATA: COPYDATASTRUCT cds = new COPYDATASTRUCT(); Type mytype = cds.GetType(); cds = (COPYDATASTRUCT)m.GetLParam(mytype); uint flag = (uint)(cds.dwData); byte[] bt = new byte[cds.cbData]; Marshal.Copy(cds.lpData,bt,0,bt.Length); if(flag <= typeFlag) { if(OnReceiveString != null) { OnReceiveString(this,flag,System.Text.Encoding.Default.GetString(bt)); } } else { if(OnReceiveBytes != null) { OnReceiveBytes(this,flag,bt); } } break; default: base.DefWndProc(ref m); break; } } /// <summary> /// 發送字符串格式數據 /// </summary> /// <param name="destWindow">目標窗口標題</param> /// <param name="flag">數據標志</param> /// <param name="str">數據</param> /// <returns></returns> public bool SendString(string destWindow,uint flag,string str) { if(flag > typeFlag) { MessageBox.Show("要發送的數據不是字符格式"); return false; } int WINDOW_HANDLER = FindWindow(null,@destWindow); if(WINDOW_HANDLER == 0) return false; try { byte[] sarr = System.Text.Encoding.Default.GetBytes(str); COPYDATASTRUCT cds; cds.dwData = (IntPtr)flag; cds.cbData = sarr.Length; cds.lpData = Marshal.AllocHGlobal(sarr.Length); Marshal.Copy(sarr,0,cds.lpData,sarr.Length); SendMessage(WINDOW_HANDLER,WM_COPYDATA,0,ref cds); if(OnSendString != null) { OnSendString(this,flag,str); } return true; } catch(Exception e) { MessageBox.Show(e.Message); return false; } } /// <summary> /// 發送二進制格式數據 /// </summary> /// <param name="destWindow">目標窗口</param> /// <param name="flag">數據標志</param> /// <param name="data">數據</param> /// <returns></returns> public bool SendBytes(string destWindow,uint flag,byte[] data) { if(flag <= typeFlag) { MessageBox.Show("要發送的數據不是二進制格式"); return false; } int WINDOW_HANDLER = FindWindow(null,@destWindow); if(WINDOW_HANDLER == 0) return false; try { COPYDATASTRUCT cds; cds.dwData = (IntPtr)flag; cds.cbData = data.Length; cds.lpData = Marshal.AllocHGlobal(data.Length); Marshal.Copy(data,0,cds.lpData,data.Length); SendMessage(WINDOW_HANDLER,WM_COPYDATA,0,ref cds); if(OnSendBytes != null) { OnSendBytes(this,flag,data); } return true; } catch(Exception e) { MessageBox.Show(e.Message); return false; } }
通過測試使用,毫無問題。現貼出來,供后來者參考。