Welcome to CSharp Labs

Interprocess Communication using Native Methods

Thursday, July 11, 2013

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.

Source Code

Download NativeReceivedataSink and Supporting Classes

Comments