简体   繁体   English

没有从控制台发出命令的C#命名管道?

[英]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 ) (原始代码在这里: 通过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. 我的问题是,我基本上想用此代码包装C#GUI,但是不确定如何修改它,以便无需使用控制台即可完成通信,因为命令将通过GUI或从代码中发出。

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. 我尝试将streamWriter sw转换为类变量,通过属性将其公开,并使用方法调用sw.WriteLine(),但这似乎不起作用。

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. 我发现这篇文章似乎很不错,在Windows中使用命名管道将GUI连接到控制台应用程序很不幸,但是不幸的是,它似乎没有任何代码,并且有点让人头疼。

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). 您可以看到我已经为Console.ReadLine(命令队列)和Console.WriteLine(事件)创建了抽象。 The "quit" is also a boolean variable that is set by the "Cancel()" method now. “ quit”也是现在由“ Cancel()”方法设置的布尔变量。 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. 显然,这并不是最理想/最正确的方法-我只是向您展示一种将命令式代码从上方关联到可重用的包装器类的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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