关于C#中获取父进程的那些实现(以及获取子进程)

1,702 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第23天,点击查看活动详情

注意:父进程并一定是进程的创建者,这一点要注意。

到 .NET 6 为止,似乎还没有获取父进程或子进程的跨平台的托管代码解决方案。更多参见Expose Parent/Child Process Information via System.Diagnostics.Process (#24423)介绍。

获取父进程

使用WMI

/// <summary>
/// 获取父进程。如果出错可能返回null
/// </summary>
/// <param name="process"></param>
/// <returns></returns>
public static Process GetParent(this Process process)
{
   try
   {
       //using (var query = new ManagementObjectSearcher("SELECT * FROM Win32_Process WHERE ProcessId=" + process.Id))
       using (var query = new ManagementObjectSearcher("root\\CIMV2", "SELECT ParentProcessId FROM Win32_Process WHERE ProcessId=" + process.Id))
       {
           return query
             .Get()
             .OfType<ManagementObject>()
             .Select(p => Process.GetProcessById((int)(uint)p["ParentProcessId"]))
             .FirstOrDefault();
       }
   }
   catch
   {
       return null;
   }
}

/// <summary>
/// 获取一个进程的父进程Id
/// </summary>
/// <param name="processId"></param>
/// <returns></returns>
public static int ParentProcessId(int processId)
{
   try
   {
       using (var query = new ManagementObjectSearcher("root\\CIMV2", "SELECT ParentProcessId FROM Win32_Process WHERE ProcessId=" + processId))
       {
           return query
             .Get()
             .OfType<ManagementObject>()
             .Select(p =>(int)(uint)p["ParentProcessId"])
             .FirstOrDefault();
       }
   }
   catch
   {
       return 0;
   }
}

或者,不使用linq查询的方式

/// <summary>
/// 获取父进程第2种方法
/// </summary>
/// <param name="process"></param>
/// <returns></returns>
public static Process GetParent2(this Process process)
{
    try
    {
        var queryStr = string.Format("SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {0}", process.Id);
        using (var search = new ManagementObjectSearcher("root\\CIMV2", queryStr))
        {
            var results = search.Get().GetEnumerator();
            results.MoveNext(); // 移动到下一个,并指示是否已经移动到了枚举的下一个对象。不执行,下面的获取将会报错。【应该是从未指向对象,移动到第一个】
            var queryObj = results.Current;
            var parentId = (uint)queryObj["ParentProcessId"];
            return Process.GetProcessById((int)parentId);
        }
    }
    catch
    {
        return null;
    }
}

使用系统内核函数 NtQueryInformationProcess/NtQueryInformationProcess64

NtQueryInformationProcess相对来说是一个性能较好的方法,但是MSDN官方文档也提到:[NtQueryInformationProcess may be altered or unavailable in future versions of Windows. Applications should use the alternate functions listed in this topic.] —— NtQueryInformationProcess function

因此可以考虑更换替代方法:CheckRemoteDebuggerPresentGetProcessId

同时,如果遇到32位和64位的问题,可以参考使用NtQueryInformationProcess64(文档中未记录的方法)

/// <summary>
/// A utility class to determine a process parent.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ParentProcessUtilities
{
    // These members must match PROCESS_BASIC_INFORMATION
    internal IntPtr Reserved1;
    internal IntPtr PebBaseAddress;
    internal IntPtr Reserved2_0;
    internal IntPtr Reserved2_1;
    internal IntPtr UniqueProcessId;
    internal IntPtr InheritedFromUniqueProcessId;

    [DllImport("ntdll.dll")]
    private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ParentProcessUtilities processInformation, int processInformationLength, out int returnLength);

    /// <summary>
    /// Gets the parent process of the current process.
    /// </summary>
    /// <returns>An instance of the Process class.</returns>
    public static Process GetParentProcess()
    {
        return GetParentProcess(Process.GetCurrentProcess().Handle);
    }

    /// <summary>
    /// Gets the parent process of specified process.
    /// </summary>
    /// <param name="id">The process id.</param>
    /// <returns>An instance of the Process class.</returns>
    public static Process GetParentProcess(int id)
    {
        Process process = Process.GetProcessById(id);
        return GetParentProcess(process.Handle);
    }

    /// <summary>
    /// Gets the parent process of a specified process.
    /// </summary>
    /// <param name="handle">The process handle.</param>
    /// <returns>An instance of the Process class.</returns>
    public static Process GetParentProcess(IntPtr handle)
    {
        ParentProcessUtilities pbi = new ParentProcessUtilities();
        int returnLength;
        int status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out returnLength);
        if (status != 0)
            throw new Win32Exception(status);

        try
        {
            return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
        }
        catch (ArgumentException)
        {
            // not found
            return null;
        }
    }
}

