Update 07/2009: Update to not use HTTP transport methodology but NetNamedPipeBinding. Updated downloadable code

Update 10/2008: Code downloadable…see comments for link.
A named pipe is a communications method between two disparate applications. This article will show how to use WCF and .Net 3+ with C# to create such named pipe(s). This article is a how-to article showing every step needed and has the code downloadable.
Premise
I have a need to have one application to provide a messages to another application (InterProcess Communication (IPC)) using a unique named pipe between them using WCF.

Named Pipe Library

We will be creating three projects. The first will be a library project (shown below as NamedPipe) and two test projects (not shown in the picture below). The test projects will be the consumers of the library which will be a receiver and a sender talking to each other. The Library will contain the guts of the operations in WCF and once done will look like this in VS2008 Solution Explorer:
image
This library will be all encompassing and contain the code necessary to perform the named pipe between applications. It handles all the core WCF functionality setup/endpoints. All one has to do is have the receiver and sender use the library to communicate by just specifying a name for the pipe.
The receiver and sender will simply consume the classes in Receiver.cs and Sender.cs respectively to achieve the named pipe communication.
Steps:
  1. Create a library project named NamedPipe.
  2. Add two references to the System namespaces Runtime.Serialization and ServiceModel.
  3. Create a folder within the project named Communication, which will be a sub-namespace to NamedPipe. We will place the low level operations and specifications there.
  4. Within the Communication folder add an interface class named IPipeService (as iPipeService.cs in the downloadable code). Within that file is where we will begin to define the service contract which the sending and receive code will adhere to. The interface code will look like this:
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ServiceModel;
    
    namespace NamedPipe.Communication
    {
     [ServiceContract
      (Namespace =
      "http://www.OmegaCoder.com/NamedPipe1")]
     public interface IPipeService
     {
      [OperationContract]
      void PipeIn(List<string> data);
     }
    }
    • Line 04: We will need to add this using so our attributes will be recognized on the interface.
    • Line 08: This is the attribute which will show our interface to be a specialized WCF contract.
    • Line 10: We setup a generic namespace to avoid naming collisions with other WCF services. This namespace does not have anything to do with opening up the ports, which will have a different URL altogether. This url is kind of a throw away url, we are not publishing to this location.
    • Line 13: We will be opening up one port which we will communicate between the processes and this attribute defines that.
    • Line 14: We will be passing a list of strings between the processes and this method is the vector of the operation.
  5. Again within the Communication folder create the second part of the contract, the actual class which will be the catalyst for the data transfer on the receiving side named PipeService (as PipeService.cs in the download code). Let me show the code and explain each line:
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ServiceModel;
    
    namespace NamedPipe.Communication
    {
    
    [ServiceBehavior
            (InstanceContextMode = InstanceContextMode.Single)]
     public class PipeService : IPipeService
     {
         public static string URI
             = "net.pipe://localhost/Pipe";
    
         // This is when we used the HTTP bindings.
         // = "http://localhost:8000/Pipe";
    
         #region IPipeService Members
    
         public void PipeIn(List<string> data)
         {
             if (DataReady != null)
                 DataReady(data);
         }
    
         public delegate void DataIsReady(List<string> hotData);
         public DataIsReady DataReady = null;
    
         #endregion
     }
    }
    • Line 04: This is the required using for WCF operations.
    • Line 09: This attribute is self explanatory, for this class will be generated for each endpoint we define. In our case only one endpoint for our pipe.
    • Line 10: This is not the default behavior. In standard WCF processing this object would be created through reflection. We don’t want that to happen, because we have a delegate (call back) which will transfer data to the receiver. Due to that fact, we will be creating the object by hand later and allowing the consumer to subscribe to the delegate. Because of that, we have to specify that this context will be a singleton instance.
    • Line 14: Here is the actual url and port which the operations will be operating on. The named part of the port will be added to this string to give us a unique pipe.  If we want to expand this to other machines on the intranet, we could use an HTTP protocol as shown below in Line 17.
    • Line 20: Here is where the data will be passed in/out.
    • Line 22: Check for a subscriber to the delegate.
    • Line 23: Finally pass the data on to the consumer(s) of the delegate after we have received data from the ether.
    • Line 25/26: Here is where we will transfer the data back and forth by allowing a consumer to subscribe to this delegate.

Receiver Class

  1. Now we will create the top level receiver class (as Receiver.cs in the zip) which will be consumed by the target of the named pipe. That class is created in the top level of the NamedPipe namespace. Here is the code and the line by line explanation.
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using NamedPipe.Communication;

namespace NamedPipe
{
 public class Receiver :  IDisposable
 {

     #region Constructors
     // Default constructor, use this if
     // the default name of the pipe can
     // be used instead of one provided.
     public Receiver() : this(DefaultPipeName) { }

     // Use this constructor to create
     // a pipe with a unique name.
     public Receiver(string PipeName)
     {
         _PipeName = PipeName;
     }
#endregion

     #region Public Operations
     // If the service host can be started,
     // true will be returned. If false is returned
     // another process owns the pipe.
     public bool ServiceOn()
     {
         return ( _operational = HostThisService());
     }

     public void ServiceOff()
     {
      if (_host != null)
          if (_host.State != CommunicationState.Closed)
              _host.Close();

      _operational = false;

     }
#endregion

     #region WCF Operations
    // The thread which will listen for communications
    // from the other side of the pipe.
    private bool HostThisService()
    {

     try
     {

         _host = new ServiceHost(_ps, new Uri(PipeService.URI));

         // Usage BasicHttpBinding can be used if this is
         // not going to be on the local machine.
         _host.AddServiceEndpoint( typeof( IPipeService ),
              new NetNamedPipeBinding(),
              _PipeName );
         _host.Open();

         _operational = true;
     }
     catch (Exception ex)
     {
         error = ex;
         _operational = false;
     }

     return _operational;

    }
#endregion

    #region Public Properties
    // The consumer of this class will subscribe to this delegate to
    // receive data in a callback fashion.
    public PipeService.DataIsReady Data
    {
        get
        {
           return _ps.DataReady;
        }

        set
        {
           _ps.DataReady += value;
        }
    }

    public string CurrentPipeName
    {
        get { return _PipeName; }
    }

    // Any error text will be placed here.
    public Exception error              = null;

    // See the actual name of the pipe for
    // any default operations.
    public const string DefaultPipeName = "Pipe1";
#endregion

#region Private Variables
    private PipeService _ps   = new PipeService();
    private bool _operational = false;
    private ServiceHost _host = null;
    private string _PipeName  = string.Empty;
#endregion

#region IDisposable Members

    // A basic dispose.
    public void Dispose()
    {
        this.ServiceOff();

        if (_ps != null)
            _ps = null;
    }

 #endregion
   }
}
  • Line 04: Even though we have lower level WCF definitions, we will be initiating the WCF functionality in this class.
  • Line 05: Pull in the lower level WCF contract and operations.
  • Line 16: If you know that this is the only named pipe on the system, use a default name for the pipe.
  • Line 20: Otherwise use this constructor to create a named pipe to your specifications.
  • Line 30: Call this after creating the object and after subscribing to the delegate, to start the process of listening. Don’t forget to check for a running status. (Note if the pipe is in use it will throw an exception, so catch any exceptions).
  • Line 35: Turn service off, or it will be done in the dispose automatically.
  • Line 49: This is the function which will do the magic of the WCF port processing.
  • Line 55: The service host will not be creating a generic end point. It will use our singleton pipe service.
  • Line 58: We will open up this URI which is specified in the URI static of PipeService.
  • Line 59: We will have one endpoint and we let it know what type it is.
  • Line 60: We are doing not http communications but netnamed pipe operations.
  • Line 62: Here is the uniqueness of this endpoint. The pipe name.
  • Line 62: Finally we begin listening operations.
  • Line 68: Any errors will be on the public error object for the consumer to check.
  • Line 85: Subscribe to this delegate to pass the information back to the creator of this receiver named pipe.
  • Line 99: Check here for any errors.
  • Line 103: This is the default name of the pipe, if one is not given by the consumer.
  • Line 107: Here we create our singleton WCF service for the one endpoint we will specify.
  • Line 115: Clean up any operations and member variables.

