Comment obtenir le processus parent dans .NET de manière gérée


85

Je cherchais beaucoup une méthode pour obtenir le processus parent dans .NET, mais je n'ai trouvé que la manière P / Invoke.


5
Que se passe-t-il lorsque plusieurs instances de votre processus sont en cours d'exécution, puisqu'elles auront toutes le même ProcessName?
Michael Burr

1
Au cas où cela aiderait quelqu'un d'autre: personnellement, j'avais juste besoin de l'ID de processus parent. Les solutions ci-dessous de Michael Hale et Simon Mourier ne fonctionnent pas si le processus parent s'est arrêté car ils appellent Process.GetProcessById()avec un ID d'un ID de processus (maintenant) inexistant. Mais à ce stade, vous avez l'ID de processus du parent, vous pouvez donc l'utiliser si vous en avez besoin comme je l'ai fait.
Tyler Collier


Que diriez-vous d'envoyer l'ID de processus parent comme argument de ligne de commande? :)
John Demetriou

Réponses:


62

Ce code fournit une interface agréable pour trouver l'objet de processus Parent et prend en compte la possibilité de plusieurs processus avec le même nom:

Usage:

Console.WriteLine("ParentPid: " + Process.GetProcessById(6972).Parent().Id);

Code:

public static class ProcessExtensions {
    private static string FindIndexedProcessName(int pid) {
        var processName = Process.GetProcessById(pid).ProcessName;
        var processesByName = Process.GetProcessesByName(processName);
        string processIndexdName = null;

        for (var index = 0; index < processesByName.Length; index++) {
            processIndexdName = index == 0 ? processName : processName + "#" + index;
            var processId = new PerformanceCounter("Process", "ID Process", processIndexdName);
            if ((int) processId.NextValue() == pid) {
                return processIndexdName;
            }
        }

        return processIndexdName;
    }

    private static Process FindPidFromIndexedProcessName(string indexedProcessName) {
        var parentId = new PerformanceCounter("Process", "Creating Process ID", indexedProcessName);
        return Process.GetProcessById((int) parentId.NextValue());
    }

    public static Process Parent(this Process process) {
        return FindPidFromIndexedProcessName(FindIndexedProcessName(process.Id));
    }
}

2
Où la méthode est-elle float.Asdéfinie?
Mark Byers

22
Ce sont des méthodes étonnamment mal nommées.
Mark

4
Dans mes tests, c'est beaucoup plus lent que la solution de Simon Mourier. En outre, il fait malheureusement une sorte de mécanisme de «mise en avant du processus». Je ne sais pas pourquoi. Quelqu'un d'autre a-t-il vécu cela? Le test que j'exécute pour cela est un EXE de bootstrapper d'installation créé par Visual Studio qui lance le programme d'installation Windows MSIEXEC.exe.
Tyler Collier

6
Malheureusement, cela ne fonctionne pas lorsque le nom de la catégorie du compteur de performances est localisé (par exemple sous Windows non anglais).
LukeSw

5
Je suggérerais la version de Simon à moins qu'il n'y ait une raison pressante de ne pas le faire, car la différence de performance est significative.
David Burton

150

Voici une solution. Il utilise p / invoke, mais semble bien fonctionner, 32 ou 64 cpu:

    /// <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;
            }
        }
    }

13
Il est en fait géré, mais pas portable sur un autre système d'exploitation que Windows, vous avez raison. Cependant, la notion de processus parent n'est pas non plus portable, car ce n'est pas dans le .NET Framework lui-même, donc je ne pense pas que ce soit un gros problème.
Simon Mourier

11
Génial! Pas de compteurs de perf lents. Je déteste vraiment les commentaires «non gérés». Comment l'interrogation d'un compteur de performances est-elle plus gérée qu'avec P / Invoke?
Jabe

5
Malheureusement, cette fonction est uniquement interne. MSDN indique que "[NtQueryInformationProcess peut être modifié ou indisponible dans les futures versions de Windows. Les applications doivent utiliser les autres fonctions répertoriées dans cette rubrique.]" Msdn.microsoft.com/en-us/library/windows/desktop/…
justin. m.chase

21
@ justin.m.chase - Il existe depuis presque 20 ans, donc je doute qu'il sera supprimé demain, et il n'y a pas de fonctions NT alternatives qui donnent le processus parent à ma connaissance, mais oui, bien sûr, utilisez à vos propres risques .
Simon Mourier

4
Cette méthode est au moins 10 fois plus rapide lorsque j'ai comparé les performances de cette méthode à d'autres méthodes. La réponse acceptée tiques: 2600657. Cette réponse tiques: 8454.
Mojtaba Rezaeian

9

Par ici:

public static Process GetParent(this Process process)
{
  try
  {
    using (var query = new ManagementObjectSearcher(
      "SELECT * " +
      "FROM Win32_Process " +
      "WHERE ProcessId=" + process.Id))
    {
      return query
        .Get()
        .OfType<ManagementObject>()
        .Select(p => Process.GetProcessById((int)(uint)p["ParentProcessId"]))
        .FirstOrDefault();
    }
  }
  catch
  {
    return null;
  }
}

