Exploring Inter-Process Communication in WPF Using Named Pipes

In a WPF application, inter-process connectivity typically involves communication between different components or modules within the same application or between separate WPF applications running on the same system or across different systems.

There are various methods to achieve inter-process communication (IPC) in WPF.

  1. Named Pipes: Named pipes enable two processes to communicate by creating a named pipe. WPF applications can utilize named pipes to establish communication between them. .NET provides classes such as NamedPipeServerStream and NamedPipeClientStream for working with named pipes.
  2. WCF (Windows Communication Foundation): WCF is a framework designed for building service-oriented applications. WPF applications can utilize WCF to communicate with other applications or services over different transport protocols like TCP/IP, HTTP, named pipes, etc.
  3. .NET Remoting: Although considered legacy, .NET Remoting allows communication between objects in different application domains or processes. However, it is less commonly used in modern WPF applications due to the availability of newer alternatives like WCF.
  4. Message Queuing (MSMQ): Message Queuing is a technology used for sending messages between applications or components asynchronously. WPF applications can utilize MSMQ to exchange messages with other applications or services.
  5. TCP/IP Sockets: WPF applications can communicate with each other or with other applications/services using TCP/IP sockets. .NET provides classes like TcpListener and TcpClient for implementing socket-based communication.
  6. Shared Memory: WPF applications can share data through shared memory mechanisms such as memory-mapped files. This allows multiple processes to access the same memory region.
  7. .NET Events and Delegates: Within a single application domain, components or modules can communicate with each other using .NET events and delegates. Although this is not traditional inter-process communication, it enables loosely coupled communication within the same application.

Named Pipes facilitate bidirectional communication between processes by establishing a communication channel either locally on the same machine or remotely across a network.

IPC offers numerous benefits in software development

  1. Enhanced Modularity and Scalability: IPC enables the division of an application into smaller, modular components (processes or threads) that can communicate with each other. This promotes scalability by distributing tasks among multiple processes or machines, thereby improving overall performance.
  2. Concurrent Execution and Parallelism: IPC allows for the concurrent execution of tasks by enabling multiple processes or threads to run simultaneously and communicate with each other. This facilitates parallel processing, resulting in improved throughput and reduced execution time for computationally intensive tasks.
  3. Improved Separation of Concerns: IPC encourages a modular and decoupled design, promoting the separation of concerns. Different components of an application can focus on specific tasks, leading to cleaner and more maintainable code.
  4. Fault Isolation and Reliability: By isolating different components of an application into separate processes or threads, IPC helps contain faults and failures. If one component crashes or encounters an error, it does not necessarily impact the entire application, thereby enhancing reliability and fault tolerance.
  5. Platform Independence: IPC mechanisms are often platform-independent, allowing communication between processes running on different operating systems or hardware architectures. This fosters interoperability and portability of applications across diverse environments.
  6. Enhanced Interoperability: IPC facilitates communication between different applications or software components developed using various programming languages or technologies. This promotes interoperability and integration between heterogeneous systems.
  7. Efficient Resource Sharing: IPC enables processes to efficiently share resources such as memory, files, and hardware devices. This promotes collaboration and optimization of resource utilization in multi-process or multi-threaded environments.
  8. Distributed Computing: IPC plays a crucial role in enabling distributed computing, where tasks are distributed across multiple machines or nodes. This allows for the efficient utilization of resources and improved performance in distributed systems.

Implementation

I have incorporated an IPC process within the WPF application. To successfully implement this, you must adhere to the subsequent steps.

Step 1. Develop a project similar to the one outlined below.

 Develop a project

Step 2. To facilitate collaboration between the "Server" and "Client", a shared project needs to be established. In this scenario, the Server acts as the receiver while the Client functions as the sender. To address this requirement, I have developed a project called "IPCCommonHelper". Its purpose is to enable seamless communication and cooperation between the Server and Client entities.

using System;
using System.IO.Pipes;
using System.Text;
using System.Threading;
using System.Windows;
using static System.Net.Mime.MediaTypeNames;

namespace IPCCommonHelper
{
    public class ClientServerCommonHelper
    {
        #region Fields declaration
        public AutoResetEvent autoProcessRequest;
        private bool isIPCServerRunning { get; set; }
        private NamedPipeServerStream _pipeServerStream = null;
        private int BytesCount = 10000;
        public string receivedString { get; set; }
        private object objReceivedDataLock = new object();
        #endregion

        #region Events
        public static event EventHandler<MessageReceived> TriggerReceivedMessage = null;

