Jak uzyskać proces nadrzędny w .NET w zarządzany sposób


85

Dużo szukałem metody uzyskania procesu nadrzędnego w .NET, ale znalazłem tylko sposób P / Invoke.


5
Co się dzieje, gdy działa wiele instancji procesu, skoro wszystkie będą miały tę samą nazwę procesu?
Michael Burr,

1
Na wypadek, gdyby pomogło to komuś innemu: osobiście potrzebowałem tylko identyfikatora procesu rodzica. Poniższe rozwiązania autorstwa Michaela Hale'a i Simona Mouriera nie działają, jeśli proces nadrzędny został zamknięty, ponieważ wywołują Process.GetProcessById()z identyfikatorem (teraz) nieistniejącego identyfikatora procesu. Ale w tym momencie masz identyfikator procesu rodzica, więc możesz go użyć, jeśli potrzebujesz go, tak jak ja.
Tyler Collier,


Co powiesz na wysłanie identyfikatora procesu nadrzędnego jako argumentu wiersza poleceń? :)
John Demetriou

Odpowiedzi:


62

Ten kod zapewnia przyjemny interfejs do znajdowania obiektu procesu nadrzędnego i uwzględnia możliwość wielu procesów o tej samej nazwie:

Stosowanie:

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

Kod:

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
Gdzie jest float.Aszdefiniowana metoda ?
Mark Byers

22
To są zadziwiająco słabo nazwane metody.
Mark

4
W moich testach jest to znacznie wolniejsze niż rozwiązanie Simona Mouriera. Ponadto, niestety, wykonuje pewien mechanizm „wyprowadzania procesu na wierzch”. Nie wiem dlaczego. Czy ktoś jeszcze tego doświadczył? Test, który przeprowadzam w tym celu, to plik EXE programu instalacyjnego programu ładującego utworzony przez program Visual Studio, który uruchamia instalator systemu Windows MSIEXEC.exe.
Tyler Collier

6
Niestety nie działa, gdy nazwa kategorii licznika wydajności jest zlokalizowana (np. W systemie Windows innym niż angielski).
LukeSw,

5
Proponuję wersję Simona, chyba że istnieje pilny powód, aby tego nie robić, ponieważ różnica w wydajności jest znacząca.
David Burton,

150

Oto rozwiązanie. Używa p / invoke, ale wydaje się działać dobrze, 32 lub 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
W rzeczywistości jest zarządzany, ale nie przenośny w innym systemie operacyjnym niż Windows, masz rację. Jednak pojęcie procesu nadrzędnego również nie jest przenośne, ponieważ nie ma go w samym .NET Framework, więc nie sądzę, aby był to duży problem.
Simon Mourier

11
Świetny! Brak wolnych liczników wydajności. Naprawdę nienawidzę komentarzy „niezarządzanych”. W jaki sposób wysyłanie zapytań do licznika perf jest bardziej zarządzane niż przy użyciu P / Invoke.
Jabe

5
Niestety ta funkcja jest tylko wewnętrzna. MSDN informuje, że „[NtQueryInformationProcess może być zmieniony lub niedostępny w przyszłych wersjach systemu Windows. Aplikacje powinny używać alternatywnych funkcji wymienionych w tym temacie]”. Msdn.microsoft.com/en-us/library/windows/desktop/…
justin. m.chase

21
@ justin.m.chase - Jest tam od prawie 20 lat, więc wątpię, że zostanie jutro usunięty i nie ma alternatywnych funkcji NT, które przekazują proces nadrzędny zgodnie z moją wiedzą, ale tak, oczywiście, używaj na własne ryzyko .
Simon Mourier,

4
Ta metoda jest co najmniej 10 razy szybsza, gdy porównałem wydajność tej metody z innymi metodami. Odpowiedź Zaakceptowany kleszcze: 2600657. Ta odpowiedź kleszcze: 8454.
Mojtaba Rezaeian

9

Tą drogą:

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
Działa, ale WMI może działać bardzo wolno (w sekundach). Pinvoke to droga do zrobienia.
Alastair Maw

4

Oto moja próba rozwiązania zarządzanego.

Sonduje liczniki wydajności dla wszystkich procesów i zwraca słownik podrzędnego PID do nadrzędnego PID. Następnie możesz sprawdzić słownik z aktualnym PID, aby zobaczyć swojego rodzica, dziadka itp.

Na pewno przesada, jeśli chodzi o ilość otrzymywanych informacji. Zapraszam do optymalizacji.

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

W innych wiadomościach dowiedziałem się, ile liczników wydajności było na mojej maszynie: 13401. Święta krowa.


2
Ta metoda działa, ale wydaje się być bardzo powolna. W mojej maszynie zajęło to ponad 10 sekund.
Karsten

3

Jeśli akceptujesz P / Invoke, istnieje lepszy sposób, który jest bardziej udokumentowany niż NtQueryInformationProcess: a mianowicie PROCESSENTRY32 (CreateToolhelp32Snapshot, Process32First, Process32Next). Jest to pokazane w tym poście .

Zwróć uwagę na subtelne szczegóły i zauważ, że nadrzędny PID niekoniecznie jest PID twórcy, w rzeczywistości mogą one być całkowicie niezwiązane, jak wskazano w komentarzach społeczności w PROCESSENTRY32 .


2

Jeśli kiedykolwiek odkryłeś BCL, zauważysz, że celowo unika się sposobów znajdowania procesu rodzicielskiego, weź na przykład:

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

Jak widać w kodzie źródłowym, zawiera kompleksowe struktury i importowane metody natywne, które są absolutnie wystarczające do wykonania zadania. Jednak nawet jeśli uzyskasz do nich dostęp poprzez refleksję (jest to możliwe), nie znajdziesz metody, aby zrobić to bezpośrednio. Nie potrafię odpowiedzieć dlaczego, ale to zjawisko powoduje, że pytania takie jak twoje są zadawane nieco wielokrotnie; na przykład:

Jak mogę uzyskać PID procesu nadrzędnego mojej aplikacji

Ponieważ nie ma odpowiedzi wraz z kodem używającym CreateToolhelp32Snapshot w tym wątku, dodałbym go - część definicji struktur i nazw, które kradnę ze źródła referencji MS :)

  • Kod

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

I możemy to wykorzystać tak jak:

  • Test

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

Alternatywne zakończenie ...

Zgodnie z dokumentacją istnieje para metod iteracji dla każdego typu wpisów, takich jak Process32FirstiProcess32Next służą do iteracji procesów; ale stwierdziłem, że metody `xxxxFirst 'są niepotrzebne, a potem pomyślałem, dlaczego nie umieścić metody iteracji z odpowiadającym jej typem wpisu? Byłoby łatwiejsze do wdrożenia i zrozumienia (chyba tak…).

Tak samo jak Toolhelp32z dodatkiem help , myślę, że statyczna klasa pomocnicza jest właściwa, dzięki czemu możemy mieć jasne nazwy kwalifikowane, takie jak Toolhelp32.Snapshotlub Toolhelp32.IEntrygdyby nie miało to tutaj znaczenia.

Po uzyskaniu procesu nadrzędnego, jeśli chcesz dalej uzyskać szczegółowe informacje, możesz łatwo rozszerzyć o to, na przykład iterować na jego modułach, a następnie dodać:

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

    i trochę testu ...

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

Jeśli potrzebujesz przykładowego kodu ...

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.