如何围绕控制台应用程序创建 C# 包装器(任何语言)

发布于 2024-12-04 12:05:38 字数 756 浏览 0 评论 0原文

我正在尝试制作一个控制台应用程序,它可以包含几乎任何打开控制台的应用程序。

一些简单的事实:

  • 包装应用程序的输出应显示在控制台中。
  • 包装器控制台的输入也应该是包装控制台应用程序的输入。
  • 您必须能够使用代码来插入命令。

这是我到目前为止所拥有的,除了最后几行之外,一切正常:

ProcessStartInfo startInfo = new ProcessStartInfo("someBatchThingy.cmd");

startInfo.WorkingDirectory = Environment.CurrentDirectory;
startInfo.UseShellExecute = false;

Process process = new Process();
process.StartInfo = startInfo;

process.Start();

Thread.Sleep(5000);

Stream s = Console.OpenStandardOutput();
byte[] stopCommand = new byte[] { 115, 116, 111, 112, 13 };
s.Write(stopCommand, 0, stopCommand.Length);

process.WaitForExit();

因为性能很重要,所以我真的想重新分配到进程输出的位置,而不是手动将数据从隐藏控制台传输到包装器控制台。

有谁知道如何做到这一点/如果可能的话?

I'm trying to make a console application, that can wrap around pretty much any application that opens a console.

A few quick facts:

  • The output of the wrapped application should show in the console.
  • The input of the wrapper console should also be the input of the wrapped console app.
  • You must be able to use code to also insert commands.

This is what I have so far, everything works, except the last few lines:

ProcessStartInfo startInfo = new ProcessStartInfo("someBatchThingy.cmd");

startInfo.WorkingDirectory = Environment.CurrentDirectory;
startInfo.UseShellExecute = false;

Process process = new Process();
process.StartInfo = startInfo;

process.Start();

Thread.Sleep(5000);

Stream s = Console.OpenStandardOutput();
byte[] stopCommand = new byte[] { 115, 116, 111, 112, 13 };
s.Write(stopCommand, 0, stopCommand.Length);

process.WaitForExit();

Because performance matters a lot, I really want to reassign to where the process outputs, and not manually transfer the data from a hidden console, to the wrapper console.

Does anyone know how to do this/if this is possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

多孤肩上扛 2024-12-11 12:05:38

据我所知,如果不通过应用程序传输数据,就无法实现这一目标。

执行此操作的代码如下:

using System;
using System.Diagnostics;
using System.IO;
using System.Threading;

namespace TestApp
{
    internal static class Program
    {
        [MTAThread]
        public static void Main(string[] args)
        {
            const string fileName = @"..\..\..\ChildConsoleApp\bin\Debug\ChildConsoleApp.exe";

            // Fires up a new process to run inside this one
            var process = Process.Start(new ProcessStartInfo
            {
                UseShellExecute = false,

                RedirectStandardError = true,
                RedirectStandardInput = true,
                RedirectStandardOutput = true,

                FileName = fileName
            });

            // Depending on your application you may either prioritize the IO or the exact opposite
            const ThreadPriority ioPriority = ThreadPriority.Highest;
            var outputThread = new Thread(outputReader) { Name = "ChildIO Output", Priority = ioPriority};
            var errorThread = new Thread(errorReader) { Name = "ChildIO Error", Priority = ioPriority };
            var inputThread = new Thread(inputReader) { Name = "ChildIO Input", Priority = ioPriority };

            // Set as background threads (will automatically stop when application ends)
            outputThread.IsBackground = errorThread.IsBackground
                = inputThread.IsBackground = true;

            // Start the IO threads
            outputThread.Start(process);
            errorThread.Start(process);
            inputThread.Start(process);

            // Demonstrate that the host app can be written to by the application
            process.StandardInput.WriteLine("Message from host");

            // Signal to end the application
            ManualResetEvent stopApp = new ManualResetEvent(false);

            // Enables the exited event and set the stopApp signal on exited
            process.EnableRaisingEvents = true;
            process.Exited += (e, sender) => { stopApp.Set(); };

            // Wait for the child app to stop
            stopApp.WaitOne();

            // Write some nice output for now?
            Console.WriteLine();
            Console.Write("Process ended... shutting down host");
            Thread.Sleep(1000);
        }

