[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
Excel.Application app = new Excel.ApplicationClass();
uint pid = 0;
Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
job.AddProcess(Process.GetProcessById((int)pid).Handle);
1
这篇文章旨在作为@Matt Howells答案的扩展,特别是对于那些在 Vista or Win7 下使用Job Objects时遇到问题的人,特别是如果在调用AssignProcessToJobObject时遇到访问被拒绝错误('5') .
tl;dr
要确保与Vista和Win7的兼容性,请将以下清单添加到.NET父进程:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<v3:trustInfo xmlns:v3="urn:schemas-microsoft-com:asm.v3">
<v3:security>
<v3:requestedPrivileges>
<v3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
</v3:requestedPrivileges>
</v3:security>
</v3:trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<!-- We specify these, in addition to the UAC above, so we avoid Program Compatibility Assistant in Vista and Win7 -->
<!-- We try to avoid PCA so we can use Windows Job Objects -->
<!-- See https://stackoverflow.com/questions/3342941/kill-child-process-when-parent-process-is-killed -->
<application>
<!--The ID below indicates application support for Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!--The ID below indicates application support for Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
</assembly>
请注意,在Visual Studio 2012中添加新清单时,它将包含上面的代码段,因此您无需从侦听中复制它 . 它还将包含Windows 8的节点 .
我在调用 CreateJobObject 时遇到的堆栈不 balancer 异常(使用Windows 10,Visual Studio 2015,32位) .
命名作业,例如,如果您使用SysInternals,则可以轻松找到它 .
有一个更简单的API和更少的代码 .
以下是使用此代码的方法:
// Get a Process object somehow.
Process process = Process.Start(exePath, args);
// Add the Process to ChildProcessTracker.
ChildProcessTracker.AddProcess(process);
/// <summary>
/// Allows processes to be automatically killed if this parent process unexpectedly quits.
/// This feature requires Windows 8 or greater. On Windows 7, nothing is done.</summary>
/// <remarks>References:
/// https://stackoverflow.com/a/4657392/386091
/// https://stackoverflow.com/a/9164742/386091 </remarks>
public static class ChildProcessTracker
{
/// <summary>
/// Add the process to be tracked. If our current process is killed, the child processes
/// that we are tracking will be automatically killed, too. If the child process terminates
/// first, that's fine, too.</summary>
/// <param name="process"></param>
public static void AddProcess(Process process)
{
if (s_jobHandle != IntPtr.Zero)
{
bool success = AssignProcessToJobObject(s_jobHandle, process.Handle);
if (!success && !process.HasExited)
throw new Win32Exception();
}
}
static ChildProcessTracker()
{
// This feature requires Windows 8 or later. To support Windows 7 requires
// registry settings to be added if you are using Visual Studio plus an
// app.manifest change.
// https://stackoverflow.com/a/4232259/386091
// https://stackoverflow.com/a/9507862/386091
if (Environment.OSVersion.Version < new Version(6, 2))
return;
// The job name is optional (and can be null) but it helps with diagnostics.
// If it's not null, it has to be unique. Use SysInternals' Handle command-line
// utility: handle -a ChildProcessTracker
string jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id;
s_jobHandle = CreateJobObject(IntPtr.Zero, jobName);
var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
// This is the key flag. When our process is killed, Windows will automatically
// close the job handle, and when that happens, we want the child processes to
// be killed, too.
info.LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
extendedInfo.BasicLimitInformation = info;
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
try
{
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation,
extendedInfoPtr, (uint)length))
{
throw new Win32Exception();
}
}
finally
{
Marshal.FreeHGlobal(extendedInfoPtr);
}
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name);
[DllImport("kernel32.dll")]
static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType,
IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
// Windows will automatically close any open job handles when our process terminates.
// This can be verified by using SysInternals' Handle utility. When the job handle
// is closed, the child processes will be killed.
private static readonly IntPtr s_jobHandle;
}
public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public Int64 PerProcessUserTimeLimit;
public Int64 PerJobUserTimeLimit;
public JOBOBJECTLIMIT LimitFlags;
public UIntPtr MinimumWorkingSetSize;
public UIntPtr MaximumWorkingSetSize;
public UInt32 ActiveProcessLimit;
public Int64 Affinity;
public UInt32 PriorityClass;
public UInt32 SchedulingClass;
}
[Flags]
public enum JOBOBJECTLIMIT : uint
{
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
}
[StructLayout(LayoutKind.Sequential)]
public struct IO_COUNTERS
{
public UInt64 ReadOperationCount;
public UInt64 WriteOperationCount;
public UInt64 OtherOperationCount;
public UInt64 ReadTransferCount;
public UInt64 WriteTransferCount;
public UInt64 OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UIntPtr ProcessMemoryLimit;
public UIntPtr JobMemoryLimit;
public UIntPtr PeakProcessMemoryUsed;
public UIntPtr PeakJobMemoryUsed;
}
Process childProcess = new Process();
childProcess.StartInfo = new ProcessStartInfo("pathToConsoleModeApp.exe");
childProcess.StartInfo.RedirectStandardInput = true;
childProcess.StartInfo.CreateNoWindow = true; // no sense showing an empty black console window which the user can't input into
using System;
using System.IO;
using System.Threading;
using System.Windows.Forms;
static int Main()
{
Application.Run(new MyApplicationContext());
return 0;
}
public class MyApplicationContext : ApplicationContext
{
private SynchronizationContext _mainThreadMessageQueue = null;
private Stream _stdInput;
public MyApplicationContext()
{
_stdInput = Console.OpenStandardInput();
// feel free to use a better way to post to the message loop from here if you know one ;)
System.Windows.Forms.Timer handoffToMessageLoopTimer = new System.Windows.Forms.Timer();
handoffToMessageLoopTimer.Interval = 1;
handoffToMessageLoopTimer.Tick += new EventHandler((obj, eArgs) => { PostMessageLoopInitialization(handoffToMessageLoopTimer); });
handoffToMessageLoopTimer.Start();
}
private void PostMessageLoopInitialization(System.Windows.Forms.Timer t)
{
if (_mainThreadMessageQueue == null)
{
t.Stop();
_mainThreadMessageQueue = SynchronizationContext.Current;
}
// constantly monitor standard input on a background thread that will
// signal the main thread when stuff happens.
BeginMonitoringStdIn(null);
// start up your application's real work here
}
private void BeginMonitoringStdIn(object state)
{
if (SynchronizationContext.Current == _mainThreadMessageQueue)
{
// we're already running on the main thread - proceed.
var buffer = new byte[128];
_stdInput.BeginRead(buffer, 0, buffer.Length, (asyncResult) =>
{
int amtRead = _stdInput.EndRead(asyncResult);
if (amtRead == 0)
{
_mainThreadMessageQueue.Post(new SendOrPostCallback(ApplicationTeardown), null);
}
else
{
BeginMonitoringStdIn(null);
}
}, null);
}
else
{
// not invoked from the main thread - dispatch another call to this method on the main thread and return
_mainThreadMessageQueue.Post(new SendOrPostCallback(BeginMonitoringStdIn), null);
}
}
private void ApplicationTeardown(object state)
{
// tear down your application gracefully here
_stdInput.Close();
this.ExitThread();
}
}
注意这种方法:
启动的实际子.exe必须是控制台应用程序,因此它仍然附加到stdin / out / err . 如上例所示,我通过创建一个引用现有项目的小型控制台项目,实例化我的应用程序上下文并在 Main 方法中调用 Application.Run() ,轻松调整了我现有的使用消息泵(但未显示GUI)的应用程序 . 控制台.exe .
在这里,您有一个实用程序类,它启动一个新进程并为其附加一个调试器 . 它由罗杰·克纳普改编自this post . 唯一的要求是两个进程需要共享相同的位数 . 您无法从64位进程调试32位进程,反之亦然 .
public class ProcessRunner
{
#region "API imports"
private const int DBG_CONTINUE = 0x00010002;
private const int DBG_EXCEPTION_NOT_HANDLED = unchecked((int) 0x80010001);
private enum DebugEventType : int
{
CREATE_PROCESS_DEBUG_EVENT = 3,
//Reports a create-process debugging event. The value of u.CreateProcessInfo specifies a CREATE_PROCESS_DEBUG_INFO structure.
CREATE_THREAD_DEBUG_EVENT = 2,
//Reports a create-thread debugging event. The value of u.CreateThread specifies a CREATE_THREAD_DEBUG_INFO structure.
EXCEPTION_DEBUG_EVENT = 1,
//Reports an exception debugging event. The value of u.Exception specifies an EXCEPTION_DEBUG_INFO structure.
EXIT_PROCESS_DEBUG_EVENT = 5,
//Reports an exit-process debugging event. The value of u.ExitProcess specifies an EXIT_PROCESS_DEBUG_INFO structure.
EXIT_THREAD_DEBUG_EVENT = 4,
//Reports an exit-thread debugging event. The value of u.ExitThread specifies an EXIT_THREAD_DEBUG_INFO structure.
LOAD_DLL_DEBUG_EVENT = 6,
//Reports a load-dynamic-link-library (DLL) debugging event. The value of u.LoadDll specifies a LOAD_DLL_DEBUG_INFO structure.
OUTPUT_DEBUG_STRING_EVENT = 8,
//Reports an output-debugging-string debugging event. The value of u.DebugString specifies an OUTPUT_DEBUG_STRING_INFO structure.
RIP_EVENT = 9,
//Reports a RIP-debugging event (system debugging error). The value of u.RipInfo specifies a RIP_INFO structure.
UNLOAD_DLL_DEBUG_EVENT = 7,
//Reports an unload-DLL debugging event. The value of u.UnloadDll specifies an UNLOAD_DLL_DEBUG_INFO structure.
}
[StructLayout(LayoutKind.Sequential)]
private struct DEBUG_EVENT
{
[MarshalAs(UnmanagedType.I4)] public DebugEventType dwDebugEventCode;
public int dwProcessId;
public int dwThreadId;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)] public byte[] bytes;
}
[DllImport("Kernel32.dll", SetLastError = true)]
private static extern bool DebugActiveProcess(int dwProcessId);
[DllImport("Kernel32.dll", SetLastError = true)]
private static extern bool WaitForDebugEvent([Out] out DEBUG_EVENT lpDebugEvent, int dwMilliseconds);
[DllImport("Kernel32.dll", SetLastError = true)]
private static extern bool ContinueDebugEvent(int dwProcessId, int dwThreadId, int dwContinueStatus);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool IsDebuggerPresent();
#endregion
public Process ChildProcess { get; set; }
public bool StartProcess(string fileName)
{
var processStartInfo = new ProcessStartInfo(fileName)
{
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Normal,
ErrorDialog = false
};
this.ChildProcess = Process.Start(processStartInfo);
if (ChildProcess == null)
return false;
new Thread(NullDebugger) {IsBackground = true}.Start(ChildProcess.Id);
return true;
}
private void NullDebugger(object arg)
{
// Attach to the process we provided the thread as an argument
if (DebugActiveProcess((int) arg))
{
var debugEvent = new DEBUG_EVENT {bytes = new byte[1024]};
while (!this.ChildProcess.HasExited)
{
if (WaitForDebugEvent(out debugEvent, 1000))
{
// return DBG_CONTINUE for all events but the exception type
var continueFlag = DBG_CONTINUE;
if (debugEvent.dwDebugEventCode == DebugEventType.EXCEPTION_DEBUG_EVENT)
continueFlag = DBG_EXCEPTION_NOT_HANDLED;
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, continueFlag);
}
}
}
else
{
//we were not able to attach the debugger
//do the processes have the same bitness?
//throw ApplicationException("Unable to attach debugger") // Kill child? // Send Event? // Ignore?
}
}
}
用法:
new ProcessRunner().StartProcess("c:\\Windows\\system32\\calc.exe");
private void KillProcess(Process proc)
{
var job = new Job();
job.AddProcess(proc.Handle);
job.Close();
}
0
只是我的2018年版 . 将它用在Main()方法旁边 .
using System.Management;
using System.Diagnostics;
...
// Called when the Main Window is closed
protected override void OnClosed(EventArgs EventArgs)
{
string query = "Select * From Win32_Process Where ParentProcessId = " + Process.GetCurrentProcess().Id;
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
ManagementObjectCollection processList = searcher.Get();
foreach (var obj in processList)
{
object data = obj.Properties["processid"].Value;
if (data != null)
{
// retrieve the process
var childId = Convert.ToInt32(data);
var childProcess = Process.GetProcessById(childId);
// ensure the current process is still live
if (childProcess != null) childProcess.Kill();
}
}
Environment.Exit(0);
}
12 回答
从this forum,贷记到'Josh' .
Application.Quit()
和Process.Kill()
是可能的解决方案,但已被证明是不可靠的 . 当您的主应用程序死亡时,您仍然会继续运行子进程 . 我们真正想要的是,一旦主要过程消失,子进程就会死亡 .解决方案是使用"job objects" http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx .
我们的想法是为您的主应用程序创建一个“作业对象”,并使用作业对象注册您的子进程 . 如果主进程终止,操作系统将负责终止子进程 .
看着构造函数......
这里的关键是正确设置作业对象 . 在构造函数中,我将"limits"设置为0x2000,这是
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
的数值 .MSDN将此标志定义为:
一旦设置了这个类......你只需要在每个子进程中注册该作业 . 例如:
这篇文章旨在作为@Matt Howells答案的扩展,特别是对于那些在 Vista or Win7 下使用Job Objects时遇到问题的人,特别是如果在调用AssignProcessToJobObject时遇到访问被拒绝错误('5') .
tl;dr
要确保与Vista和Win7的兼容性,请将以下清单添加到.NET父进程:
请注意,在Visual Studio 2012中添加新清单时,它将包含上面的代码段,因此您无需从侦听中复制它 . 它还将包含Windows 8的节点 .
full explanation
如果您启动的进程已与另一个作业关联,则您的作业关联将失败并显示拒绝访问错误 . 进入程序兼容性助手,从Windows Vista开始,将所有类型的进程分配给自己的作业 .
在Vista中,您可以通过简单地包含应用程序清单来标记您的应用程序被排除在PCA之外 . Visual Studio似乎会自动为.NET应用程序执行此操作,因此您可以在那里使用 .
一个简单的清单不再在Win7中削减它 . [1]在那里,您必须明确指定您与清单中的标记兼容Win7 . [2]
这让我担心Windows 8.我是否必须再次更改我的清单?显然 Cloud 中断了,因为Windows 8现在允许进程属于多个作业 . [3]所以我还没有测试过它,但我想如果你只是包含一个带有支持的OS信息的清单,那么这种疯狂现在就会结束 .
Tip 1 :如果您正在使用Visual Studio开发.NET应用程序,那么[4]是关于如何自定义应用程序清单的一些很好的说明 .
Tip 2 :从Visual Studio启动应用程序时要小心 . 我发现,在添加适当的清单后,从Visual Studio启动时我仍然遇到PCA问题,即使我使用Start而不调试 . 但是,从资源管理器启动我的应用程序 . 使用注册表手动添加devenv以从PCA中排除后,启动使用VS中的Job Objects的应用程序也开始工作 . [5]
Tip 3 :如果您想知道PCA是否是您的问题,请尝试从命令行启动应用程序,或将程序复制到网络驱动器并从那里运行 . PCA在这些上下文中自动禁用 .
[1] http://blogs.msdn.com/b/cjacks/archive/2009/06/18/pca-changes-for-windows-7-how-to-tell-us-you-are-not-an-installer-take-2-because-we-changed-the-rules-on-you.aspx
[2] http://ayende.com/blog/4360/how-to-opt-out-of-program-compatibility-assistant
[3] http://msdn.microsoft.com/en-us/library/windows/desktop/ms681949(v=vs.85).aspx:"A process can be associated with more than one job in Windows 8"
[4] How can I embed an application manifest into an application using VS2008?
[5] How to stop the Visual Studio debugger starting my process in a job object?
这个答案以@Matt Howells' excellent answer加上其他答案开头(参见下面代码中的链接) . 改进:
支持32位和64位 .
解决了@Matt Howells的回答中的一些问题:
extendedInfoPtr
的小内存泄漏'Win32'编译错误,和
我在调用
CreateJobObject
时遇到的堆栈不 balancer 异常(使用Windows 10,Visual Studio 2015,32位) .命名作业,例如,如果您使用SysInternals,则可以轻松找到它 .
有一个更简单的API和更少的代码 .
以下是使用此代码的方法:
要支持Windows 7,需要:
一个简单的app.manifest更改为@adam smith describes .
Registry settings to be added如果您使用的是Visual Studio .
在我的情况下,我不需要支持Windows 7,所以我在下面的静态构造函数的顶部进行了简单的检查 .
我通过以编程方式比较托管版本和本机版本(整体大小以及每个成员的偏移量)仔细测试了32位和64位版本的结构 .
我在Windows 7,8和10上测试了这段代码 .
当您控制子进程运行的代码时,这可能适用于某些人 . 这种方法的好处是它不需要任何本机Windows调用 .
基本思想是将子标准输入重定向到另一端连接到父级的流,并使用该流检测父级何时消失 . 当您使用
System.Diagnostics.Process
启动子项时,很容易确保其标准输入被重定向:然后,在子进程上,利用标准输入流中的
Read
将始终以至少1个字节返回的事实,直到流关闭,然后它们将开始返回0个字节 . 我最终做到这一点的方式概述如下;我的方式也使用消息泵来保持主线程可用于除了观看标准之外的其他东西,但是这种通用方法也可以在没有消息泵的情况下使用 .注意这种方法:
启动的实际子.exe必须是控制台应用程序,因此它仍然附加到stdin / out / err . 如上例所示,我通过创建一个引用现有项目的小型控制台项目,实例化我的应用程序上下文并在
Main
方法中调用Application.Run()
,轻松调整了我现有的使用消息泵(但未显示GUI)的应用程序 . 控制台.exe .从技术上讲,这只是在父进程退出时发出子进程的信号,因此无论父进程是正常退出还是崩溃,它都会起作用,但仍然由子进程执行自己的关闭 . 这可能是也可能不是你想要的......
一种方法是将父进程的PID传递给子进程 . 如果具有指定pid的进程存在,则子进程将定期轮询 . 如果不是它就会退出 .
您还可以在子方法中使用Process.WaitForExit方法在父进程结束时收到通知,但在任务管理器的情况下可能不起作用 .
还有另一种相关方法,简单有效,可以在程序终止时完成子进程 . 你可以从父母那里实现和 attach a debugger ;当父进程结束时,子进程将被操作系统杀死 . 它可以通过两种方式将调试器附加到子节点的父节点(请注意,您一次只能附加一个调试器) . 你可以找到关于这个主题的更多信息here .
在这里,您有一个实用程序类,它启动一个新进程并为其附加一个调试器 . 它由罗杰·克纳普改编自this post . 唯一的要求是两个进程需要共享相同的位数 . 您无法从64位进程调试32位进程,反之亦然 .
用法:
我正在寻找一个不需要非托管代码的问题的解决方案 . 我也无法使用标准输入/输出重定向,因为它是Windows窗体应用程序 .
我的解决方案是在父进程中创建一个命名管道,然后将子进程连接到同一个管道 . 如果父进程退出,则管道被破坏,子进程可以检测到这一点 .
以下是使用两个控制台应用程序的示例:
家长
孩子
使用 event handlers 在几个退出场景中挂钩:
我看到两个选择:
如果您确切知道可以启动哪些子进程,并且您确定它们只是从您的主进程启动,那么您可以考虑只是通过名称搜索它们并杀死它们 .
遍历所有进程并终止将进程作为父进程的每个进程(我猜你需要先杀死子进程) . Here解释了如何获取父进程ID .
我创建了一个子进程管理库,其中由于双向WCF管道而监视父进程和子进程 . 如果子进程终止或父进程终止,则通知彼此 . 还有一个调试器助手可以自动连接VS调试器到已启动的子进程
项目现场:
http://www.crawler-lib.net/child-processes
NuGet包:
https://www.nuget.org/packages/ChildProcesses https://www.nuget.org/packages/ChildProcesses.VisualStudioDebug/
调用job.AddProcess在开始进程后做得更好:
在终止之前调用AddProcess时,子进程不会被终止 . (Windows 7 SP1)
只是我的2018年版 . 将它用在Main()方法旁边 .