The WM_COPYDATA message, in conjunction with the SendMessageTimeout native method, can be used to transfer data to another process. Any serializable object can therefore, easily be transferred between processes. I have created the NativeReceivedataSink class to receive messages sent from another process.
How It Works
The NativeReceiveDataSink derives from the NativeWindow class to catch the WM_COPYDATA message and associated COPYDATASTRUCT. The binary data is copied from unmanaged memory and deserialized into a managed object:
/// <summary> /// This method is called when a window message is sent to the handle of the window. /// </summary> /// <param name="m">The Windows <see cref="System.Windows.Forms.Message"/> to process.</param> [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] protected override void WndProc(ref Message m) { if (m.Msg == (int)WindowsMessages.WM_COPYDATA) { //marshals the unmanaged memory object to a managed structure: COPYDATASTRUCT st = (COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT)); //initialize the byte array: byte[] dataReceived = new byte[st.cbData]; //marshals the bytes received to a managed array: Marshal.Copy(st.lpData, dataReceived, 0, st.cbData); base.WndProc(ref m); //enables waiting application to continue before processing message if (DataReceived != null) { //initialize the serialization formatter: BinaryFormatter formatter = new BinaryFormatter(); //create a memory stream to deserialize the data using (MemoryStream ms = new MemoryStream()) { //write the data received: ms.Write(dataReceived, 0, st.cbData); //reset stream position: ms.Position = 0; //deserialize data, raise event: DataReceived(this, new DataReceivedEventArgs(formatter.Deserialize(ms))); } } return; } base.WndProc(ref m); }
To transmit data to a process, the NativeDataTransfer.Transfer static method is used which expects a handle to the target NativeReceiveDataSink and an object. The object is serialized to binary, copied to unmanaged memory and sent with the SendMessageTimeout native method:
/// <summary> /// Transfers an object to a <see cref="NativeReceiveDataSink"/>. /// </summary> /// <param name="hWnd">The <see cref="NativeReceiveDataSink.Handle"/> to send to.</param> /// <param name="graph">The object to serialize and send.</param> /// <returns>true if the object was transferred; otherwise, false if <see cref="NativeMethods.SendMessageTimeout"/> failed to send within the allotted timeout.</returns> public static bool Transfer(IntPtr hWnd, object graph) { //create the data structure: COPYDATASTRUCT data = new COPYDATASTRUCT(); //create a formatter to serialize data: BinaryFormatter formatter = new BinaryFormatter(); try { //create a stream to serialize data to: using (MemoryStream stream = new MemoryStream()) { //serialize the object: formatter.Serialize(stream, graph); //set data length: data.cbData = (int)stream.Length; //allocate unmanaged array: data.lpData = NativeMethods.LocalAlloc(Runtime.InteropServices.Enums.LocalAllocFlags.LPTR, new IntPtr(data.cbData)); //copy data from the managed array to unmanaged array: Marshal.Copy(stream.ToArray(), 0, data.lpData, data.cbData); } IntPtr result; //send and wait: return NativeMethods.SendMessageTimeout(hWnd, WindowsMessages.WM_COPYDATA, IntPtr.Zero, ref data, SendMessageTimeoutFlags.SMTO_NOTIMEOUTIFNOTHUNG, 5000, out result) != IntPtr.Zero; } finally { //destroy the unmanaged block of memory: data.Dispose(); } }
The unmanaged memory is released after the message is received or SendMessageTimeout times out.
Using
Initialize the NativeReceiveDataSink and wire the DataReceived event to receive messages:
/// <summary> /// Defines a data sink for inter-process communication. /// </summary> NativeReceiveDataSink _Sink = new NativeReceiveDataSink(); private void MainForm_Load(object sender, EventArgs e) { //wire the data received event: _Sink.DataReceived += (obj, args) => { //cast the data received to the expected type: string message = args.DataReceived as string; //outputs message: Console.WriteLine(message); }; //log _Sink.Handle for another application }
To transmit data, use the NativeDataTransfer.Transfer static method:
NativeDataTransfer.Transfer([Handle], "Hello World!");
To access the handle for the Transfer method, use a MemoryMappedFile to share the NativeReceiveDataSink.Handle with another process.