Tri*_*nko 19 .net process console-application break
这看起来像是一项不可能的任务.绝对没有我找到的作品.问题是如何干净地关闭以Process.Start启动的控制台应用程序,该控制器应用程序在没有控制台窗口且没有使用shell执行的情况下启动:( ProcessStartInfo.CreateNoWindow = true; ProcessStartInfo.UseShellExecute = false;
).
如果它接收到ctrl-c或ctrl-break信号,那么正在启动的应用程序将"干净地"关闭,但似乎没有办法发送一个有效的应用程序(特别是GenerateConsoleCtrlEvent).
任何能够提供接受"Process"对象的代码的人都将被标记为答案,该代码以上述参数开始,这些对象导致已启动进程的干净关闭而不影响调用进程.使用7z.exe(7-zip归档程序)作为示例控制台应用程序,它开始压缩大文件,如果没有干净地终止,将留下损坏的未完成文件.
直到有人提供了通向功能示例的功能例子或代码,这个问题没有答案.我见过几十个人在网上问这个问题和几十个答案,但都没有..NET似乎不支持干净地关闭控制台应用程序给定其进程ID,考虑到它是从.NET进程对象开始的,这很奇怪.部分问题是无法在新进程组中创建进程,这使得使用GenerateConsoleCtrlEvent无用.必须有一个解决方案.
小智 5
我花了几个小时试图自己解决这个问题。正如您所提到的,网络上充斥着根本行不通的答案。很多人建议使用 GenerateConsoleCtrlEvent,但他们不提供任何上下文,只提供无用的代码片段。下面的解决方案使用 GenerateConsoleCtrlEvent,但它有效。我已经测试过了。
请注意,这是一个 WinForms 应用程序,我正在启动和停止的进程是FFmpeg。我还没有用其他任何东西测试过这个解决方案。我在这里使用 FFmpeg 来录制视频并将输出保存到一个名为“video.mp4”的文件中。
下面的代码是我的 Form1.cs 文件的内容。这是在您创建 WinForms 解决方案时 Visual Studio 为您创建的文件。
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace ConsoleProcessShutdownDemo {
public partial class Form1 : Form {
BackgroundWorker worker;
Process currentProcess;
public Form1() {
InitializeComponent();
}
private void Worker_DoWork(object sender, DoWorkEventArgs e) {
const string outFile = "video.mp4";
var info = new ProcessStartInfo();
info.UseShellExecute = false;
info.CreateNoWindow = true;
info.FileName = "ffmpeg.exe";
info.Arguments = string.Format("-f gdigrab -framerate 60 -i desktop -crf 0 -pix_fmt yuv444p -preset ultrafast {0}", outFile);
info.RedirectStandardInput = true;
Process p = Process.Start(info);
worker.ReportProgress(-1, p);
}
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e) {
currentProcess = (Process)e.UserState;
}
private void btnStart_Click(object sender, EventArgs e) {
btnStart.Enabled = false;
btnStop.Enabled = true;
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.WorkerReportsProgress = true;
worker.DoWork += Worker_DoWork;
worker.ProgressChanged += Worker_ProgressChanged;
worker.RunWorkerAsync();
}
private void btnStop_Click(object sender, EventArgs e) {
btnStop.Enabled = false;
btnStart.Enabled = true;
if (currentProcess != null)
StopProgram(currentProcess);
}
//MAGIC BEGINS
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool FreeConsole();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);
[DllImport("Kernel32", SetLastError = true)]
private static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add);
enum CtrlTypes {
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
private delegate bool HandlerRoutine(CtrlTypes CtrlType);
public void StopProgram(Process proc) {
int pid = proc.Id;
FreeConsole();
if (AttachConsole((uint)pid)) {
SetConsoleCtrlHandler(null, true);
GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);
Thread.Sleep(2000);
FreeConsole();
SetConsoleCtrlHandler(null, false);
}
proc.WaitForExit();
proc.Close();
}
//MAGIC ENDS
}
Run Code Online (Sandbox Code Playgroud)
}
这有点晚了,所以你可能不再使用它,但也许它会帮助其他人......
你想太多了。问题是您只能从共享同一控制台的进程发出中断信号 - 解决方案应该相当明显。
创建一个控制台项目。该项目将以通常的方式启动目标应用程序:
var psi = new ProcessStartInfo();
psi.FileName = @"D:\Test\7z\7z.exe";
psi.WorkingDirectory = @"D:\Test\7z\";
psi.Arguments = "a output.7z input.bin";
psi.UseShellExecute = false;
var process = Process.Start(psi);
Run Code Online (Sandbox Code Playgroud)
UseShellExecute
是重要的部分 - 这确保两个应用程序将共享同一个控制台。
这允许您将中断发送到辅助应用程序,该应用程序也将传递到托管应用程序:
Console.CancelKeyPress += (s, e) => e.Cancel = true;
Thread.Sleep(1000);
GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, 0);
Run Code Online (Sandbox Code Playgroud)
这将在启动后一秒钟破坏托管应用程序。轻松、安全。这CancelKeyPress
不是必需的 - 我只是将其放在那里,以表明您可以中断托管进程,但仍然继续运行。在真正的帮助应用程序中,这可以用于某些通知或类似的东西,但这并不是真正需要的。
现在,您只需要一种方法来向帮助应用程序发出信号以发出中断命令 - 最简单的方法是仅使用简单的控制台输入,但这可能会干扰托管应用程序。如果这不适合您,一个简单的互斥锁就可以正常工作:
using (var mutex = Mutex.OpenExisting(args[0]))
using (var processWaitHandle = new SafeWaitHandle(process.Handle, false))
using (var processMre = new ManualResetEvent(false) { SafeWaitHandle = processWaitHandle })
{
var which = WaitHandle.WaitAny(new WaitHandle[] { mutex, processMre });
if (which == 0)
{
Console.WriteLine("Got signalled.");
GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, 0);
}
else if (which == 1)
{
Console.WriteLine("Exitted normally.");
}
}
Run Code Online (Sandbox Code Playgroud)
这将等待互斥体上的信号,或者等待托管应用程序退出。要启动帮助程序应用程序,您需要做的就是:
var mutexName = Guid.NewGuid().ToString();
mutex = new Mutex(true, mutexName);
var process = Process.Start(@"TestBreak.exe", mutexName);
Run Code Online (Sandbox Code Playgroud)
要发出中断,只需释放互斥体:
mutex.ReleaseMutex();
Run Code Online (Sandbox Code Playgroud)
就是这样。如果您需要更严格的控制,使用命名管道之类的东西可能是更好的选择,但如果您只需要中断信号,则互斥体就可以了。您需要传递的任何参数都可以作为参数传递给帮助程序应用程序,您甚至可以使用 shell 脚本来完成此操作(只需使用帮助程序应用程序来运行您需要运行和中断的任何内容)。
归档时间: |
|
查看次数: |
5055 次 |
最近记录: |