C#调用控制台(DOS)程序

本文分为两部分,第一部分是理论篇讲述调用的原理,第二部分为实战篇通过具体代码来进行实现。

前言

在C#中调用 DOS命令是程序设计中非常常用的一项功能。以下代码润演示了如何在C#中相对安全地调用DOS程序并返回结果。

调用代码

foreach (var file in Directory.GetFiles("."))
{
    string fullpath = Path.GetFullPath(file);
    string sha1 = RunDosCommand("certutil", "-hashfile", fullpath, "SHA1").Split('\n')[1];
    Console.WriteLine($"The SHA1 of {fullpath} is {sha1}.");
}

输出结果

The SHA1 of D:\TestProject\Debug\TestProject.exe is 50f7275510cad7078c7500cea318c9736d8f45cd.
The SHA1 of D:\TestProject\Debug\TestProject.exe.config is 2711de49785aba673df043f543686685e3b53e73.
The SHA1 of D:\TestProject\Debug\TestProject.pdb is 12d81ecc44ea2c321f1506343e717081b1933155.

注意事项

调用DOS命令时,是只返回执行结果,而不包括其他信息,举例来说,在DOS中输入 certutil -hashfile test.py SHA1 会得到以下信息。

C:\Users\Administrator>certutil -hashfile test.py SHA1
SHA1 的 test.py 哈希:
0c3f2b2f85c713140d98ca1d5c524881c556117a
CertUtil: -hashfile 命令成功完成。

C:\Users\Administrator>

而在程序中调用时,只返回结果信息如下:

SHA1 的 test.py 哈希:
0c3f2b2f85c713140d98ca1d5c524881c556117a
CertUtil: -hashfile 命令成功完成。

函数源代码

/// <summary>
/// 用于执行DOS自带的命令并返回执行结果(仅包括执行结果信息)。
/// 由于DOS执行几乎不会卡死,所以没有设置等待时间。
/// 调用格式如: RunDosCommand("dir", "*.exe);
/// </summary>
/// <param name="doscmd">待调用的DOS命令。</param>
/// <param name="arguments">参数列表</param>
/// <returns></returns>
public static string RunDosCommand(string doscmd, params string[] arguments)
{
    Process p = new Process();
    p.StartInfo.FileName = "cmd.exe";
    p.StartInfo.Arguments = "/C " + doscmd + " " + string.Join(" ", arguments);
    p.StartInfo.UseShellExecute = false; // 不显示用户界面
    p.StartInfo.RedirectStandardOutput = true; // 是否重
    p.StartInfo.CreateNoWindow = true;
    // 返回数据
    string output = "";
    try
    {
        if (p.Start())//开始进程  
        {
            p.WaitForExit(); //等待进程结束,等待时间为指定的毫秒  
            output = p.StandardOutput.ReadToEnd();//读取进程的输出  
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);//捕获异常,输出异常信息
    }
    finally
    {
        if (p != null)
            p.Close();
    }

    return output;
}

补充

上面的方法基本上所有的第三方调用都可以支持,但是必需在 cmd.exe 中进行调用,下面补充了一个函数,用于直接调用第三方程序,而不用经过 cmd.exe,同时提供了超时处理。


/// <summary>
/// 用于执行第三方程序,并返回执行结果。
/// </summary>
/// <param name="execuable">可执行的程序。</param>
/// <param name="timeout">超时时间,单位秒,设置为0时不限时。</param>
/// <param name="arguments">可执行程序的参数列表。</param>
/// <returns></returns>
public static string RunCommand(string execuable, int timeout, params string[] arguments)
{
    Process p = new Process();
    p.StartInfo.FileName = execuable;
    p.StartInfo.Arguments = string.Join(" ", arguments);
    p.StartInfo.UseShellExecute = false; // 不显示用户界面
    p.StartInfo.RedirectStandardOutput = true; // 是否重定位输出于当前输出。
    p.StartInfo.CreateNoWindow = true; // 不创建新窗口。

    string output = ""; 
    try
    {
        if (p.Start())//开始进程  
        {
            if (timeout == 0)
            {
                p.WaitForExit();//这里无限等待进程结束  
            }
            else
            {
                p.WaitForExit(timeout * 1000); //等待进程结束,等待时间为指定的毫秒  
            }

            // 如果还没有结束,就将线程关闭
            if (!p.HasExited)
                p.Kill();

            output = p.StandardOutput.ReadToEnd();//读取进程的输出  
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);//捕获异常,输出异常信息
    }
    finally
    {
        if (p != null)
            p.Close();
    }

    return output;
}