同时,This function has no associated import library. You must use the LoadLibrary and GetProcAddress functions to dynamically link to Ntdll.dll. 提到需要使用LoadLibraryGetProcAddress 加载,但是上面的DllImport,测试可以正常运行。如果有问题,可以考虑使用这两个方法

使用 CreateToolhelp32Snapshot 等内核函数、PROCESSENTRY32结构

CreateToolhelp32Snapshot获取进程、堆、模块或者线程等信息的快照。是一种工具方法,相对上面的方法,会安全方便些。

相对更推荐。

CreateToolhelp32Snapshot:用于获取进程、堆、模块或者线程等信息的快照。 拿到的不同快照信息需要使用对应函数去遍历。

/// <summary>
/// PInvoke获取父进程id 未测试
/// </summary>
/// <param name="process"></param>
/// <returns></returns>
public static int ParentProcessId_PInvoke(this Process process)
{
    return ParentProcessId_PInvoke(process.Id);
}
/// <summary>
/// PInvoke获取父进程id 未测试
/// </summary>
/// <param name="Id"></param>
/// <returns></returns>
public static int ParentProcessId_PInvoke(int Id)
{
    PROCESSENTRY32 pe32 = new PROCESSENTRY32 { };
    pe32.dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32));
    using (var hSnapshot = CreateToolhelp32Snapshot(SnapshotFlags.Process, (uint)Id))
    {
        if (hSnapshot.IsInvalid)
            throw new Win32Exception();

        if (!Process32First(hSnapshot, ref pe32))
        {
            int errno = Marshal.GetLastWin32Error();
            if (errno == ERROR_NO_MORE_FILES)
                return -1;
            throw new Win32Exception(errno);
        }
        do
        {
            if (pe32.th32ProcessID == (uint)Id)
                return (int)pe32.th32ParentProcessID;
        } while (Process32Next(hSnapshot, ref pe32));
    }
    return -1;
}
private const int ERROR_NO_MORE_FILES = 0x12;
[DllImport("kernel32.dll", SetLastError = true)]
private static extern SafeSnapshotHandle CreateToolhelp32Snapshot(SnapshotFlags flags, uint id);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool Process32First(SafeSnapshotHandle hSnapshot, ref PROCESSENTRY32 lppe);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool Process32Next(SafeSnapshotHandle hSnapshot, ref PROCESSENTRY32 lppe);

[Flags]
private enum SnapshotFlags : uint
{
    HeapList = 0x00000001,
    Process = 0x00000002,
    Thread = 0x00000004,
    Module = 0x00000008,
    Module32 = 0x00000010,
    All = (HeapList | Process | Thread | Module),
    Inherit = 0x80000000,
    NoHeaps = 0x40000000
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESSENTRY32
{
    public uint dwSize;
    public uint cntUsage;
    public uint th32ProcessID;
    public IntPtr th32DefaultHeapID;
    public uint th32ModuleID;
    public uint cntThreads;
    public uint th32ParentProcessID;
    public int pcPriClassBase;
    public uint dwFlags;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szExeFile;
};
[SuppressUnmanagedCodeSecurity, HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
internal sealed class SafeSnapshotHandle : SafeHandleMinusOneIsInvalid
{
    internal SafeSnapshotHandle() : base(true)
    {
    }

    [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
    internal SafeSnapshotHandle(IntPtr handle) : base(true)
    {
        base.SetHandle(handle);
    }

    protected override bool ReleaseHandle()
    {
        return CloseHandle(base.handle);
    }

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
    private static extern bool CloseHandle(IntPtr handle);
}

通过性能计数器获取

参考的 stackoverflow 中也给出了,通过性能计数器获取所有子父进程id的字典,从而获取父进程祖父进程id、获取父进程的办法。

但是,似乎有着很多限制,比如性能很差、本地化的版本中无法使用、用户需在性能用户组中等等。具体未进行测试,后续再了解。

获取子进程

仅获取子进程

/// <summary>
/// 仅获取子进程id
/// </summary>
/// <param name="pid">需要结束的进程ID</param>
public static int[] GetChildrenProcessIds(int pid)
{
    using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("Select ProcessID From Win32_Process Where ParentProcessID=" + pid))
    {
        ManagementObjectCollection moc = searcher.Get();
        var childPids = new int[moc.Count];
        var i = 0;
        foreach (ManagementObject mo in moc)
        {
            childPids[i]= Convert.ToInt32(mo["ProcessID"]);
            i++;
        }
        return childPids;
    }
}

参考