WPF开发-Process使用详解及打开本地文件(新进程打开文件)

前言

Process类在System.Diagnostics命名空间中,用于启动和管理系统进程。

主要方法和属性如下:

  • Start() - 启动进程
  • Kill() - 终止进程
  • Close() - 仅仅是强制关闭了进程的标准流,但并没有停止进程的运行。
  • ExitCode - 获取进程退出代码
  • StartInfo - 设置启动进程的相关属性,如文件名、参数、工作目录等
  • StandardInput - 进程的标准输入流
  • StandardOutput - 进程的标准输出流
  • StandardError - 进程的标准错误流

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
Process process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = "/c dir";
process.StartInfo.CreateNoWindow = true; //无窗口启动
process.StartInfo.UseShellExecute = false; //是否使用操作系统外壳启动
process.StartInfo.RedirectStandardOutput = true; //重定向标准输出
process.Start();
//读取输出流中的输出
string output = p.StandardOutput.ReadToEnd();
Console.WriteLine(output);
process.WaitForExit();
process.Dispose();

此代码启动cmd.exe,执行dir命令,并获取输出,输出目录列表信息。

StartInfo的主要属性:

  • FileName - 可执行文件的文件名
  • Arguments - 传递给可执行文件的命令行参数
  • WorkingDirectory - 进程工作目录
  • CreateNoWindow - 无窗口启动进程等等。使用Process类可以很方便的启动和管理系统进程。
  • UseShellExecute - 是否使用操作系统外壳启动
  • RedirectStandardInput / RedirectStandardOutput / RedirectStandardError - 重定向标准输入/输出/错误流

打开文件

使用系统的文件关联方式打开文件,如果没有关联的打开方式会抛出异常,要进行异常捕获。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void OpenFile(string filepath)
{
try
{
ProcessStartInfo psi = new ProcessStartInfo(filepath);
Process pro = new Process { StartInfo = psi };
pro.Start();
}
catch (Exception ex)
{
ZLogHelper.Logerror.Error("文件打开", ex);
MessageWindow.Show("文件打开失败,请设置文件的打开方式!");
}
}

打开文件夹 选中文件

1
Process.Start("explorer.exe", $"/select,\"{mp4Path}\"");

启动文件并等待安装

1
Process.Start(microsoftEdgeWebview2Setup, " /install")?.WaitForExit();

打开本程序

1
Process.Start(Application.ResourceAssembly.Location);

应用更新

下面两种方式生效的前提是:

程序必须以管理员身份运行。

可参考 https://www.psvmc.cn/article/2020-07-31-wpf-run-admin.html 配置。

新进程中打开

注意:

程序安装升级包建议用这种方式

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
try
{
using (ManagementClass managementClass = new ManagementClass("Win32_Process"))
{
ManagementClass processInfo = new ManagementClass("Win32_ProcessStartup");
processInfo.Properties["CreateFlags"].Value = 0x00000008;
ManagementBaseObject inParameters = managementClass.GetMethodParameters("Create");
inParameters["CommandLine"] = filepath;
inParameters["ProcessStartupInformation"] = processInfo;
ManagementBaseObject result = managementClass.InvokeMethod(
"Create",
inParameters,
null
);
if ((result != null) && ((uint)result.Properties["ReturnValue"].Value != 0))
{
Console.WriteLine(@"Process ID: {0}", result.Properties["ProcessId"].Value);
}
}
}
catch (Exception)
{
// ignored
}
Application.Current.Shutdown();

子进程中打开

子进程打开进行更新的时候一定要等待一下,在关闭程序,否则会导致更新程序还没打开就退出了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
try
{
using (Process process = new Process())
{
process.StartInfo.FileName = filepath;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.Start();
}
}
catch (Exception)
{
// ignored
}
await Task.Delay(300);
Dispatcher.Invoke(() =>
{
Close();
Application.Current.Shutdown();
});

获取错误码

如果我们需要获取进程的 ExitCode,应该使用 Process.WaitForExit() 方法来等待进程结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static bool LoadDll(string dllPath)
{
int exitCode;
ProcessStartInfo processStartInfo =
new ProcessStartInfo { FileName = "regsvr32.exe", Arguments = "/s " + dllPath };

//启动新进程并等待执行完毕
using (Process process = new Process())
{
process.StartInfo = processStartInfo;
process.Start();
bool exited = process.WaitForExit(5000);
if (!exited)
{
process.Kill();
}
// 获取进程的出错码
exitCode = process.ExitCode;
}
return exitCode == 0;
}

如果要调用Close,并且要获取 ExitCode,怎么办呢?

