简体   繁体   中英

C# Named Pipes without issuing commands from the Console?

I am using Named Pipes to communicate with a process. I have been able to make it work with the following code. (Original code found here : via archive.org )

class ProgramPipeTest
    {

        public void ThreadSenderStartClient(object obj)
        {
            // Ensure that we only start the client after the server has created the pipe
            ManualResetEvent SyncClientServer = (ManualResetEvent)obj;

            using (NamedPipeClientStream pipeStream = new NamedPipeClientStream(".","ToSrvPipe",PipeDirection.Out,PipeOptions.None))
            {
                // The connect function will indefinately wait for the pipe to become available
                // If that is not acceptable specify a maximum waiting time (in ms)
                pipeStream.Connect();

                Console.WriteLine("[Client] Pipe connection established");
                using (StreamWriter sw = new StreamWriter(pipeStream))
                {
                    sw.AutoFlush = true;
                    string temp;
                    Console.WriteLine("Please type a message and press [Enter], or type 'quit' to exit the program");
                    while ((temp = Console.ReadLine()) != null)
                    {
                        if (temp == "quit") break;
                        sw.WriteLine(temp);
                    }
                }
            }
        }

        public void ThreadStartReceiverClient(object obj)
        {
            // Ensure that we only start the client after the server has created the pipe
            ManualResetEvent SyncClientServer = (ManualResetEvent)obj;

            using (NamedPipeClientStream pipeStream = new NamedPipeClientStream(".", "FromSrvPipe", PipeDirection.In, PipeOptions.None))
            {
                // The connect function will indefinately wait for the pipe to become available
                // If that is not acceptable specify a maximum waiting time (in ms)
                pipeStream.Connect();

                Console.WriteLine("[ClientReceiver] Pipe connection established");

                using (StreamReader sr = new StreamReader(pipeStream))
                {
                    // Display the read text to the console
                    string temp;
                    while ((temp = sr.ReadLine()) != null)
                    {
                        Console.WriteLine("Received from server: {0}", temp);
                    }
                }
            }
        }

        static void Main(string[] args)
        {

            // To simplify debugging we are going to create just one process, and have two tasks
            // talk to each other. (Which is a bit like me sending an e-mail to my co-workers)

            ProgramPipeTest Client = new ProgramPipeTest();

            Thread ClientThread = new Thread(Client.ThreadSenderStartClient);
            Thread ReceivedThread = new Thread(Client.ThreadStartReceiverClient);

            ClientThread.Start();
            ReceivedThread.Start();


        }
    }

Everything works as intended. I am able to issue commands to my target process (audacity).

My issue is, I basically want to wrap a C# GUI around this code, but am not sure how to modify it so that the communication is done without having to use the console, as commands would be issued via the GUI or from the code.

I have tried turning the streamWriter sw into a class variable, exposing it via property and calling sw.WriteLine() with a method, but that doesn't seem to work.

So I am unsure how to encapsulate the stream back and forth nicely within an object.

I found this article which seemed like it was spot on, Using Named Pipes to Connect a GUI to a Console App in Windows , but unfortunately it does not seem to come with any code and is kind of over my head without any to refer to.

So how can I use named pipes without having to use the console to issue the commands ?

What you want to do is take the main pieces of logic which are the sender, the receiver out of that code and rewrite it into a re-usable class that can be used like a purpose-specific wrapper class.

Perhaps the code below could serve as a guideline (I have NOT checked to see if this works, it might require minor changes)

public sealed class ResponseReceivedEventArgs : EventArgs
{
    public ResponseReceivedEventArgs(string id, string response)
    {
        Id = id;
        Response = response;
    }

    public string Id
    {
        private set;
        get;
    }

    public string Response
    {
        private set;
        get;
    }
}

public delegate void ResponseReceived(object sender, ResponseReceivedEventArgs e);

public sealed class NamedPipeCommands
{
    private readonly Queue<Tuple<string, string>> _queuedCommands = new Queue<Tuple<string,string>>();
    private string _currentId;

