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






4.71/5 (20 votes)
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 inlpData
. - The member
lpData
is a pointer to the data. In this case, astring
.
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:
if (ptrWnd == null)
corrected toif (ptrWnd == IntPtr.Zero)
String.Format("No window found with the title", windowTitle)
corrected to
String.Format("No window found with the title '{0}'.", windowTitle)