Як отримати батьківський процес у .NET керованим способом


85

Я багато шукав метод отримати батьківський процес у .NET, але знайшов лише P / Invoke спосіб.


5
Що відбувається, коли запущено кілька екземплярів вашого процесу, оскільки всі вони матимуть одне і те ж ім’я ProcessName?
Майкл Бурр

1
На випадок, якщо це допоможе комусь іншому: мені особисто знадобився лише ідентифікатор батьківського процесу. Наведені нижче рішення Майкла Хейла та Саймона Мур'є не працюють, якщо батьківський процес завершився, оскільки вони телефонують Process.GetProcessById()із ідентифікатором (зараз) неіснуючого ідентифікатора процесу. Але на той момент у вас є батьківський ідентифікатор процесу, тому ви можете використовувати його, якщо вам це потрібно, як мені.
Тайлер Коллер

1
Дивіться також stackoverflow.com/questions/2531837 / ...
hortman

Як щодо того, щоб надіслати ідентифікатор батьківського процесу як аргумент командного рядка? :)
Джон Деметріу

Відповіді:


62

Цей код забезпечує приємний інтерфейс для пошуку об'єкта батьківського процесу та враховує можливість декількох процесів з однаковим ім'ям:

Використання:

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

Код:

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
Де float.Asвизначено метод ?
Mark Byers

22
Це дивовижно погано названі методи.
Марк

4
Під час мого тестування це набагато повільніше, ніж рішення Саймона Мур'є. Крім того, на жаль, це якийсь механізм "виведення процесу на перший план". Я не впевнений чому. Хтось ще переживав це? Тестом, який я запускаю для цього, є завантажувальний завантажувач EXE, створений Visual Studio, який запускає інсталятор MSIEXEC.exe.
Тайлер Коллер,

6
На жаль, це не працює, коли назва категорії лічильника продуктивності локалізована (наприклад, на неанглійській Windows).
LukeSw

5
Я б запропонував версію Саймона, якщо не буде нагальної причини цього не робити, оскільки різниця в продуктивності значна.
Девід Бертон,

150

Ось рішення. Він використовує p / invoke, але, здається, працює добре, 32 або 64 процесора:

    /// <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
Це насправді керовано, але не портативно в іншій ОС, ніж Windows, ви маєте рацію. Однак поняття батьківського процесу теж не є портативним, оскільки його немає в самій .NET Framework, тому я не думаю, що це велика проблема.
Simon Mourier

11
Чудово! Немає повільних лічильників перф. Я дуже ненавиджу "некеровані" коментарі. Як запити лічильника перфомантів більш керовані, ніж використання P / Invoke.
Джейб,

5
На жаль, ця функція є лише внутрішньою. MSDN каже, що "[NtQueryInformationProcess може бути змінений або недоступний у майбутніх версіях Windows. Додатки повинні використовувати альтернативні функції, перелічені в цій темі.]" Msdn.microsoft.com/en-us/library/windows/desktop/…
justin. м. покупка

21
@ justin.m.chase - Він був там майже 20 років, тому я сумніваюся, що його буде видалено завтра, і немає жодних функцій NT, які передають батьківський процес, наскільки мені відомо, але так, звичайно, використовувати на свій страх і ризик .
Саймон Мур'є

4
Цей метод принаймні в 10 разів швидший, якщо я порівнював результативність цього методу з іншими методами. Прийнятий відповідь кліщі: 2600657. Ця відповідь кліщів: 8454.
Мойтаба Резаян

9

Сюди:

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
Працює, але WMI може бути надповільним (секунди). Pinvoke - це шлях.
Alastair Maw

4

Ось моя спробу керованого рішення.

Він опитує лічильники продуктивності для всіх процесів і повертає словник дочірнього PID до батьківського PID. Потім ви можете перевірити словник із вашим поточним ПІД, щоб побачити своїх батьків, бабусь і дідусів тощо.

Напевно, це надмірна кількість інформації, яку вона отримує. Не соромтеся оптимізувати.

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

В інших новинах я дізнався, скільки лічильників продуктивності було на моїй машині: 13401. Свята корова.


2
Цей метод працює, але здається надзвичайно повільним. Це зайняло більше 10 секунд у моїй машині.
Карстен,

3

Якщо ви приймаєте P / Invoke, є кращий спосіб, який більш задокументований, ніж NtQueryInformationProcess: а саме PROCESSENTRY32 (CreateToolhelp32Snapshot, Process32First, Process32Next). Це показано в цій публікації .

Зверніть увагу на тонкі деталі та зауважте, що батьківський ПІД не обов’язково є ПІД творця, насправді вони можуть бути абсолютно не пов’язані між собою, на що вказують коментарі спільноти на PROCESSENTRY32 .


2

Якщо ви коли-небудь копали BCL, ви виявите, що способів знайти батьківський процес навмисно уникають, візьміть це для прикладу:

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

Як ви можете бачити у вихідному коді, він містить вичерпні структури та імпортовані власні методи, яких абсолютно достатньо для виконання роботи. Однак, навіть якщо ви отримаєте доступ до них за допомогою роздумів (це можливо), ви не знайдете методу, щоб зробити це безпосередньо. Я не можу відповісти, чому, але це явище викликає такі запитання, як у вас, задаються кілька разів; наприклад:

Як я можу отримати PID батьківського процесу моєї програми

Оскільки в цій темі немає відповіді разом з деяким кодом, який використовує CreateToolhelp32Snapshot , я б додав її - частина визначень структури та імен, які я викрадаю з джерела джерела довідки MS :)

  • Код

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

І ми можемо використовувати це як:

  • Тест

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

Для альтернативного закінчення ..

Згідно з документацією, існує пара методів ітерації для кожного типу записів, таких як Process32Firstі Process32Nextдля ітерації процесів; але я виявив, що методи `xxxxFirst 'непотрібні, і тоді я подумав, чому б не поставити метод ітерації з відповідним типом запису? Це було б простіше реалізувати і зрозуміти (я думаю, так ..).

Так само , як Toolhelp32з суфіксом допомоги , я думаю , що статичний допоміжний клас є правильним, так що ми можемо мати чіткі уточнені імена , такі як Toolhelp32.Snapshotабо Toolhelp32.IEntryхоча б тут недоречно ..

Отримавши батьківський процес, якщо ви хочете отримати детальну інформацію, ви можете легко продовжити це, наприклад, виконати ітерацію по його модулях, а потім додати:

  • Код - 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;
    }
    

    і якийсь тест ..

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

Якщо вам потрібен приклад коду ..

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.