        // Method to trigger the event
        public static void ReceiveMessage(string message)
        {
            // Some message processing logic...
            // Raise the event
            if (!string.IsNullOrEmpty(message))
            {
                MessageReceived eventArgs = new MessageReceived { ReceivedData = message };
                OnTriggerReceivedMessage(eventArgs);
            }
        }

        // Method to raise the event
        private static void OnTriggerReceivedMessage(MessageReceived e)
        {
            // Verify if there are any attendees registered for the event.
            if (TriggerReceivedMessage != null)
            {
                // Raise the event
                TriggerReceivedMessage.Invoke(null, e);
            }
        }
        #endregion

        #region Singleton instance
        private static readonly object objLock = new object();
        private static ClientServerCommonHelper instance = null;
        public static ClientServerCommonHelper Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (objLock)
                    {
                        if (instance == null)
                        {
                            instance = new ClientServerCommonHelper();
                        }
                    }
                }
                return instance;
            }
        }
        #endregion

        public ClientServerCommonHelper()
        {
            isIPCServerRunning = false;
            autoProcessRequest = new AutoResetEvent(false);
        }

        public void StartServer()
        {
            try
            {
                _pipeServerStream = new NamedPipeServerStream(CommonConstant.NamedPipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
                _pipeServerStream.BeginWaitForConnection(new AsyncCallback(CallBackWaitingHandler), _pipeServerStream);
                isIPCServerRunning = true;
                Thread thread = new Thread(ReceiveDataProcessHelper);
                thread.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void ReceiveDataProcessHelper(object? obj)
        {
            try
            {
                while (isIPCServerRunning)
                {
                    if (autoProcessRequest.WaitOne())
                    {
                        autoProcessRequest.Reset();
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void CallBackWaitingHandler(IAsyncResult ar)
        {
            try
            {
                NamedPipeServerStream pipeServerStream = (NamedPipeServerStream)ar.AsyncState;
                pipeServerStream.EndWaitForConnection(ar);
                byte[] byteArray = new byte[BytesCount];
                pipeServerStream.Read(byteArray, 0, BytesCount);
                // Transform byte array into a string.
                string text = Encoding.UTF8.GetString(byteArray);
                lock (objReceivedDataLock)
                {
                    // receivedString = text;
                    ReceiveMessage(text);
                    autoProcessRequest.Set();
                }
                // Terminate all running processes.
                pipeServerStream.Close();
                pipeServerStream = null;
                pipeServerStream = new NamedPipeServerStream(CommonConstant.NamedPipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
                pipeServerStream.BeginWaitForConnection(new AsyncCallback(CallBackWaitingHandler), pipeServerStream);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        #region Transfer information via Inter-Process Communication.
        public void TransmitDataToIPCServerExecutable(string data)
        {
            {
                try
                {
                    NamedPipeClientStream pipeStreamClient = new NamedPipeClientStream(".", CommonConstant.NamedPipeName, PipeDirection.Out, PipeOptions.Asynchronous);
                    SelfTestUnsuccessfulImplementationProcedure(10, TimeSpan.FromMilliseconds(400), () =>
                    {
                        pipeStreamClient.Connect(10000);
                    });
                    if (!pipeStreamClient.IsConnected)
                    {
                        return;
                    }
                    if (!pipeStreamClient.CanWrite)
                    {
                        return;
                    }
                    byte[] byteArray = Encoding.UTF8.GetBytes(data);
                    IAsyncResult writeResult = pipeStreamClient.BeginWrite(byteArray, 0, byteArray.Length, ResponseMessageSent, pipeStreamClient);
                    writeResult.AsyncWaitHandle.WaitOne();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
        }

        private void ResponseMessageSent(IAsyncResult iar)
        {
            try
            {
                NamedPipeClientStream clientPipeStream = (NamedPipeClientStream)iar.AsyncState;
                clientPipeStream.EndWrite(iar);
                clientPipeStream.Flush();
                clientPipeStream.Close();
                clientPipeStream.Dispose();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        public void SelfTestUnsuccessfulImplementationProcedure(int maximumCounter, TimeSpan delay, Action actionProcess)
        {
            try
            {
                var counter = 0;
                do
                {
                    try
                    {
                        counter++;
                        actionProcess();
                        break;
                    }
                    catch (Exception ex)
                    {
                        if (counter == maximumCounter)
                        {
                            return;
                        }
                        Task.Delay(delay).Wait();
                    }
                } while (true);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        #endregion
    }

    public class MessageReceived : EventArgs
    {
        public string ReceivedData { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IPCCommonHelper
{
    public class CommonConstant
    {
        //The communication between the client and server is facilitated through a pipe channel.
        public const string NamedPipeName = "PIPECHANNELCLIENTSERVER";
    }
}

Step 3. Develop a server that will receive messages from two distinct clients.

XAML View

<Window x:Class="IPCServerExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:IPCServerExample"
        mc:Ignorable="d"
        Title="RECEIVER EXE" Height="450" Width="800">
    <Grid>
        <StackPanel Orientation="Vertical">
            <!--<Button x:Name="BtnIPCServer" Content="IPC RECEIVER EXE" Height="41" Margin="10,50,10,0" Click="BtnIPCServer_Click"/>-->
            <TextBox BorderBrush="DarkGray" FontSize="20" FontWeight="Bold" BorderThickness="2" x:Name="TxtBlckResult" MinHeight="300" Margin="10,50,10,0" IsReadOnly="True" Foreground="Green" AcceptsReturn="True" Height="309"/>
        </StackPanel>
    </Grid>
</Window>

Code Behind

using IPCCommonHelper;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Windows;

namespace IPCServerExample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Fields declaration
        ClientServerCommonHelper ClientServerCommonHelper = ClientServerCommonHelper.Instance;
        #endregion

        StringBuilder sb;

        public MainWindow()
        {
            InitializeComponent();
            sb = new StringBuilder();
            ClientServerCommonHelper.StartServer();
            ClientServerCommonHelper.TriggerReceivedMessage += ClientServerCommonHelper_TriggerReceivedMessage;
        }

        private void ClientServerCommonHelper_TriggerReceivedMessage(object? sender, MessageReceived e)
        {
            if (e != null)
            {
                Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    TxtBlckResult.Text = sb.Append("Message Received from :" + e.ReceivedData).ToString();
                }));
            }
        }
    }
}

Receiver

Step 4. Below are two distinct clients that have been created.

First Client XAML View

<Window x:Class="IPCClientExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:IPCClientExample"
        mc:Ignorable="d"
        Title="FIRST SENDER EXE" Height="450" Width="800">
    <Grid>
        <StackPanel Orientation="Vertical">
            <Button x:Name="BtnClient" Content="FIRST IPC INFORMATION SENDER TO EXE" Height="41" Margin="10,50,10,0" Click="BtnClient_Click"/>
            <TextBox x:Name="TxtBoxResult" Height="100" FontSize="20" FontWeight="Bold" Margin="10,50,10,0" IsReadOnly="False" Foreground="Blue"/>
        </StackPanel>
    </Grid>
</Window>

Code Behind

using IPCCommonHelper;
using System.Windows;

namespace IPCClientExample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Fields declaration
        ClientServerCommonHelper ClientServerCommonHelper = ClientServerCommonHelper.Instance;
        #endregion

        public MainWindow()
        {
            InitializeComponent();
        }

        private void BtnClient_Click(object sender, RoutedEventArgs e)
        {
            if (!string.IsNullOrEmpty(TxtBoxResult.Text))
            {
                ClientServerCommonHelper.TransmitDataToIPCServerExecutable(TxtBoxResult.Text);
            }
        }
    }
}

First sender

Second Client XAML View

<Window x:Class="SECONDEXEIPCEXAMPLE.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SECONDEXEIPCEXAMPLE"
        mc:Ignorable="d"
        Title="SECOND IPC SENDER EXE" Height="450" Width="800">
    <StackPanel Orientation="Vertical">
        <Button x:Name="BtnClient" Content="SECOND IPC INFORMATION SENDER TO EXE" Height="41" Margin="10,50,10,0" Click="BtnClient_Click"/>
        <TextBox x:Name="TxtBoxResult" FontSize="20" FontWeight="Bold" Height="100" Margin="10,50,10,0"  Foreground="Red"/>
    </StackPanel>
</Window>

Code Behind

using System.IO.Pipes;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using IPCCommonHelper;

namespace SECONDEXEIPCEXAMPLE
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Fields declaration
        ClientServerCommonHelper ClientServerCommonHelper = ClientServerCommonHelper.Instance;
        StringBuilder sb;
        #endregion

        public MainWindow()
        {
            InitializeComponent();
            sb = new StringBuilder();
        }

        private void BtnClient_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (ClientServerCommonHelper != null) {
                    if (!String.IsNullOrEmpty(TxtBoxResult.Text))
                    {
                        ClientServerCommonHelper.TransmitDataToIPCServerExecutable(TxtBoxResult.Text);
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
    }
}

Second IPC

Step 5. Visual Presentation in Operation.

Visual Presentation

Repository Path: https://github.com/OmatrixTech/IPCConnectivityProcess