2
Fonctionne, mais WMI peut être très lent (secondes) .pinvoke est la voie à suivre.
Alastair Maw

4

Voici mon essai d'une solution gérée.

Il interroge les compteurs de performance pour tous les processus et renvoie un dictionnaire de PID enfant au PID parent. Ensuite, vous pouvez consulter le dictionnaire avec votre PID actuel pour voir vos parents, grands-parents, etc.

La quantité d'informations qu'il obtient est exagérée, c'est certain. N'hésitez pas à optimiser.

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace PidExamples
{
    class ParentPid
    {
        static void Main(string[] args)
        {
            var childPidToParentPid = GetAllProcessParentPids();
            int currentProcessId = Process.GetCurrentProcess().Id;

            Console.WriteLine("Current Process ID: " + currentProcessId);
            Console.WriteLine("Parent Process ID: " + childPidToParentPid[currentProcessId]);
        }

        public static Dictionary<int, int> GetAllProcessParentPids()
        {
            var childPidToParentPid = new Dictionary<int, int>();

            var processCounters = new SortedDictionary<string, PerformanceCounter[]>();
            var category = new PerformanceCounterCategory("Process");

            // As the base system always has more than one process running, 
            // don't special case a single instance return.
            var instanceNames = category.GetInstanceNames();
            foreach(string t in instanceNames)
            {
                try
                {
                    processCounters[t] = category.GetCounters(t);
                }
                catch (InvalidOperationException)
                {
                    // Transient processes may no longer exist between 
                    // GetInstanceNames and when the counters are queried.
                }
            }

            foreach (var kvp in processCounters)
            {
                int childPid = -1;
                int parentPid = -1;

                foreach (var counter in kvp.Value)
                {
                    if ("ID Process".CompareTo(counter.CounterName) == 0)
                    {
                        childPid = (int)(counter.NextValue());
                    }
                    else if ("Creating Process ID".CompareTo(counter.CounterName) == 0)
                    {
                        parentPid = (int)(counter.NextValue());
                    }
                }

                if (childPid != -1 && parentPid != -1)
                {
                    childPidToParentPid[childPid] = parentPid;
                }
            }

            return childPidToParentPid;
        }
    }
}    

Par ailleurs, j'ai appris combien de compteurs de performance il y avait sur ma machine: 13401. Holy cow.


2
Cette méthode fonctionne mais semble extrêmement lente. Cela a pris plus de 10 secondes dans ma machine.
Karsten

3

Si vous acceptez P / Invoke, il existe un meilleur moyen, plus documenté que NtQueryInformationProcess: à savoir PROCESSENTRY32 (CreateToolhelp32Snapshot, Process32First, Process32Next). C'est montré dans ce post .

Faites attention aux détails subtils et notez que le PID parent n'est pas nécessairement le PID du créateur, en fait, ceux-ci peuvent être totalement indépendants, comme indiqué par les commentaires de la communauté sur PROCESSENTRY32 .


2

Si vous avez déjà creusé la BCL, vous constaterez que les moyens de trouver le processus parent sont délibérément évités, prenez ceci par exemple:

https://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/ProcessManager.cs,327

Comme vous pouvez le voir dans le code source, il contient des structures complètes et des méthodes natives importées qui sont absolument suffisantes pour faire le travail. Cependant, même si vous y accédez par réflexion (c'est possible), vous ne trouverez pas de méthode pour le faire directement. Je ne peux pas répondre pourquoi, pourtant ce phénomène provoque des questions comme les vôtres sont posées à plusieurs reprises; par exemple:

Comment puis-je obtenir le PID du processus parent de ma demande

Car il n'y a pas de réponse avec du code utilisant CreateToolhelp32Snapshot dans ce fil, je l'ajouterais - une partie des définitions de structure et des noms que je vole de la source de référence MS :)

  • Code

    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Collections.Generic;
    using System.Linq;
    using System;
    

    public static class Toolhelp32 {
        public const uint Inherit = 0x80000000;
        public const uint SnapModule32 = 0x00000010;
        public const uint SnapAll = SnapHeapList|SnapModule|SnapProcess|SnapThread;
        public const uint SnapHeapList = 0x00000001;
        public const uint SnapProcess = 0x00000002;
        public const uint SnapThread = 0x00000004;
        public const uint SnapModule = 0x00000008;
    
        [DllImport("kernel32.dll")]
        static extern bool CloseHandle(IntPtr handle);
        [DllImport("kernel32.dll")]
        static extern IntPtr CreateToolhelp32Snapshot(uint flags, int processId);
    
        public static IEnumerable<T> TakeSnapshot<T>(uint flags, int id) where T : IEntry, new() {
            using(var snap = new Snapshot(flags, id))
                for(IEntry entry = new T { }; entry.TryMoveNext(snap, out entry);)
                    yield return (T)entry;
        }
    
        public interface IEntry {
            bool TryMoveNext(Toolhelp32.Snapshot snap, out IEntry entry);
        }
    
        public struct Snapshot:IDisposable {
            void IDisposable.Dispose() {
                Toolhelp32.CloseHandle(m_handle);
            }
            public Snapshot(uint flags, int processId) {
                m_handle=Toolhelp32.CreateToolhelp32Snapshot(flags, processId);
            }
            IntPtr m_handle;
        }
    }
    

    [StructLayout(LayoutKind.Sequential)]
    public struct WinProcessEntry:Toolhelp32.IEntry {
        [DllImport("kernel32.dll")]
        public static extern bool Process32Next(Toolhelp32.Snapshot snap, ref WinProcessEntry entry);
    
        public bool TryMoveNext(Toolhelp32.Snapshot snap, out Toolhelp32.IEntry entry) {
            var x = new WinProcessEntry { dwSize=Marshal.SizeOf(typeof(WinProcessEntry)) };
            var b = Process32Next(snap, ref x);
            entry=x;
            return b;
        }
    
        public int dwSize;
        public int cntUsage;
        public int th32ProcessID;
        public IntPtr th32DefaultHeapID;
        public int th32ModuleID;
        public int cntThreads;
        public int th32ParentProcessID;
        public int pcPriClassBase;
        public int dwFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public String fileName;
        //byte fileName[260];
        //public const int sizeofFileName = 260;
    }
    

    public static class Extensions {
        public static Process Parent(this Process p) {
            var entries = Toolhelp32.TakeSnapshot<WinProcessEntry>(Toolhelp32.SnapAll, 0);
            var parentid = entries.First(x => x.th32ProcessID==p.Id).th32ParentProcessID;
            return Process.GetProcessById(parentid);
        }
    }
    

