從C#下使用WM_COPYDATA傳輸數據說到Marshal的應用
筆者曾在一個項目的實施過程中,需要使用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;
}
}