        /// <summary>
        /// Continuously copies data from one stream to the other.
        /// </summary>
        /// <param name="instream">The input stream.</param>
        /// <param name="outstream">The output stream.</param>
        private static void passThrough(Stream instream, Stream outstream)
        {
            byte[] buffer = new byte[4096];
            while (true)
            {
                int len;
                while ((len = instream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    outstream.Write(buffer, 0, len);
                    outstream.Flush();
                }
            }
        }

        private static void outputReader(object p)
        {
            var process = (Process)p;
            // Pass the standard output of the child to our standard output
            passThrough(process.StandardOutput.BaseStream, Console.OpenStandardOutput());
        }

        private static void errorReader(object p)
        {
            var process = (Process)p;
            // Pass the standard error of the child to our standard error
            passThrough(process.StandardError.BaseStream, Console.OpenStandardError());
        }

        private static void inputReader(object p)
        {
            var process = (Process)p;
            // Pass our standard input into the standard input of the child
            passThrough(Console.OpenStandardInput(), process.StandardInput.BaseStream);
        }
    }
}

我的子应用程序的代码如下所示:

using System;

namespace ChildConsoleApp
{
    internal static class Program
    {
        public static void Main()
        {
            Console.WriteLine("Hi");

            string text; // Echo all input
            while ((text = Console.ReadLine()) != "stop")
                Console.WriteLine("Echo: " + text);

            Console.WriteLine("Stopped.");
        }
    }
}

与往常一样,这会产生一些开销,尽管它在您正在包装的任何重要应用程序中可能微不足道。

如果您要发送大量数据,并且希望避免超出必要范围的刷新,则可以将缓冲区大小从 4KB 增加到适合您的大小。

So as far as I know there is no way to achieve this without tunneling the data through your application.

Code to do this is as follows:

using System;
using System.Diagnostics;
using System.IO;
using System.Threading;

namespace TestApp
{
    internal static class Program
    {
        [MTAThread]
        public static void Main(string[] args)
        {
            const string fileName = @"..\..\..\ChildConsoleApp\bin\Debug\ChildConsoleApp.exe";

            // Fires up a new process to run inside this one
            var process = Process.Start(new ProcessStartInfo
            {
                UseShellExecute = false,

                RedirectStandardError = true,
                RedirectStandardInput = true,
                RedirectStandardOutput = true,

                FileName = fileName
            });

            // Depending on your application you may either prioritize the IO or the exact opposite
            const ThreadPriority ioPriority = ThreadPriority.Highest;
            var outputThread = new Thread(outputReader) { Name = "ChildIO Output", Priority = ioPriority};
            var errorThread = new Thread(errorReader) { Name = "ChildIO Error", Priority = ioPriority };
            var inputThread = new Thread(inputReader) { Name = "ChildIO Input", Priority = ioPriority };

            // Set as background threads (will automatically stop when application ends)
            outputThread.IsBackground = errorThread.IsBackground
                = inputThread.IsBackground = true;

            // Start the IO threads
            outputThread.Start(process);
            errorThread.Start(process);
            inputThread.Start(process);

            // Demonstrate that the host app can be written to by the application
            process.StandardInput.WriteLine("Message from host");

            // Signal to end the application
            ManualResetEvent stopApp = new ManualResetEvent(false);

            // Enables the exited event and set the stopApp signal on exited
            process.EnableRaisingEvents = true;
            process.Exited += (e, sender) => { stopApp.Set(); };

            // Wait for the child app to stop
            stopApp.WaitOne();

            // Write some nice output for now?
            Console.WriteLine();
            Console.Write("Process ended... shutting down host");
            Thread.Sleep(1000);
        }

        /// <summary>
        /// Continuously copies data from one stream to the other.
        /// </summary>
        /// <param name="instream">The input stream.</param>
        /// <param name="outstream">The output stream.</param>
        private static void passThrough(Stream instream, Stream outstream)
        {
            byte[] buffer = new byte[4096];
            while (true)
            {
                int len;
                while ((len = instream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    outstream.Write(buffer, 0, len);
                    outstream.Flush();
                }
            }
        }

        private static void outputReader(object p)
        {
            var process = (Process)p;
            // Pass the standard output of the child to our standard output
            passThrough(process.StandardOutput.BaseStream, Console.OpenStandardOutput());
        }

        private static void errorReader(object p)
        {
            var process = (Process)p;
            // Pass the standard error of the child to our standard error
            passThrough(process.StandardError.BaseStream, Console.OpenStandardError());
        }

        private static void inputReader(object p)
        {
            var process = (Process)p;
            // Pass our standard input into the standard input of the child
            passThrough(Console.OpenStandardInput(), process.StandardInput.BaseStream);
        }
    }
}

My child app's code looks like:

using System;

namespace ChildConsoleApp
{
    internal static class Program
    {
        public static void Main()
        {
            Console.WriteLine("Hi");

            string text; // Echo all input
            while ((text = Console.ReadLine()) != "stop")
                Console.WriteLine("Echo: " + text);

            Console.WriteLine("Stopped.");
        }
    }
}

As always this has some overhead, though it's likely insignificant in any non-trivial application you're wrapping around.

If you're sending a lot of data, and want to avoid flushing more than necessary, you can increase the buffer size from 4KB to whatever suits you.

街道布景 2024-12-11 12:05:38

老问题,但刚刚提出以下来解决类似的问题。

public class ConsoleAppWrapper
{
    private Process _process;