Sender Class

    1. Here is the class which will be consumed by the application which will send the information over. Note this class is not instantiated but used by simply calling the static which will send the data on over:
using System;
using System.Collections.Generic;
using System.Text;

using System.ServiceModel;
using NamedPipe.Communication;

namespace NamedPipe
{
// Create this class to send a message on a
// named pipe.
public class Sender
{
    // Use a default pipe name. Be careful not to reuse
    // this on other applications.
    public static void SendMessage(List<string> messages)
    {
        SendMessage(messages, Receiver.DefaultPipeName);

    }

    // Use this method when we have an actual pipe name.
    public static void SendMessage(List<string> messages, string PipeName)
    {
        EndpointAddress ep
            = new EndpointAddress(
                string.Format("{0}/{1}",
                   PipeService.URI,
                   PipeName));

//      IPipeService proxy = ChannelFactory<IPipeService>.CreateChannel( new BasicHttpBinding(), ep );
        IPipeService proxy = ChannelFactory<IPipeService>.CreateChannel( new NetNamedPipeBinding(), ep );
        proxy.PipeIn( messages );

    }
}
}
  • Line 05: Need to pull in the WCF information to initiate the message.
  • Line 06: We have all the connection contract information which we will need to initiate a message.
  • Line 16: The class and method is a static class so just call this static. No muss no fuss. Note this uses the default named pipe name. If our receiver has specified a name for the pipe do not call this one.
  • Line 21: Call this method with a unique name in for the pipe so a message to us can be sent.
  • Line 23: We will combine the URL and port with the name of the pipe which gives us the uniqueness.
  • Line 29: We create a proxy service to do the transfer and expose the method we need to send the info to the receiver application.
  • Line 30: We send in the list of strings and we are done!

Test Projects

Finally we will create two test projects. A winform which will be the receiver and a console application which will be the sender. All we need to do is reference the Named Pipe Library!

image

Test Receiver

  1. Create a winform project and reference the NamedPipe Project.
  2. Add a richtext box to the form. Here is the code inside:
    using NamedPipe;
    
    public partial class Form1 : Form
    {
        Receiver pipe = new Receiver();
        public Form1()
        {
            InitializeComponent();
            pipe.Data += new NamedPipe.Communication.PipeService.DataIsReady(DataBeingRecieved);
            if (pipe.ServiceOn() == false)
                MessageBox.Show(pipe.error.Message);
    
        }
    
        void DataBeingRecieved(List<string> data)
        {
            this.Invoke(new MethodInvoker(delegate()
            {
                rtb_Status.Text += string.Join(Environment.NewLine, data.ToArray());
                rtb_Status.Text += Environment.NewLine;
            }));
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
            if (pipe != null)
                rtb_Status.Text += "Not Null" + Environment.NewLine;
    
            if (pipe.error != null)
                rtb_Status.Text += pipe.error + Environment.NewLine;
    
        }
    }
  • Line 01: We pull in the Named Pipe namespace.
  • Line 05: Since we will be continuously monitoring, we will instantiate the receiver class on the class, so its lifetime is the same as that of the application.
  • Line 09: Here is where we will subscribe to the delegate which will provide us with the text sent from the sender.
  • Line 11: We have gotten an error, show the exception.
  • Line 15: Here is our subscriber target which will receive the data!
  • Line 17: Since the WCF process is on another thread, we invoke back to the GUI thread to populate the richtext box.
  • Line 19: We add the data to the rich text box!
  • Line 24: I added a button, not in the steps to test out the object. This code is optional.

Test Sender

  1. Create a console application and reference the NamedPipe project.
  2. Copy this code:
    static void Main(string[] args)
    {
        List<string> messages = new List<string>();
    
        messages.Add("Line 1");
        messages.Add("Second Line");
        try
        {
            NamedPipe.Sender.SendMessage(messages);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    
    }
  • Line 09: We simply send our message. We are using the default pipe name, so we just use the single argument SendMessage. Otherwise we would use the constructor which would allow us to send data on the unique named pipe!

Summary

With that we are finished. Run the winform receiver first. Then run the console sender. If everything has been coded you should see the text on the screen. With the NamedPipe library it is now a toolbox item which we can take with us from project to project.

image

Share