Come ottenere il processo genitore in .NET in modo gestito


85

Stavo cercando molto il metodo per ottenere il processo genitore in .NET, ma ho trovato solo il modo P / Invoke.


5
Cosa succede quando sono in esecuzione più istanze del tuo processo, poiché avranno tutte lo stesso ProcessName?
Michael Burr

1
Nel caso in cui aiuti qualcun altro: personalmente avevo bisogno solo dell'ID del processo genitore. Le soluzioni seguenti di Michael Hale e Simon Mourier non funzionano se il processo genitore è terminato perché stanno chiamando Process.GetProcessById()con un ID di un ID processo (ora) inesistente. Ma a quel punto hai l'ID di processo del genitore, quindi puoi usarlo se ne hai bisogno come ho fatto io.
Tyler Collier


Che ne dici di inviare l'ID del processo genitore come argomento della riga di comando? :)
John Demetriou

Risposte:


62

Questo codice fornisce una bella interfaccia per trovare l'oggetto del processo Parent e tiene conto della possibilità di più processi con lo stesso nome:

Utilizzo:

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

Codice:

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
Dov'è float.Asdefinito il metodo ?
Mark Byers

22
Questi sono metodi sorprendentemente mal chiamati.
Mark

4
Nei miei test, questo è molto più lento della soluzione di Simon Mourier. Inoltre, sfortunatamente fa una sorta di meccanismo di "porta il processo in primo piano". Non sono sicuro del perché. Qualcun altro ha sperimentato questo? Il test che sto eseguendo per questo è un programma di avvio automatico EXE creato da Visual Studio che avvia il programma di installazione di Windows MSIEXEC.exe.
Tyler Collier

6
Sfortunatamente non funziona quando il nome della categoria del contatore delle prestazioni è localizzato (ad esempio su Windows non in inglese).
LukeSw

5
Suggerirei la versione di Simon a meno che non ci sia un motivo urgente per non farlo, perché la differenza di prestazioni è significativa.
David Burton

151

Ecco una soluzione. Usa p / invoke, ma sembra funzionare bene, 32 o 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
In realtà è gestito, ma non portabile su un altro sistema operativo diverso da Windows, hai ragione. Tuttavia, anche la nozione di processo genitore non è portabile, poiché non è in .NET Framework stesso, quindi non credo sia un grosso problema.
Simon Mourier

11
Grande! Nessun contatore delle prestazioni lente. Odio davvero i commenti "non gestiti". In che modo eseguire query su un contatore delle prestazioni è più gestito rispetto a P / Invoke.
Jabe

5
Purtroppo questa funzione è solo interna. MSDN dice che "[NtQueryInformationProcess potrebbe essere alterato o non disponibile nelle versioni future di Windows. Le applicazioni dovrebbero utilizzare le funzioni alternative elencate in questo argomento.]" Msdn.microsoft.com/en-us/library/windows/desktop/…
justin. m. acquista il

21
@ justin.m.chase - È lì da quasi 20 anni, quindi dubito che verrà rimosso domani e non ci sono funzioni NT altenate che danno il processo genitore a mia conoscenza, ma sì, certo, usalo a tuo rischio e pericolo .
Simon Mourier

4
Questo metodo è almeno 10 volte più veloce quando ho confrontato le prestazioni di questo metodo con altri metodi. La risposta accettata zecche: 2600657. Questa risposta zecche: 8454.
Mojtaba Rezaeian

9

Per di qua:

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
Funziona, ma WMI può essere super lento (secondi) .pinvoke è la strada da percorrere.
Alastair Maw

4

Ecco il mio tentativo con una soluzione gestita.

Esegue il polling dei contatori delle prestazioni per tutti i processi e restituisce un dizionario del PID figlio al PID padre. Quindi puoi controllare il dizionario con il tuo attuale PID per vedere i tuoi genitori, nonni, ecc.

Sicuramente è eccessivo per quante informazioni ottiene. Sentiti libero di ottimizzare.

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

In altre notizie, ho appreso quanti contatori delle prestazioni c'erano sulla mia macchina: 13401. Holy cow.


2
Questo metodo funziona ma sembra essere estremamente lento. Ci sono voluti più di 10 secondi nella mia macchina.
Karsten

3

Se si accetta P / Invoke, c'è un modo migliore, che è più documentato di NtQueryInformationProcess: vale a dire PROCESSENTRY32 (CreateToolhelp32Snapshot, Process32First, Process32Next). È mostrato in questo post .

Presta attenzione ai dettagli sottili e nota che il PID genitore non è necessariamente il PID creatore, infatti questi potrebbero essere completamente estranei, come sottolineato dai commenti della comunità su PROCESSENTRY32 .


2

Se hai mai scavato nel BCL, scoprirai che i modi per trovare il processo genitore sono deliberatamente evitati, prendi questo ad esempio:

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

Come puoi vedere nel codice sorgente, contiene strutture complete e metodi nativi importati che sono assolutamente sufficienti per portare a termine il lavoro. Tuttavia, anche se accedi ad essi tramite la riflessione (questo è possibile), non troverai un metodo per farlo direttamente. Non so rispondere perché, eppure questo fenomeno fa sì che domande come le tue vengano poste un po 'ripetutamente; per esempio:

Come posso ottenere il PID del processo genitore della mia domanda

Poiché non c'è una risposta insieme ad un codice che utilizza CreateToolhelp32Snapshot in questo thread, lo aggiungerei - parte delle definizioni e dei nomi della struttura che rubo dalla fonte di riferimento di MS :)

  • Codice

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

E possiamo usarlo come:

  • Test

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

Per un finale alternativo ..

Secondo la documentazione, ci sono un paio di metodi di iterazione per tipo di voci come Process32FirsteProcess32Next sono per l'iterazione dei processi; ma ho scoperto che i metodi "xxxxFirst" non sono necessari, e poi ho pensato perché non mettere il metodo di iterazione con il tipo di voce corrispondente? Sarebbe più facile da implementare ed essere compreso (credo di sì ..).

Proprio come Toolhelp32suffisso con help , penso che una classe helper statica sia appropriata, in modo che possiamo avere nomi qualificati chiari come Toolhelp32.SnapshotoToolhelp32.IEntry anche se sarebbe irrilevante qui ..

Una volta ottenuto il processo genitore, se desideri ulteriormente ottenere alcune informazioni dettagliate, puoi estenderlo facilmente, ad esempio, iterare sui suoi moduli, quindi aggiungere:

  • Codice: 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;
    }
    

    e qualche prova ..

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

Nel caso in cui desideri un esempio di codice ..

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.