    public ConsoleAppWrapper(string exeFilename)
    {
        // Start the Player console app up with hooks to standard input/output and error output
        _process = new Process()
        {
            StartInfo = new ProcessStartInfo(exeFilename)
            {
                UseShellExecute = false,

                RedirectStandardError = true,
                RedirectStandardInput = true,
                RedirectStandardOutput = true
            }
        };
        _process.Start();
    }

    public StreamReader StandardOutput => _process.StandardOutput;
    public StreamReader StandardError => _process.StandardError;
    public StreamWriter StandardInput => _process.StandardInput;

    ~ConsoleAppWrapper()
    {
        // When I used this class to help with some SpecFlow feature tests of a console app I found it wasn't destroying the console window correctly when assertions failed
        // using a destructor ensures the windows is destroyed when the wrapper goes out of scope.
        Kill();
    }

    public void Kill()
    {
        Dispose();
    }
}

使用此类,您可以控制交互,而无需像这样的控制台应用程序。

        var c = new ConsoleAppWrapper("App.exe");

        Assert.That(c.StandardOutput.ReadLine(), Is.StringContaining("Enter some data:"));
        c.StandardInput.WriteLine("SOME DATA\n");
        Assert.That(c.StandardOutput.ReadLine(), !Is.StringContaining("Error"));

Old question, but just up came up with following to solve a similar problem.

public class ConsoleAppWrapper
{
    private Process _process;

    public ConsoleAppWrapper(string exeFilename)
    {
        // Start the Player console app up with hooks to standard input/output and error output
        _process = new Process()
        {
            StartInfo = new ProcessStartInfo(exeFilename)
            {
                UseShellExecute = false,

                RedirectStandardError = true,
                RedirectStandardInput = true,
                RedirectStandardOutput = true
            }
        };
        _process.Start();
    }

    public StreamReader StandardOutput => _process.StandardOutput;
    public StreamReader StandardError => _process.StandardError;
    public StreamWriter StandardInput => _process.StandardInput;

    ~ConsoleAppWrapper()
    {
        // When I used this class to help with some SpecFlow feature tests of a console app I found it wasn't destroying the console window correctly when assertions failed
        // using a destructor ensures the windows is destroyed when the wrapper goes out of scope.
        Kill();
    }

    public void Kill()
    {
        Dispose();
    }
}

using this class you can control interaction without console app like so.

        var c = new ConsoleAppWrapper("App.exe");

        Assert.That(c.StandardOutput.ReadLine(), Is.StringContaining("Enter some data:"));
        c.StandardInput.WriteLine("SOME DATA\n");
        Assert.That(c.StandardOutput.ReadLine(), !Is.StringContaining("Error"));
盛夏已如深秋| 2024-12-11 12:05:38

它的新版本会很漂亮..但至少这不依赖于发送密钥。

            var process = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = "myLegacyConsoleApp.exe",
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    WorkingDirectory = @"C:\Program Files\myAppDirectory\",
                }
            };


            process.OutputDataReceived += new DataReceivedEventHandler(process_OutputDataReceived);
            process.Start();

            process.BeginOutputReadLine();
            // You need to wait a bit.. Im sorry..
            System.Threading.Thread.Sleep(5000);
            process.StandardInput.WriteLine("Welcome to Ûberland");

            process.WaitForExit();

Its newer going to be pretty.. But atleast this does not rely on sendkeys.

            var process = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = "myLegacyConsoleApp.exe",
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    WorkingDirectory = @"C:\Program Files\myAppDirectory\",
                }
            };


            process.OutputDataReceived += new DataReceivedEventHandler(process_OutputDataReceived);
            process.Start();

            process.BeginOutputReadLine();
            // You need to wait a bit.. Im sorry..
            System.Threading.Thread.Sleep(5000);
            process.StandardInput.WriteLine("Welcome to Ûberland");

            process.WaitForExit();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文