要确保 WaitForExit 方法在调用 Close 方法之后能够正确工作,你可以在调用 Close 方法之前先设置 Process 对象的 EnableRaisingEvents 属性为 true,并订阅 Process 对象的 Exited 事件。然后,在事件处理程序中调用 WaitForExit 方法以等待进程退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Process myProcess = new Process();
myProcess.StartInfo.FileName = "your_process.exe";

// 允许引发 Exited 事件
myProcess.EnableRaisingEvents = true;

// 订阅 Exited 事件
myProcess.Exited += (sender, e) =>
{
// 进程已退出,此时可以调用 WaitForExit 方法等待退出
myProcess.WaitForExit();

// 获取进程的退出码
int exitCode = myProcess.ExitCode;

// 处理退出码...
};
myProcess.Start();

// 关闭进程
myProcess.Close();

执行程序的几种方式

示例

1
2
3
4
5
6
7
8
9
10
11
using (Process process = new Process())
{
process.StartInfo.FileName = filepath;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = false;
process.StartInfo.RedirectStandardError = false;
process.StartInfo.RedirectStandardInput = false;
process.Start();
process.WaitForExit();
}

参数说明:

CreateNoWindow

CreateNoWindowtrue时不显示窗口。

UseShellExecute

程序中大多情况都是false

  • UseShellExecutetrue 时,可以使用文件关联来启动应用程序。

    例如,直接打开文本文件或图像文件,系统会使用默认关联的程序打开相应的文件。

  • 如果要指定工作目录、传递命令行参数、重定向标准输入输出等。

    需要将UseShellExecute设置为 false,这样,Process类将直接创建一个新进程来执行指定的应用程序,而不依赖操作系统的 Shell。

RedirectStandardInput

默认为false

重定向输入,需要在执行程序的时候进行输入的时候设置为true

RedirectStandardOutput

默认为false

重定向标准输出,需要获取标准输出的时候设置为true

RedirectStandardError

默认为false

重定向标准错误,需要获取标准错误的时候设置为true

不重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/// <summary>
/// 生成缩略图
/// </summary>
/// <param name="videoPath"></param>
/// <param name="imagePath"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns></returns>
public static bool GenerateThumbnails
(
string videoPath,
string imagePath,
int width = 1280,
int height = 720
)
{
if (File.Exists(imagePath))
{
File.Delete(imagePath);
}
string ffmpegpath = FfmpegPath;
string whStr = "";
if (width > 0)
{
whStr = " -s " + width + "x" + height;
}
try
{
string paras = "-i \"" + videoPath + "\" -ss 1 -vframes 1 -r 1 -ac 1 -ab 2" + whStr + " -f image2 \"" + imagePath + "\"";
using (Process process = new Process())
{
process.StartInfo.FileName = ffmpegpath; //ffmpeg.exe的绝对路径
process.StartInfo.Arguments = paras;
process.StartInfo.UseShellExecute = false; //不使用操作系统外壳程序启动
process.StartInfo.RedirectStandardError = false; //重定向标准错误输出
process.StartInfo.CreateNoWindow = true; //不显示程序窗口
process.StartInfo.RedirectStandardInput = false; //用于模拟该进程控制台的输入
process.Start(); //启动线程
bool exited = process.WaitForExit(5000);
if (!exited)
{
// 如果进程没有在5秒内结束,就终止进程
process.Kill();
}
}
return true;
}
catch (Exception)
{
return false;
}
}

这里不用输出的结果,所以都不重定向。

重定向标准输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static void AddFirewallRule()
{
try
{
// 添加防火墙入站规则
Process process = new Process();
string appPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
process.StartInfo.FileName = $@"{appPath}\addfirewall.bat";
process.StartInfo.UseShellExecute = false; //运行时隐藏dos窗口
process.StartInfo.CreateNoWindow = true; //运行时隐藏dos窗口
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.Verb = "runas"; //设置该启动动作,会以管理员权限运行进程
process.Start();
process.WaitForExit(5000);
string output = process.StandardOutput.ReadToEnd();
// 打印结果
Console.WriteLine(@"================================");
Console.WriteLine(@"添加防火墙规则:");
Console.WriteLine(output);
Console.ReadLine();
Console.WriteLine(@"================================");
}
catch (Exception ex)
{
ZLogHelper.Logerror.Error("添加防火墙失败", ex);
}
}