Et nous pouvons l'utiliser comme:

  • Tester

    public class TestClass {
        public static void TestMethod() {
            var p = Process.GetCurrentProcess().Parent();
            Console.WriteLine("{0}", p.Id);
        }
    }
    

Pour une fin alternative.

Selon la documentation, il existe une paire de méthodes d'itération par type d'entrées telles que Process32Firstet Process32Nextsont pour l'itération des processus; mais j'ai trouvé que les méthodes `xxxxFirst 'n'étaient pas nécessaires, puis j'ai pensé pourquoi ne pas mettre la méthode d'itération avec son type d'entrée correspondant? Ce serait plus facile à mettre en œuvre et à être compris (je suppose que oui ..).

Tout comme Toolhelp32avec l' aide d'un suffixe , je pense qu'une classe d'assistance statique est appropriée, de sorte que nous puissions avoir les noms qualifiés clairs tels que Toolhelp32.Snapshotou Toolhelp32.IEntrysi cela ne serait pas pertinent ici ...

Une fois le processus parent obtenu, si vous souhaitez en outre obtenir des informations détaillées, vous pouvez l'étendre facilement, par exemple, itérer sur ses modules, puis ajouter:

  • Code - WinModuleEntry

    [StructLayout(LayoutKind.Sequential)]
    public struct WinModuleEntry:Toolhelp32.IEntry { // MODULEENTRY32
        [DllImport("kernel32.dll")]
        public static extern bool Module32Next(Toolhelp32.Snapshot snap, ref WinModuleEntry entry);
    
        public bool TryMoveNext(Toolhelp32.Snapshot snap, out Toolhelp32.IEntry entry) {
            var x = new WinModuleEntry { dwSize=Marshal.SizeOf(typeof(WinModuleEntry)) };
            var b = Module32Next(snap, ref x);
            entry=x;
            return b;
        }
    
        public int dwSize;
        public int th32ModuleID;
        public int th32ProcessID;
        public int GlblcntUsage;
        public int ProccntUsage;
        public IntPtr modBaseAddr;
        public int modBaseSize;
        public IntPtr hModule;
        //byte moduleName[256];
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
        public string moduleName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string fileName;
        //byte fileName[260];
        //public const int sizeofModuleName = 256;
        //public const int sizeofFileName = 260;
    }
    

    et quelques tests ..

    public class TestClass {
        public static void TestMethod() {
            var p = Process.GetCurrentProcess().Parent();
            Console.WriteLine("{0}", p.Id);
    
            var formatter = new CustomFormatter { };
            foreach(var x in Toolhelp32.TakeSnapshot<WinModuleEntry>(Toolhelp32.SnapModule, p.Id)) {
                Console.WriteLine(String.Format(formatter, "{0}", x));
            }
        }
    }
    
    public class CustomFormatter:IFormatProvider, ICustomFormatter {
        String ICustomFormatter.Format(String format, object arg, IFormatProvider formatProvider) {
            var type = arg.GetType();
            var fields = type.GetFields();
            var q = fields.Select(x => String.Format("{0}:{1}", x.Name, x.GetValue(arg)));
            return String.Format("{{{0}}}", String.Join(", ", q.ToArray()));
        }
    
        object IFormatProvider.GetFormat(Type formatType) {
            return typeof(ICustomFormatter)!=formatType ? null : this;
        }
    }
    

Au cas où vous voudriez un exemple de code ..

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.