从 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 ;
}
}
通过测试使用,毫无问题。现贴出来,供后来者参考。