重定向标准错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/// <summary>
/// 获取视频时长
/// </summary>
/// <param name="sourceFile">视频地址</param>
/// <returns></returns>
public static string GetVideoDuration(string sourceFile)
{
string ffmpegpath = FfmpegPath;
string duration = "";
using (Process process = new Process())
{
process.StartInfo.UseShellExecute = false;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.FileName = ffmpegpath;
process.StartInfo.Arguments = "-i \"" + sourceFile + "\"";
process.StartInfo.CreateNoWindow = true; // 不显示程序窗口
process.Start();
bool exited = process.WaitForExit(5000);
if (!exited)
{
// 如果进程没有在5秒内结束,就终止进程
process.Kill();
}
string result = process.StandardError.ReadToEnd();
if (result.Contains("Duration: "))
{
duration = result.Substring(result.IndexOf("Duration: ", StringComparison.Ordinal) + ("Duration: ").Length, ("00:00:00").Length);
}
}
return duration;
}

这里重定向错误,因为时长信息在错误中。

重定向输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
_recordProcess = new Process
{
StartInfo =
{
FileName = FfmpegPath,
Arguments = $"{cmdStr}",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = false,
RedirectStandardError = false
}
};
_recordProcess?.Start();

结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (_recordProcess != null)
{
_recordProcess.StandardInput.WriteLine("q");
bool exited = _recordProcess.WaitForExit(5000);
if (!exited)
{
// 如果进程没有在5秒内结束,就终止进程
_recordProcess?.Kill();
_recordProcess?.Dispose();
_recordProcess = null;
}
else
{
_recordProcess?.Dispose();
}
}

退出方法对比

相关方法

对于Process的方法WaitForExit、Close、Kill、Dispose的作用

  • WaitForExit(): 等待关联进程退出为止。
  • Kill(): 结束关联进程,不会执行任何清理工作。
  • Close(): 关闭与进程的连接,但不会终止关联进程。调用Close()后,Process对象不再能够访问或控制该进程。
  • Dispose(): 释放与Process关联的资源。不会结束关联进程。

通常情况下,你不需要显式调用 Close() 或 Dispose() 方法。

当你调用 Process 类的 WaitForExit() 方法等待进程完成时,会自动释放进程相关的资源。

在进程完成后,相关的资源会被自动释放。

然而,如果你在代码中创建了很多进程对象,并且你希望在使用完之后尽快释放相关资源,可以考虑在适当的时机手动调用 Dispose() 方法。

例如,在一个处理大量进程的循环中,你可以在每次迭代结束后调用 Dispose() 方法来释放资源。

总而言之,大多数情况下不需要显式调用 Close() 方法,而是依靠自动资源释放机制。

如果需要手动释放资源,应该使用 Dispose() 方法。

注意

WaitForExitKill都能使进程关闭,关闭后要调用Dispose释放资源。

Close()Dispose()并不能关闭关联的进程。

WaitForExit后不用调用Close

常用的方式

正常退出并释放资源

1
2
3
process?.WaitForExit();
process?.Dispose();
process = null;

等待退出并释放资源

注意

使用WaitForExit的时候建议添加超时时间,返回值为超时的时候进程是否退出了,如果没有退出我们就要杀掉进程。

设置超时时间后,一定要根据返回值处理进程,否则可能导致进程不退出的情况!

设置超时时间后,一定要根据返回值处理进程,否则可能导致进程不退出的情况!

设置超时时间后,一定要根据返回值处理进程,否则可能导致进程不退出的情况!

示例

1
2
3
4
5
6
7
8
9
10
11
12
bool exited = process.WaitForExit(5000);
if (!exited)
{
// 如果进程没有在5秒内结束,就终止进程
process.Kill();
}
else
{
Console.WriteLine("进程已正常退出");
}
process?.Dispose();
process = null;

强制杀进程并释放资源

1
2
3
process?.Kill();   
process?.Dispose();
process=null;

结束关联并释放资源

这种方式并不能结束关联进程

1
2
3
process?.Close();  
process?.Dispose();
process = null;

注意

在调用Close()Kill()之后,强烈建议对关联的Process对象调用Dispose()方法,以释放相关资源。

也可以使用using自动Dispose()

1
2
3
4
5
6
using (Process process = new Process())
{
process.StartInfo = processStartInfo;
process.Start();
process.WaitForExit(5000);
}

Close和Dispose

Close()Dispose()方法都可用于关闭进程。

Close()方法是Process类的一个实例方法,用于关闭关联的进程和释放与之关联的资源,包括释放由进程打开的内部句柄和操作系统资源。

调用Close()方法后,进程将被关闭,并且不再可用。

Dispose()方法是Process类的一个公开实现的IDisposable接口方法,用于释放进程及其关联资源。调用Dispose()方法将会关闭关联的进程,并释放与之关联的资源,包括通过进程打开的任何文件、管道或其他资源。使用Dispose()方法可以确保资源被立即释放,而不等待垃圾回收器的回收。

区别在于,

Close()方法主要用于关闭进程,释放资源的工作将由垃圾回收器完成。

Dispose()方法会立即关闭进程,并手动释放与之关联的资源。