如何创建新的命令提示符窗口并重定向用户输入?

时间:2015-12-06 12:35:48

标签: c# input command-prompt

对于游戏服务器应用程序,我已经有一个控制台,其中显示了一些运行时信息。尽管如此,我还希望另一个管理员可以输入命令(例如在紧急情况下),而这些输入的结果效果仍显示在主控制台窗口中。

关于此主题的stackoverflow已经有类似的问题,但是,应用这些答案并没有产生我希望的结果。我很难理解为什么一方面,我似乎必须设置UseShellExecute = true;来实际获得一个新窗口,而这使得RedirectStandardInput = true;无法实现,反之亦然。但是,通过新进程输入和输出,但在同一控制台提示符下工作正常,除了视觉混乱(当你写,输出附加到你的wirtten但没有发送输入,这是非常不舒服)。

那么,是否仍然可以使用单独的命令提示符进行管理输入(我猜是这样),或者我是否必须设置另一种形式的进程间通信并创建一个单独的程序(具有Main-function和all) )?

这是我目前关于流程生成的代码。请注意,如果您想了解整体构图,它会嵌入在一个不太简化的上下文中:

bool canExecute = false;

Process consoleProcess;
ProcessStartInfo startInfo = new ProcessStartInfo();

OperatingSystem os = Environment.OSVersion;
switch (os.Platform)
{
    case PlatformID.MacOSX:
        canExecute = false;
        break;
    case PlatformID.Unix:
        canExecute = true;
        startInfo.FileName = "/bin/bash";
        break;
    case PlatformID.Win32NT:
        canExecute = true;
        startInfo.FileName = "cmd.exe";
        break;
    case PlatformID.Win32S:
        canExecute = true;
        startInfo.FileName = "cmd.exe";
        break;
    case PlatformID.Win32Windows:
        canExecute = true;
        startInfo.FileName = "cmd.exe";
        break;
    case PlatformID.WinCE:
        canExecute = true;
        startInfo.FileName = "cmd.exe";
        break;
    case PlatformID.Xbox:
        canExecute = false;
        break;
}

startInfo.RedirectStandardInput = true;
startInfo.UseShellExecute = false;

consoleProcess = new Process();
consoleProcess.StartInfo = startInfo;
consoleProcess.Start();

if (canExecute)
{
    using (StreamWriter sw = consoleProcess.StandardInput)
    {
        String line;

        while ((line = Console.ReadLine()) != null)
        {
            // do something useful with the user input
        }
    }
}

提前谢谢!

1 个答案:

答案 0 :(得分:1)

仅使用内置的.NET Process类不能这样做。它不支持正确的选项。

问题在于,默认情况下,新的Windows控制台进程始终会继承其父进程的已分配控制台。当您使用UseShellExecute = true;Process的默认值)时,这会导致Process类(当然)使用ShellExecuteEx()方法。由于新进程是通过Windows Shell而不是进程创建的,因此没有可继承的控制台,因此进程可以自行创建。但是,如果直接创建进程,则会获得默认的控制台继承行为。

但是,当然,由于您要重定向标准I / O,因此无法使用UseShellExecute = true;。您必须将其设置为false

解决这个问题的唯一方法是通过p / invoke直接调用CreateProcess(),这样你就可以传递你需要的标志以及Process类没有提供控制方法的方法。有问题的旗帜是CREATE_NEW_CONSOLE。传递给函数调用,它告诉CreateProcess()函数为新进程创建一个单独的控制台。

有关此行为的详细信息以及如何根据您的需要进行调整,请参阅MSDN的Creation of a Console

当然,这会打开一个全新的蠕虫病毒,因为您将不再具有Process类的直接便利来帮助重定向I / O.从长远来看,您可能会发现只需编写一个瘦的非控制台代理程序来运行实际程序就更容易了。这样,你可以启动非控制台代理,当然不会继承你当前的控制台,然后让启动你真正想要运行的程序。这样做并不是非常优雅(其中最重要的是,因为代理不是控制台程序,你将无法通过stdio轻松地重定向I / O),但它相当简单,让你保持管理 - 代码世界,使用更易于使用的API。


请在下面找到双向代理(编译为“Windows应用程序”,“控制台应用程序”)的示例,其中包含父进程和子进程。子进程简单地向控制台回送任何输入内容。父进程将任何输入内容写入代理。代理向父发送从父进程收到的任何内容,并向父进程发送从子进程收到的任何内容。所有进程都将空行输入视为终止条件。

出于您自己的目的,您可能会使用单向管道(即PipeDirection.In用于代理,PipeDirection.Out用于父进程),并且仅使用代理重定向StandardInput 。这样,所有输出仍将出现在子进程的窗口中。 (双向示例更多用于概念验证......显然,如果输入和输出都是定向的,那么将子进程强制进入自己的窗口并没有多大意义:))。

代理:(ConsoleProxy.exe)

class Program
{
    static void Main(string[] args)
    {
        NamedPipeClientStream pipe = new NamedPipeClientStream(".", args[1],
            PipeDirection.InOut, PipeOptions.Asynchronous);

        pipe.Connect();

        Process process = new Process();

        process.StartInfo.FileName = args[0];
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardInput = true;
        process.StartInfo.RedirectStandardOutput = true;

        process.Start();

        using (TextReader reader = new StreamReader(pipe))
        using (TextWriter writer = new StreamWriter(pipe))
        {
            Task readerTask = ConsumeReader(process.StandardOutput, writer);
            string line;

            do
            {
                line = reader.ReadLine();
                if (line != "")
                {
                    line = "proxied write: " + line;
                }
                process.StandardInput.WriteLine(line);
                process.StandardInput.Flush();
            } while (line != "");

            readerTask.Wait();
        }
    }

    static async Task ConsumeReader(TextReader reader, TextWriter writer)
    {
        char[] rgch = new char[1024];
        int cch;

        while ((cch = await reader.ReadAsync(rgch, 0, rgch.Length)) > 0)
        {
            writer.Write("proxied read: ");
            writer.Write(rgch, 0, cch);
            writer.Flush();
        }
    }
}

子进程:(ConsoleApplication1.exe)

class Program
{
    static void Main(string[] args)
    {
        Console.Title = "ConsoleApplication1";

        string line;

        while ((line = PromptLine("Enter text: ")) != "")
        {
            Console.WriteLine("    Text entered: \"" + line + "\"");
        }
    }

    static string PromptLine(string prompt)
    {
        Console.Write(prompt);
        return Console.ReadLine();
    }
}

家长流程:

class Program
{
    static void Main(string[] args)
    {
        NamedPipeServerStream pipe = new NamedPipeServerStream("ConsoleProxyPipe",
            PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);

        Console.Title = "Main Process";

        Process process = new Process();

        process.StartInfo.FileName = "ConsoleProxy.exe";
        process.StartInfo.Arguments = "ConsoleApplication1.exe ConsoleProxyPipe";
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.CreateNoWindow = true;

        process.Start();

        pipe.WaitForConnection();

        using (TextReader reader = new StreamReader(pipe))
        using (TextWriter writer = new StreamWriter(pipe))
        {
            Task readerTask = ConsumeReader(reader);
            string line;

            do
            {
                line = Console.ReadLine();
                writer.WriteLine(line);
                writer.Flush();
            } while (line != "");

            readerTask.Wait();
        }
    }

    static async Task ConsumeReader(TextReader reader)
    {
        char[] rgch = new char[1024];
        int cch;

        while ((cch = await reader.ReadAsync(rgch, 0, rgch.Length)) > 0)
        {
            Console.Write(rgch, 0, cch);
        }
    }
}