C# 通过 WM_COPYDATA 简单实现IPC

发布时间 2024-01-06 10:18:25作者: 张赐荣

功能概述

这段代码定义了一个名为WinIPC的静态类,该类主要用于Windows进程间通信(IPC)。以下是其主要功能:

  1. 发送和接收消息WinIPC类使用Windows消息(特别是WM_COPYDATA消息)在进程之间发送和接收数据。这是通过SendMessageWFindWindowExW这两个Windows API函数实现的,这两个函数通过DllImport属性导入自user32.dll

  2. 检查应用程序是否正在运行IsRunning方法检查具有特定应用程序ID的应用程序是否正在运行。它通过查找窗口标题为@MRW[{applicationId}]的窗口来实现这一点。

  3. 异步发送消息SendMessageAsync方法异步发送消息到具有特定应用程序ID的应用程序。它创建一个任务来发送消息,并在完成后返回任务的结果。

  4. 处理接收到的消息:当接收到WM_COPYDATA消息时,MessageReceiveWindow类的WndProc方法会调用OnMessageReceived方法来处理消息。这个方法会从消息中提取数据,并触发MessageReceived事件。

  5. 管理接收消息的窗口MessageReceiveWindow类是一个不可见的窗口,用于接收WM_COPYDATA消息。它是一个单例,可以通过GetInstance方法获取实例。

  6. 数据结构CopyDataStruct结构用于在进程之间传输数据。它包含一个指向数据的指针和数据的大小。

使用方法

检查应用程序是否正在运行

首先,可以使用IsRunning方法来检查具有特定应用程序ID的应用程序是否正在运行。例如:

bool isRunning = WinIPC.IsRunning("MyApp");

如果应用程序正在运行,IsRunning方法将返回true;否则,它将返回false

发送消息

可以使用SendMessageAsync方法来异步发送消息到具有特定应用程序ID的应用程序。例如:

byte[] message = Encoding.UTF8.GetBytes("Hello, world!");
bool success = await WinIPC.SendMessageAsync("MyApp", message);

这段代码将异步发送一个"Hello, world!"的消息到"MyApp"应用程序。如果消息发送成功,SendMessageAsync方法将返回true;否则,它将返回false

接收消息

可以通过订阅MessageReceived事件来接收消息。例如:

WinIPC.MessageReceived += (sender, data) => {
    string message = Encoding.UTF8.GetString(data);
    MessageBox.Show($"Received message: {message}");
};

上面代码会在接收到消息时弹窗显示消息内容。

完整实现代码

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WinForms
{
	static class WinIPC
	{
		const int WM_COPYDATA = 0x004a;

		[DllImport("user32.dll")]
		private extern static IntPtr SendMessageW(IntPtr hWnd, uint wMsg, IntPtr wParam, IntPtr lParam);

		[DllImport("user32.dll")]
		private extern static IntPtr FindWindowExW(IntPtr hWndParent, IntPtr hWndChildAfter, [MarshalAs(UnmanagedType.LPWStr)] string windowClassName, [MarshalAs(UnmanagedType.LPWStr)] string windowTitle);

		public static event EventHandler<byte[]> MessageReceived;

		public static bool IsRunning(string applicationId)
		{
			if (!Application.MessageLoop)
			{
				throw new InvalidOperationException("This method cannot be called on a thread that has not run a message loop.");
			}
			if (string.IsNullOrWhiteSpace(applicationId))
			{
				throw new ArgumentException($"The parameter {nameof(applicationId)} cannot be a blank string.");
			}
			IntPtr targetHwnd = FindWindowExW(IntPtr.Zero, IntPtr.Zero, null, $"@MRW[{applicationId}]");
			if (targetHwnd != IntPtr.Zero)
			{
				return (true);
			}
			MessageReceiveWindow.GetInstance(applicationId);
			return (false);
		}

		public static Task<bool> SendMessageAsync (string receiverApplicationId, byte[] message)
		{
			var tr = Task.Run(() => {
				try
				{
					IntPtr hwnd = FindWindowExW(IntPtr.Zero, IntPtr.Zero, null, $"@MRW[{receiverApplicationId}]");
					if (hwnd == IntPtr.Zero)
					{
						return (false);
					}
					IntPtr messageBuffer = Marshal.AllocHGlobal(message.Length);
					Marshal.Copy(message, 0, messageBuffer, message.Length);
					CopyDataStruct cds = new CopyDataStruct();
					cds.cbData = message.Length;
					cds.lpData = messageBuffer;
					IntPtr dataStructBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(cds));
					Marshal.StructureToPtr<CopyDataStruct>(cds, dataStructBuffer, true);
					IntPtr result = SendMessageW(hwnd, WM_COPYDATA, IntPtr.Zero, dataStructBuffer);
					Marshal.FreeHGlobal(dataStructBuffer);
					Marshal.FreeHGlobal(messageBuffer);
					return (result != IntPtr.Zero);
				}
				catch (Exception ex)
				{
					Debug.WriteLine(ex);
					return (false);
				}
			});
			return (tr);
		}

		private static void OnMessageReceived(IntPtr senderHWND, CopyDataStruct cds)
		{
			try
			{
				if (!(MessageReceived is null))
				{
					byte[] data = new byte[cds.cbData];
					Marshal.Copy(cds.lpData, data, 0, cds.cbData);
					MessageReceived?.Invoke(senderHWND, data);
				}
			}
			catch (Exception ex)
			{
				Debug.WriteLine(ex);
			}
		}

		class MessageReceiveWindow : Form
		{
			private static MessageReceiveWindow MessageReceiveWindowInstance = null;

			private MessageReceiveWindow()
			{
				this.Visible = false;
				this.Enabled = false;
				this.VisibleChanged += (_s, _e) => { if (this.Visible) this.Visible = false; };
			}

			public static MessageReceiveWindow GetInstance(string windowTitle = null)
			{
				if (MessageReceiveWindowInstance is null)
				{
					MessageReceiveWindowInstance = new MessageReceiveWindow();
					MessageReceiveWindowInstance.Show();
				}
				MessageReceiveWindowInstance.Text = $"@MRW[{windowTitle}]";
				return (MessageReceiveWindowInstance);
			}

			protected override void WndProc(ref Message m)
			{
				switch (m.Msg)
				{
					case WM_COPYDATA:
						CopyDataStruct cds = Marshal.PtrToStructure<CopyDataStruct>(m.LParam);
						WinIPC.OnMessageReceived(m.WParam, cds);
						m.Result = new IntPtr(1);
						return;
						break;
					default:
						break;
				}
				base.WndProc(ref m);
			}
		}

		[StructLayout(LayoutKind.Sequential)]
		struct CopyDataStruct
		{
			public IntPtr dwData;
			public int cbData;
			public IntPtr lpData;
		}
	}
}