本文分为两部分,第一部分是理论篇讲述调用的原理,第二部分为实战篇通过具体代码来进行实现。

前言

控制台程序(DOS)程序是一种广泛使用的用于处理后台数据的应用程序。由于很多程序主要用于数据处理而不需要面向终端用户,所以不需要设计用户界面。C#是一门非常强大的编程语言,是当前最好的桌面应用程序界面制作语言之一。所以我们在进行用户程序设计的时候,可以使用C#进行软件界面的设计,然后调用一些功能模块,尤其是已经存在可利用模块。

目前,关于C#调用DOS程序的文章有很多,但是却存在很多问题。

  • 内容简单肤浅:基本就是只讲如何在C#中调用,仅限于语法层面。
  • 缺少解释说明:经常是只直接贴代码,而没有解释说明。
  • 内容片面有限:基本只讲如何调用,而相关阻塞、线程等内容都没有说明。
  • 缺少技术细节,如怎样显示调用程序的窗口,阻塞是如何发生的,如何进行处理等。

本文针对C#调用DOS程序的多方面技术细节,通过实验的形式进行详细的分析、进而给出结论,帮助读者快速全面掌握这项调用技术。在本文中,为了描述时便于区分,我们将被调用的DOS程序称之为控制台程序,简称 CP(Console Program),而调用控制台程序的程序称之为主程序,简称 MP (Main Program)。

准备工作

首先,我们编写了一个控制台程序 ConsoleProgram.exe,其Main函数代码如下所示。此程序首先输出参数的长度,然后模拟用时2秒进行数据处理,最后返回 Done 表示任务完成。

static void Main(string[] args)
{
	// 处理时间为 2 秒,通过延时实现
	Console.WriteLine("Processing the inputs of length " + args.Length);
	System.Threading.Thread.Sleep(2000);

	// 输出结果完成
	Console.WriteLine("Done");
}

简单调用

如果只考虑能够让CP运行起来的简单调用,那么只需要以下代码即可。

public void SimpleInvoke()
{
	System.Diagnostics.Process p = new System.Diagnostics.Process();
	p.StartInfo.FileName = @"C:\data\ConsoleProgram.exe";
	p.StartInfo.Arguments = @"C:\Program Files\Test D:\Output";
	p.Start(); 
}

其中 FileName 为CP可执行文件名称,包括完全路径和文件名称。由于在DOS环境下,对空格和中文都有要求(具体请参考相关文章,不在本文设计范围内),所以我们用以下三种路径来进行测试,其中,Dir1代表简单路径,Dir2代表有空格的路径,Dir3为代表有中文的路径。

Dir1: C:\data\ConsoleProgram.exe
Dir2: C:\data\20181121 Test\ConsoleProgram.exe
Dir3: C:\data\20181121 控制台程序测试\ConsoleProgram.exe

经测试,三种路径都可以正常调用程序,说明C#在调用DOS程序时,已经对路径问题进行了处理,我们可以忽略路径的名称问题。

补充知识:关于DOS下的路径

在以上的程序中,虽然程序路径没有问题,但是参数获取则出现了问题。在输出界面上,显示的内容如下:

Processing the inputs of length 3

