Skip to content

Instantly share code, notes, and snippets.

@Skibisky
Created February 27, 2019 06:23
Show Gist options
  • Save Skibisky/d2e760af14c8ce774481a16e5a1ea556 to your computer and use it in GitHub Desktop.
Save Skibisky/d2e760af14c8ce774481a16e5a1ea556 to your computer and use it in GitHub Desktop.
Working example for sending a string between C# handles (Controls/Forms) using WM_COPYDATA.
/* ===================
* WM_COPYDATA example
* ===================
*
* Working example for sending a string between C# handles (Controls/Forms) using WM_COPYDATA.
*
* TODO:
* -----
* * Add more info
* * Make generic struct copying work
*
*
* TL;DR:
* ------
* Put Catcher.Demo() in Program.Main() and step through it.
* `MessagePasser.SendString(<handle>, "test");` will send a string to the Handle.
* Make sure target window WndProc has `MessagePasser.ReceiveString` in it/
*
*/
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace PassMessage {
/// <summary>
/// A form to demonstrate WndProc CopyData
/// </summary>
public class Catcher : Form {
private static Catcher mInstance;
public delegate void CopyStringDelegate(string msg);
public delegate void CopyDataDelegate(CopyData msg);
public static event CopyStringDelegate StringCopied;
public static event CopyDataDelegate DataCopied;
private static TaskCompletionSource<Catcher> onLoad = new TaskCompletionSource<Catcher>();
public Catcher() {
this.ShowInTaskbar = false;
this.Width = 1;
this.Height = 1;
}
/// <summary>
/// Creates a catcher window to demonstrate Message Passing
/// </summary>
public static void Demo() {
Start().Wait();
StringCopied += Console.WriteLine;
DataCopied += msg => {
Console.WriteLine("Received " + msg.dwData + ": " + msg.lpData);
};
MessagePasser.SendString(GetHandle(), "test");
MessagePasser.SendString(GetHandle(), 3, "other test");
while (IsOpen()) {
Thread.Sleep(1);
}
}
/// <summary>
/// Run the Catcher
/// </summary>
/// <returns></returns>
public static async Task<Catcher> Start() {
Thread t = new Thread(runForm);
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = true;
t.Start();
return await onLoad.Task.ConfigureAwait(false);
}
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
onLoad.SetResult(this);
}
public static IntPtr GetHandle() {
IntPtr val = IntPtr.Zero;
if (mInstance == null) throw new InvalidOperationException("Catcher not started");
mInstance.Invoke((MethodInvoker)delegate { val = mInstance.Handle; });
return val;
}
public static bool IsOpen() {
bool val = false;
if (mInstance == null) throw new InvalidOperationException("Catcher not started");
if (mInstance.IsDisposed || mInstance.Disposing)
return false;
try {
mInstance.Invoke((MethodInvoker)delegate { val = mInstance.Visible; });
}
catch (Exception e) {
return false;
}
return val;
}
public static void Stop() {
if (mInstance == null) throw new InvalidOperationException("Catcher not started");
DataCopied = null;
mInstance.Invoke(new MethodInvoker(mInstance.endForm));
}
private static void runForm() {
mInstance = new Catcher();
Application.Run(mInstance);
}
private void endForm() {
this.Close();
}
/// <inheritdoc />
protected override void WndProc(ref Message m) {
if (m.Msg == NativeMethods.WM_COPYDATA) {
DataCopied?.Invoke(MessagePasser.ReceiveData(m));
StringCopied?.Invoke(MessagePasser.ReceiveString(m));
}
base.WndProc(ref m);
}
}
public static class NativeMethods {
public const int WM_COPYDATA = 0x004A;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, ref CopyData lParam);
[DllImport("user32")]
public static extern int RegisterWindowMessage(string message);
// TODO: possibly could send custom structs by changing CopyData.lpData to an IntPtr
public static IntPtr IntPtrAlloc<T>(T param) {
IntPtr retval = Marshal.AllocHGlobal(Marshal.SizeOf(param));
Marshal.StructureToPtr(param, retval, false);
return retval;
}
public static void IntPtrFree(IntPtr preAllocated) {
if (IntPtr.Zero == preAllocated) throw new InvalidOperationException("IntPtr is already 0");
Marshal.FreeHGlobal(preAllocated);
preAllocated = IntPtr.Zero;
}
public static T GetStruct<T>(IntPtr param) {
return (T)Marshal.PtrToStructure(param, typeof(T));
}
public static IntPtr StringToIntPtr(string data) {
return Marshal.StringToHGlobalAnsi(data);
}
public static string IntPtrToString(IntPtr data) {
return Marshal.PtrToStringAnsi(data);
}
}
/// <summary>
/// Pre-assembled class for sending/receiving strings between Windows applications.
/// </summary>
public static class MessagePasser {
/// <summary>
/// ReceiveData will throw if Message.Msg isn't WindowsMessageId
/// </summary>
public static bool ThrowOnBadMessage = true;
/// <summary>
/// Defaults to MS COPYDATA, can use NativeMethods.RegisterWindowMessage to create a new MessageId
/// </summary>
public static int WindowsMessageId = NativeMethods.WM_COPYDATA;
/// <summary>
/// Sends "data" to the target window
/// </summary>
/// <param name="targetWindowHandle"></param>
/// <param name="messageId">Usually WM_COPYDATA</param>
/// <param name="id"></param>
/// <param name="message"></param>
/// <returns></returns>
public static int SendData(IntPtr targetWindowHandle, int messageId, int id, string message) {
var data = new CopyData();
data.dwData = new IntPtr(id);
data.cbData = (message.Length + 1) * Marshal.SystemDefaultCharSize;
data.lpData = message;
var resp = NativeMethods.SendMessage(
targetWindowHandle,
messageId,
IntPtr.Zero,
ref data
);
var er = Marshal.GetLastWin32Error();
var errorMessage = new Win32Exception(er).Message;
if (er != 0)
throw new InvalidOperationException("Last Error isn't success:" + er + Environment.NewLine + errorMessage);
return resp.ToInt32();
}
/// <summary>
/// Send a string with an id
/// </summary>
/// <param name="targetWindowHandle"></param>
/// <param name="id"></param>
/// <param name="message"></param>
/// <returns></returns>
public static int SendString(IntPtr targetWindowHandle, int id, string message) {
return SendData(targetWindowHandle, WindowsMessageId, id, message);
}
/// <summary>
/// Call to send a string with message type 1.
/// </summary>
/// <param name="targetWindowHandle"></param>
/// <param name="message"></param>
/// <returns></returns>
public static int SendString(IntPtr targetWindowHandle, string message) {
return SendString(targetWindowHandle, 1, message);
}
/// <summary>
/// Call this from WndProc to switch on dwData
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
public static CopyData ReceiveData(Message m) {
if (m.Msg != WindowsMessageId) {
if (ThrowOnBadMessage)
throw new InvalidOperationException("Message isn't WM_COPYDATA");
return default(CopyData);
}
m.Result = new IntPtr(1);
var data = (CopyData)m.GetLParam(typeof(CopyData));
m.Result = new IntPtr(0);
return data;
}
/// <summary>
/// Call this from WndProc if you don't care about message types
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
public static string ReceiveString(Message m) {
return ReceiveData(m).lpData;
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CopyData {
/// <summary>
/// data to be passed, can be used as a message type identifier
/// </summary>
public IntPtr dwData;
/// <summary>
/// sizeof lpData
/// </summary>
public int cbData;
/// <summary>
/// data to be passed, can be null
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string lpData;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment