Click here to Skip to main content
14,365,424 members

How to Send Data from One Process to Another in C#

Rate this:
4.68 (18 votes)
Please Sign up or sign in to vote.
4.68 (18 votes)
11 Aug 2015CPOL
This tip uses SendMessage and WM_COPYDATA to transfer the data.

Introduction

Sometimes, you need to send data between two processes. One traditional way to do this has been to use SendMessage with the message WM_COPYDATA passing a COPYDATASTRUCT as a parameter.

From Windows Vista, the message WM_COPYDATA is blocked by the User Interface Privilege Isolation (UIPI) message filter... This tip shows how to create the necessary code and data structures and allow the message to come through.

Background

Just recently, I wrote a small application that is used to print front and back of an ID card on the same paper. After a bit of testing of how to get the scanned pages into the application and also make it as user friendly as possible, I opted to use the AutoScan button of the scanner, a CanoScan 9000F. This scanner can be configured to start any application when scan has been executed. The application is started and the filename of the scanned image is passed as a command line argument.

This works well, except the scanner driver launches one instance of the application per press of the button, and this was not what I wanted.

The first problem to solve was to prevent the new instance from running and the next problem was to send the new filename to the running instance. This was more work than I expected.

Using the Code

The example code is written as a Windows Forms application. The only thing the application does is to take a command line argument and add it to a RichTextBox.

Note

I have only tested this application using Windows 8.1 and .NET 4.5

Platform Invoke

As some platform invoke (pinvoke) technology is involved, I will start with referring to the site pinvoke.net. Here, you can find many C# signatures for Win32 methods.

In order to keep the pinvoke stuff in one place, I created a helper class called MessageHandler which is shown below:

using System;
using System.Runtime.InteropServices;

namespace SendMessageDemo
{
    internal static class NativeMethods
    {
        /// <summary>
        /// Retrieves a handle to the top-level window whose class name and window name match 
        /// the specified strings.
        /// This function does not search child windows.
        /// This function does not perform a case-sensitive search.
        /// </summary>
        /// <param name="lpClassName">If lpClassName is null, it finds any window whose title matches
        /// the lpWindowName parameter.</param>
        /// <param name="lpWindowName">The window name (the window's title). If this parameter is null,
        /// all window names match.</param>
        /// <returns>If the function succeeds, the return value is a handle to the window 
        /// that has the specified class name and window name.</returns>
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        /// <summary>
        /// Handle used to send the message to all windows
        /// </summary>
        public static IntPtr HWND_BROADCAST = new IntPtr(0xffff);

        /// <summary>
        /// An application sends the WM_COPYDATA message to pass data to another application.
        /// </summary>
        public static uint WM_COPYDATA = 0x004A;

        /// <summary>
        /// Contains data to be passed to another application by the WM_COPYDATA message.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct COPYDATASTRUCT
        {
            /// <summary>
            /// User defined data to be passed to the receiving application.
            /// </summary>
            public IntPtr dwData;

            /// <summary>
            /// The size, in bytes, of the data pointed to by the lpData member.
            /// </summary>
            public int cbData;

            /// <summary>
            /// The data to be passed to the receiving application. This member can be IntPtr.Zero.
            /// </summary>
            public IntPtr lpData;
        }

        /// <summary>
        /// Sends the specified message to a window or windows.
        /// </summary>
        /// <param name="hWnd">A handle to the window whose window procedure will receive the message.
        /// If this parameter is HWND_BROADCAST ((HWND)0xffff), the message is sent to all top-level
        /// windows in the system.</param>
        /// <param name="Msg">The message to be sent.</param>
        /// <param name="wParam">Additional message-specific information.</param>
        /// <param name="lParam">Additional message-specific information.</param>
        /// <returns>The return value specifies the result of the message processing; 
        /// it depends on the message sent.</returns>
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

        /// <summary>
        /// Values used in the struct CHANGEFILTERSTRUCT
        /// </summary>
        public enum MessageFilterInfo : uint
        {
            /// <summary>
            /// Certain messages whose value is smaller than WM_USER are required to pass 
            /// through the filter, regardless of the filter setting. 
            /// There will be no effect when you attempt to use this function to 
            /// allow or block such messages.
            /// </summary>
            None = 0,

            /// <summary>
            /// The message has already been allowed by this window's message filter, 
            /// and the function thus succeeded with no change to the window's message filter. 
            /// Applies to MSGFLT_ALLOW.
            /// </summary>
            AlreadyAllowed = 1,

            /// <summary>
            /// The message has already been blocked by this window's message filter, 
            /// and the function thus succeeded with no change to the window's message filter. 
            /// Applies to MSGFLT_DISALLOW.
            /// </summary>
            AlreadyDisAllowed = 2,

            /// <summary>
            /// The message is allowed at a scope higher than the window.
            /// Applies to MSGFLT_DISALLOW.
            /// </summary>
            AllowedHigher = 3
        }

        /// <summary>
        /// Values used by ChangeWindowMessageFilterEx
        /// </summary>
        public enum ChangeWindowMessageFilterExAction : uint
        {
            /// <summary>
            /// Resets the window message filter for hWnd to the default.
            /// Any message allowed globally or process-wide will get through,
            /// but any message not included in those two categories,
            /// and which comes from a lower privileged process, will be blocked.
            /// </summary>
            Reset = 0,

            /// <summary>
            /// Allows the message through the filter. 
            /// This enables the message to be received by hWnd, 
            /// regardless of the source of the message, 
            /// even it comes from a lower privileged process.
            /// </summary>
            Allow = 1,

            /// <summary>
            /// Blocks the message to be delivered to hWnd if it comes from
            /// a lower privileged process, unless the message is allowed process-wide 
            /// by using the ChangeWindowMessageFilter function or globally.
            /// </summary>
            DisAllow = 2
        }

        /// <summary>
        /// Contains extended result information obtained by calling 
        /// the ChangeWindowMessageFilterEx function.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct CHANGEFILTERSTRUCT
        {
            /// <summary>
            /// The size of the structure, in bytes. Must be set to sizeof(CHANGEFILTERSTRUCT), 
            /// otherwise the function fails with ERROR_INVALID_PARAMETER.
            /// </summary>
            public uint size;

            /// <summary>
            /// If the function succeeds, this field contains one of the following values, 
            /// <see cref="MessageFilterInfo"/>
            /// </summary>
            public MessageFilterInfo info;
        }

        /// <summary>
        /// Modifies the User Interface Privilege Isolation (UIPI) message filter for a specified window
        /// </summary>
        /// <param name="hWnd">
        /// A handle to the window whose UIPI message filter is to be modified.</param>
        /// <param name="msg">The message that the message filter allows through or blocks.</param>
        /// <param name="action">The action to be performed, and can take one of the following values
        /// <see cref="MessageFilterInfo"/></param>
        /// <param name="changeInfo">Optional pointer to a 
        /// <see cref="CHANGEFILTERSTRUCT"/> structure.</param>
        /// <returns>If the function succeeds, it returns TRUE; otherwise, it returns FALSE. 
        /// To get extended error information, call GetLastError.</returns>
        [DllImport("user32.dll", SetLastError = true)]
        public static extern bool ChangeWindowMessageFilterEx(IntPtr hWnd, uint msg, 
        ChangeWindowMessageFilterExAction action, ref CHANGEFILTERSTRUCT changeInfo);
    }
}

Good old MSDN is filled with information about the methods used herein, so here is a list of references to the different methods and structures.

I strongly recommend you read the information on MSDN if you run into trouble.

One Instance Application

In my case, I needed to make sure that only one instance of the application was launched and after some searching, the solution was to use the Mutex class, and more specifically once named Mutex.

I also needed to get the filename sent to the application, so I added code for that too. Below is the modified main function in the file Program.cs.

[STAThread]
static void Main(string[] args)
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    // Get the filename if it exists
    string fileName = null;
    if (args.Length == 1)
       fileName = args[0];

    // If a mutex with the name below already exists, 
    // one instance of the application is already running 
    bool isNewInstance;
    Mutex singleMutex = new Mutex(true, "MyAppMutex", out isNewInstance);
    if (isNewInstance)
    {
        // Start the form with the file name as a parameter
        Form1 frm = new Form1(fileName);
        Application.Run(frm);
    }
    else
    {
        // Nothing to do here yet, but we can show a message box
        MessageBox.Show(fileName, "Received File Name", 
			MessageBoxButtons.OK, MessageBoxIcon.Information);
    }
}

So that took care of the first problem.

Transferring the File Name

The next problem to solve was how to get the file name of the second scanned image into the running application. There are quite a few ways to solve this, for example named pipes, sockets or writing data to a file or the registry. In my opinion, this creates a lot of overhead for the simple task of sending a string from one application to another, so I wanted something more straightforward.

I started out by using SendMessage and my own defined message, but ran into various memory access violations (of course). This is because SendMessage only passes a pointer to a memory location. However, the pointer doesn't point to a physical memory location, but a virtual and two applications will have two different virtual memory spaces. The result is that the pointer will have no useful meaning for the receiving application. (Before I have mostly used SendMessage within in the same application where this is not an issue)

The solution is to use the message WM_COPYDATA as this message is designed to transfer data between processes. One caveat for this message is that on Windows Vista and later, it is blocked by default by the User Interface Privilege Isolation (UIPI) message filter. (I have only tested this application on Windows 8.1)

Enabling WM_COPYDATA

It is simple enough to unblock the message with the code below:

private void Form1_Load(object sender, EventArgs e)
{
    NativeMethods.CHANGEFILTERSTRUCT changeFilter = new NativeMethods.CHANGEFILTERSTRUCT();
    changeFilter.size = (uint)Marshal.SizeOf(changeFilter);
    changeFilter.info = 0;
    if (!NativeMethods.ChangeWindowMessageFilterEx
	(this.Handle, NativeMethods.WM_COPYDATA, 
	NativeMethods.ChangeWindowMessageFilterExAction.Allow, ref changeFilter))
    {
        int error = Marshal.GetLastWin32Error();
        MessageBox.Show(String.Format("The error {0} occurred.", error));
    }
}

The most likely error message would be ERROR_ACCESS_DENIED if the process security level is too low.

Sending the Message

In order to send the message, a handle to the existing window has to be available. This is done with the method FindWindow using the title of the window as the parameter.

We also have to create an instance of the COPYDATASTRUCT structure that will be used to hold the data to send.

  • The member dwData can, for example, be used to identify the type of data sent.
  • The member cbData contains the length of the data in lpData.
  • The member lpData is a pointer to the data. In this case, a string.

Below is the finalized else part of the main function in Program.cs.

else
{
    string windowTitle = "SendMessage Demo";
    // Find the window with the name of the main form
    IntPtr ptrWnd = MessageHandler.FindWindow(null, windowTitle);
    if (ptrWnd == IntPtr.Zero)
    {
        MessageBox.Show(String.Format("No window found with the title {0}.", windowTitle),
        "SendMessage Demo", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    else
    {
        IntPtr ptrCopyData = IntPtr.Zero;
        try
        {
            // Create the data structure and fill with data
            NativeMethods.COPYDATASTRUCT copyData = new NativeMethods.COPYDATASTRUCT();
            copyData.dwData = new IntPtr(2);    // Just a number to identify the data type
            copyData.cbData = fileName.Length + 1;  // One extra byte for the \0 character
            copyData.lpData = Marshal.StringToHGlobalAnsi(fileName);

            // Allocate memory for the data and copy
            ptrCopyData = Marshal.AllocCoTaskMem(Marshal.SizeOf(copyData));
            Marshal.StructureToPtr(copyData, ptrCopyData, false);

            // Send the message
            NativeMethods.SendMessage(ptrWnd, MessageHandler.WM_COPYDATA, IntPtr.Zero, ptrCopyData);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString(), "SendMessage Demo",
        MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
        finally
        {
            // Free the allocated memory after the control has been returned
            if (ptrCopyData != IntPtr.Zero)
                Marshal.FreeCoTaskMem(ptrCopyData);
        }
    }
}

Implementing the Message Receiver

The last step is to implement the code used to receive the message and the data. This is done by overriding the method WndProc in the Form class.

This code is simple enough when just sending a string.

protected override void WndProc(ref Message m)
{
    if (m.Msg == MessageHandler.WM_COPYDATA)
    {
        // Extract the file name
        NativeMethods.COPYDATASTRUCT copyData = 
        (NativeMethods.COPYDATASTRUCT)Marshal.PtrToStructure
		(m.LParam, typeof(NativeMethods.COPYDATASTRUCT));
        int dataType = (int)copyData.dwData;
        if (dataType == 2)
        {
            string fileName = Marshal.PtrToStringAnsi(copyData.lpData);

            // Add the file name to the edit box
            richTextBox1.AppendText(fileName);
            richTextBox1.AppendText("\r\n");
        }
        else
        {
            MessageBox.Show(String.Format("Unrecognized data type = {0}.", 
            dataType), "SendMessageDemo", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
    else
    {
        base.WndProc(ref m);
    }
}

The receiving application should NOT free any memory. This should be done by the sender.

Points of Interest

In my case, I only needed to send a string, but it is of course possible to send more complex data as well. In order to do this, you can create a structure in the same fashion as the COPYDATASTRUCT structure and use Marshal.StructureToPtr on the sending side and Marshal.PtrToStructure on the receiving side.

Mike Barthold pointed out that I didn't follow Microsoft Code Compliance rules regarding the class containing the native methods. See CA1060: Move P/Invokes to NativeMethods class. It is good to learn something new everyday.

History

  • 2015-08-11 First release
  • 2015-08-12 Second release. Changed the naming convention of the class containing P/Invoke methods to NativeMethods
  • 2015-08-12 Two bugs fixed in Program.cs:
    1. if (ptrWnd == null) corrected to if (ptrWnd == IntPtr.Zero)
    2. String.Format("No window found with the title", windowTitle)

      corrected to

      String.Format("No window found with the title '{0}'.", windowTitle)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

George Jonsson
Architect
Philippines Philippines
I have worked with mobile phone testing in manufacturing for 20 years.
Originally from Sweden, but moved to China in 2012.
At the moment I am on a sabbatical year in the Philippines.

Over the years I have used various programming languages such as Pascal, C, C++, C#, SQL, XML, XSLT.

Comments and Discussions

 
SuggestionA very helpful article Pin
BinaryReason7-Jan-19 4:56
memberBinaryReason7-Jan-19 4:56 
SuggestionFindWindow suggestion Pin
EminST2-Oct-18 4:29
memberEminST2-Oct-18 4:29 
GeneralMy vote of 4 Pin
Dmitriy Gakh3-Sep-15 2:23
professionalDmitriy Gakh3-Sep-15 2:23 
QuestionSelf Host WCF Service Pin
Joel Palmer12-Aug-15 12:26
memberJoel Palmer12-Aug-15 12:26 
AnswerRe: Self Host WCF Service Pin
George Jonsson12-Aug-15 20:37
professionalGeorge Jonsson12-Aug-15 20:37 
GeneralRe: Self Host WCF Service Pin
Joel Palmer13-Aug-15 8:13
memberJoel Palmer13-Aug-15 8:13 
GeneralRe: Self Host WCF Service Pin
George Jonsson13-Aug-15 20:43
professionalGeorge Jonsson13-Aug-15 20:43 
QuestionWhy go through all this? Pin
ArchAngel12312-Aug-15 9:20
memberArchAngel12312-Aug-15 9:20 
AnswerRe: Why go through all this? Pin
George Jonsson12-Aug-15 20:42
professionalGeorge Jonsson12-Aug-15 20:42 
BugTwo bugs detected Pin
George Jonsson11-Aug-15 22:50
professionalGeorge Jonsson11-Aug-15 22:50 
GeneralMy vote of 3 Pin
Mike Barthold11-Aug-15 10:58
professionalMike Barthold11-Aug-15 10:58 
GeneralRe: My vote of 3 Pin
George Jonsson11-Aug-15 16:25
professionalGeorge Jonsson11-Aug-15 16:25 
QuestionNice but a simpler alternative you might want to concider Pin
aSarafian11-Aug-15 9:54
memberaSarafian11-Aug-15 9:54 
AnswerRe: Nice but a simpler alternative you might want to concider Pin
George Jonsson11-Aug-15 16:26
professionalGeorge Jonsson11-Aug-15 16:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Tip/Trick
Posted 11 Aug 2015

Stats

43.6K views
1.5K downloads
38 bookmarked