这说明,参数的长度为3。通过调试查看args可以发现这三个参数分别为 C:\\ProgramFilesD:\\Outout。显然,第一个参数 C:\Program Files由于中间有空格,被DOS拆分成2个参数来处理了。为了避免这样的问题,根据DOS的要求,带有空格的路径名称需要用双引号包起来,所以可以将第一个参数修改为 "C:\Program Files",即:

p.StartInfo.Arguments = "\"C:\\Program Files\" D:\\Outout";

这样 p.StartInfo.Arguments的赋值为: "C:\\Program Files\" D:\\Outout
再次运行,即可得到正确的结果,即参数长度为2。这说明参数路径的空格并没有进行相应的处理,所以我们在传参的时候,需要手工处理参数中包括的空格。

可接收结果的调用

在调用了DOS程序后,很多DOS程序都是有返回结果的。但是在以上的简单调用时,由于CP的窗体是独立于MP且它们之间任何直接的关联,所以我们是接收不到结果的。为了解决这个问题,我们可以通过建立输出连接的方式来接收数据。
具体实现办法如下代码所示。

public void ReturnableInvoke()
{
	System.Diagnostics.Process p = new System.Diagnostics.Process();
	p.StartInfo.FileName = @"C:\data\ConsoleProgram.exe";
	p.StartInfo.UseShellExecute = false; // 必需设置此属性为true,下面两个属性才有效
	p.StartInfo.RedirectStandardOutput = true; // 关键行2
	p.StartInfo.CreateNoWindow = true;
	p.Start();  
	string output = p.StandardOutput.ReadToEnd();
	Console.WriteLine(output);
}

执行过以后,即可在控制台获得CP的输出数据。在以上代码中,我们修改了p.StartInfo的几个属性,分别介绍如下。

  • UseShellExecute
    由于本来输出到控制台的数据,现在输出到调用的程序中来,所以必需将此值改为 false。官方文档中,也明确提出:**要重定向 IO 流,Process 对象必须将 UseShellExecute 属性设置为 False。**这个属性的主要作用就是用于在程序调用时,允许重定位了输出或输出,否则其他作用无效。

  • StartInfo.RedirectStandardOutput
    通过名称可以看出,这个变量的作用是决定是否重新定位输出流,只有修改了这个值以后,调用程序才可以接收到数据。

  • StartInfo.StandardOutput
    控制台程序的输出,可以调用ReadToEnd()方法获取所有的输出内容。

  • StartInfo.CreateNoWindow
    通常情况下,我们在C#程序中调用控制台程序是不希望显示出控制台的黑色界面的。隐藏此窗实现起来非常简单,只需要将 p.StartInfo.CreateNoWindow设置为true即可。但是要注意,隐藏窗体也必需在UseShellExecute属性为false时才可以生效。

阻塞

使用以上的代码接收处理的数据时,主程序将会在调用时发生阻塞,即主程序需要等待控制台程序退出后才会继续响应用户操作。这是因为主程序调用p.StandardOutput.ReadToEnd()接收数据的前提条件是必需让控制台完成数据的处理并输出后,才能收到数据。所以程序会卡在这里,直到数据处理完成。这时候,如果控制台程序出现问题,卡的时间很长,会影响主程序的用户体验。我们这时候有两种办法来处理这个问题:超时中止和异步调用。

超时中止

    p.Start();
	p.WaitForExit(1000); 
	if(!p.HasExited)
		p.Kill();
	p.StandardOutput.ReadToEnd(); 

WaitForExit:等待一段时间,系统将卡在这里一段时间。时间长度由括号内的参数决定,单位为毫秒。

在以上代码段中,首先调用控制台程序,这里调用程序会在启动后继续执行,然后运行WaitForExit(1000)阻塞1000毫秒,这里有两种可能:

  • 阻塞结束前执行完成
    这时候,调用程序会中止阻塞,提前让调用程序继续执行下面的代码。
  • 阻塞结束后执行完成
    这时候,如果不想再等待,可以调用 p.Kill结束调用程序。

关于如何判断是否已经结束,可以使用 HasExited 属性进行判断,如果其值为真则表示程序执行完成顺利退出;否则表示仍然在执行中,可以使用Kill()中止CP的运行。以上的工作方式可以理解为在很多程序中都经常用到的 timeout.

异步调用

在以上的代码中,由于阻塞,我们需要等待程序结果后,才能继续执行调用程序。在实际的使用中,如果调用程序是带有UI界面的程序,那么界面会卡住,无法接受用户响应。所以为了解决此问题,我们需要进行异步调用,即在调用后,返回调用程序,然后等控制台程序计算结果完成后,再返回结果,具体做法如下所示。

	p.OutputDataReceived += P_OutputDataReceived;
	p.Start();
	p.BeginOutputReadLine();

OutputDataReceived 是一个消息接收时触发的事件,事件回调函数包括DataReceivedEventArgs 参数,其中有个 Data 属性即是控制台程序返回的输出字符串。需要注意的是,在启动控制台程序后,需要调用 BeginOutputReadLine() 函数,以通知调用函数发回数据。由于它是一个异步函数,在调用后调用程序仍然会继续向前执行,不会导致发生阻塞导致界面卡死。当DP有任何输出时 ,会发送给MP。注意:由于数据是以行的形式读取的,所以每发送一行数据,OutputDataReceived 事件会被触发一次。

 

C# Console.OpenStandardOutput方法代码示例

本文整理汇总了C#中System.Console.OpenStandardOutput方法的典型用法代码示例。如果您正苦于以下问题:C# Console.OpenStandardOutput方法的具体用法?C# Console.OpenStandardOutput怎么用?C# Console.OpenStandardOutput使用的例子?那么恭喜您, 这里精选的方法代码示例或许可以为您提供帮助。您也可以进一步了解该方法所在类System.Console的用法示例。



在下文中一共展示了Console.OpenStandardOutput方法的1个代码示例,这些例子默认根据受欢迎程度排序。您可以为喜欢或者感觉有用的代码点赞,您的评价将有助于我们的系统推荐出更棒的C#代码示例。

示例1: Main

 
//引入命名空间
using System;
using System.IO;

public class InsertTabs
{
    private const int tabSize = 4;
    private const string usageText = "Usage: INSERTTABS inputfile.txt outputfile.txt";
    public static int Main(string[] args)
    {
        if (args.Length < 2)
        {
            Console.WriteLine(usageText);
            return 1;
        }

        try
        {
            // Attempt to open output file.
            using (var writer = new StreamWriter(args[1]))
            {
                using (var reader = new StreamReader(args[0]))
                {
                    // Redirect standard output from the console to the output file.
                    Console.SetOut(writer);
                    // Redirect standard input from the console to the input file.
                    Console.SetIn(reader);
                    string line;
                    while ((line = Console.ReadLine()) != null)
                    {
                        string newLine = line.Replace(("").PadRight(tabSize, ' '), "\t");
                        Console.WriteLine(newLine);
                    }
                }
            }
        }
        catch(IOException e)
        {
            TextWriter errorWriter = Console.Error;
            errorWriter.WriteLine(e.Message);
            errorWriter.WriteLine(usageText);
            return 1;
        }

        // Recover the standard output stream so that a 
        // completion message can be displayed.
        var standardOutput = new StreamWriter(Console.OpenStandardOutput());
        standardOutput.AutoFlush = true;
        Console.SetOut(standardOutput);
        Console.WriteLine($"INSERTTABS has completed the processing of {args[0]}.");
        return 0;
    }
}
开发者ID:.NET开发者,项目名称:System,代码行数:53,代码来源:Console.OpenStandardOutput




注:本文中的System.Console.OpenStandardOutput方法示例由纯净天空整理自Github/MSDocs等源码及文档管理平台,相关代码片段筛选自各路编程大神贡献的开源项目,源码版权归原作者所有,传播和使用请参考对应项目的License;未经允许,请勿转载。

posted @ 2022-05-25 18:11  CharyGao  阅读(106)  评论(0)    收藏  举报