    private readonly Thread _sender;
    private readonly Thread _receiver;

    // Equivalent to receiving a "quit" on the console
    private bool _cancelRequested; 

    // To wait till a response is received for a request and THEN proceed
    private readonly AutoResetEvent _waitForResponse = new AutoResetEvent(false);

    // Lock to modify the command queue safely
    private readonly object _commandQueueLock = new object();

    // Raise an event when a response is received
    private void RaiseResponseReceived(string id, string message)
    {
        if (ResponseReceived != null)
            ResponseReceived(this, new ResponseReceivedEventArgs(id, message));
    }

    // Add a command to queue of outgoing commands
    // Returns the id of the enqueued command
    // So the user can relate it with the corresponding response
    public string EnqueueCommand(string command)
    {
        var resultId = Guid.NewGuid().ToString();
        lock (_commandQueueLock)
        {
            _queuedCommands.Enqueue(Tuple.Create(resultId, command));
        }
        return resultId;
    }

    // Constructor. Please pass in whatever parameters the two pipes need
    // The list below may be incomplete
    public NamedPipeCommands(string servername, string pipeName)
    {
        _sender = new Thread(syncClientServer =>
        {
            // Body of thread
            var waitForResponse = (AutoResetEvent)syncClientServer;

            using (var pipeStream = new NamedPipeClientStream(servername, pipeName, PipeDirection.Out, PipeOptions.None))
            {
                pipeStream.Connect();

                using (var sw = new StreamWriter(pipeStream) { AutoFlush = true })
                    // Do this till Cancel() is called
                    while (!_cancelRequested)
                    {
                        // No commands? Keep waiting
                        // This is a tight loop, perhaps a Thread.Yield or something?
                        if (_queuedCommands.Count == 0)
                            continue;

                        Tuple<string, string> _currentCommand = null;

                        // We're going to modify the command queue, lock it
                        lock (_commandQueueLock)
                            // Check to see if someone else stole our command
                            // before we got here
                            if (_queuedCommands.Count > 0)
                                _currentCommand = _queuedCommands.Dequeue();

                        // Was a command dequeued above?
                        if (_currentCommand != null)
                        {
                            _currentId = _currentCommand.Item1;
                            sw.WriteLine(_currentCommand.Item2);

                            // Wait for the response to this command
                            waitForResponse.WaitOne();
                        }
                    }
            }
        });

        _receiver = new Thread(syncClientServer =>
        {
            var waitForResponse = (AutoResetEvent)syncClientServer;

            using (var pipeStream = new NamedPipeClientStream(servername, pipeName, PipeDirection.In, PipeOptions.None))
            {
                pipeStream.Connect();

                using (var sr = new StreamReader(pipeStream))
                    // Do this till Cancel() is called
                    // Again, this is a tight loop, perhaps a Thread.Yield or something?
                    while (!_cancelRequested)
                        // If there's anything in the stream
                        if (!sr.EndOfStream)
                        {
                            // Read it
                            var response = sr.ReadLine();
                            // Raise the event for processing
                            // Note that this event is being raised from the
                            // receiver thread and you can't access UI here
                            // You will need to Control.BeginInvoke or some such
                            RaiseResponseReceived(_currentId, response);

                            // Proceed with sending subsequent commands
                            waitForResponse.Set();
                        }
            }
        });
    }

    public void Start()
    {
        _sender.Start(_waitForResponse);
        _receiver.Start(_waitForResponse);
    }

    public void Cancel()
    {
        _cancelRequested = true;
    }

    public event ResponseReceived ResponseReceived;
}

You can see that I have created abstractions for the Console.ReadLine (the command queue) and Console.WriteLine (the event). The "quit" is also a boolean variable that is set by the "Cancel()" method now. Obviously this isn't the most optimal/correct way of doing it - I am just showing you one way to relate the imperative code from above into a wrapper class that can be re-used.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM