Created
February 27, 2019 06:23
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* =================== | |
* 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