Cómo obtener el proceso principal en .NET de forma administrada


85

Estaba buscando mucho el método para obtener el proceso principal en .NET, pero solo encontré la forma P / Invoke.


5
¿Qué sucede cuando se ejecutan varias instancias de su proceso, ya que todas tendrán el mismo nombre de proceso?
Michael Burr

1
En caso de que ayude a otra persona: yo personalmente necesitaba solo la identificación del proceso principal. Las siguientes soluciones de Michael Hale y Simon Mourier no funcionan si el proceso principal ha salido porque están llamando Process.GetProcessById()con una ID de una ID de proceso (ahora) inexistente. Pero en ese momento tiene el ID de proceso del padre, por lo que puede usarlo si lo necesita como lo hice yo.
Tyler Collier


¿Qué tal si envía la identificación del proceso principal como argumento de línea de comando? :)
John Demetriou

Respuestas:


62

Este código proporciona una interfaz agradable para encontrar el objeto de proceso principal y tiene en cuenta la posibilidad de que haya varios procesos con el mismo nombre:

Uso:

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

Código:

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
¿Dónde se float.Asdefine el método ?
Mark Byers

22
Esos son algunos métodos sorprendentemente mal nombrados.
Mark

4
En mis pruebas, esto es mucho más lento que la solución de Simon Mourier. Además, desafortunadamente, tiene algún tipo de mecanismo de 'llevar el proceso al frente'. No estoy seguro de por qué. Alguien más ha experimentado esto? La prueba que estoy ejecutando para esto es un programa de instalación de arranque EXE creado por Visual Studio que inicia el instalador de Windows MSIEXEC.exe.
Tyler Collier

6
Desafortunadamente, no funciona cuando el nombre de la categoría del contador de rendimiento está localizado (por ejemplo, en Windows que no es inglés).
LukeSw

5
Sugeriría la versión de Simon a menos que haya una razón imperiosa para no hacerlo, porque la diferencia de rendimiento es significativa.
David Burton

150

He aquí una solución. Utiliza p / invoke, pero parece funcionar bien, 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
En realidad, se administra, pero no es portátil en otro sistema operativo que no sea Windows, tiene razón. Sin embargo, la noción de un proceso padre tampoco es portátil, ya que no está en .NET Framework en sí, por lo que no creo que sea un gran problema.
Simon Mourier

11
¡Excelente! Sin contadores de rendimiento lentos. Realmente odio los comentarios de "no administrado". ¿Cómo se puede consultar un contador de rendimiento más administrado que usar P / Invoke?
Jabe

5
Desafortunadamente, esta función es solo interna. MSDN dice que "[NtQueryInformationProcess puede estar alterado o no estar disponible en futuras versiones de Windows. Las aplicaciones deben usar las funciones alternativas que se enumeran en este tema.]" Msdn.microsoft.com/en-us/library/windows/desktop/…
justin. m.chase

21
@ justin.m.chase: ha estado allí durante casi 20 años, por lo que dudo que se elimine mañana, y no hay funciones de NT alternativas que le brinden el proceso principal a mi conocimiento, pero sí, claro, úselo bajo su propio riesgo. .
Simon Mourier

4
Este método es al menos 10 veces más rápido cuando comparé el rendimiento de este método con otros métodos. La respuesta aceptada garrapatas: 2600657. Esta respuesta garrapatas: 8454.
Mojtaba Rezaeian

9

De esta manera:

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
Funciona, pero WMI puede ser muy lento (segundos). Pinvoke es el camino a seguir.
Alastair Maw

4

Aquí está mi intento con una solución administrada.

Sondea los contadores de rendimiento de todos los procesos y devuelve un diccionario del PID secundario al PID principal. Luego, puede consultar el diccionario con su PID actual para ver a sus padres, abuelos, etc.

Es exagerado la cantidad de información que recibe, seguro. Siéntase libre de optimizar.

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

En otras noticias, supe cuántos contadores de rendimiento había en mi máquina: 13401. Santo cielo.


2
Este método funciona pero parece ser extremadamente lento. Tomó más de 10 segundos en mi máquina.
Karsten

3

Si acepta P / Invoke, hay una forma mejor, que está más documentada que NtQueryInformationProcess: a saber, PROCESSENTRY32 (CreateToolhelp32Snapshot, Process32First, Process32Next). Se muestra en esta publicación .

Preste atención a los detalles sutiles y tenga en cuenta que el PID padre no es necesariamente el PID creador, de hecho, estos pueden no estar relacionados en absoluto, como lo señalan los comentarios de la comunidad en PROCESSENTRY32 .


2

Si alguna vez ha investigado el BCL, encontrará que las formas de encontrar el proceso principal se evitan deliberadamente, tome esto, por ejemplo:

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

Como puede ver en el código fuente, contiene estructuras completas y métodos nativos importados que son absolutamente suficientes para hacer el trabajo. Sin embargo, incluso si accede a ellos a través de la reflexión (esto es posible), no encontrará un método para hacerlo directamente. No puedo responder por qué, sin embargo, este fenómeno hace que preguntas como la suya se hagan repetidamente; por ejemplo:

¿Cómo puedo obtener el PID del proceso principal de mi solicitud?

Porque no hay respuesta junto con algún código usando CreateToolhelp32Snapshot en este hilo, lo agregaría: parte de las definiciones de estructura y los nombres que robo de la fuente de referencia de MS :)

  • Código

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

Y podemos usarlo como:

  • Prueba

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

Para un final alternativo ...

De acuerdo con la documentación, hay un par de métodos de iteración por tipo de entradas como Process32Firsty Process32Nextson para la iteración de procesos; pero encontré que los métodos 'xxxxFirst' son innecesarios, y luego pensé ¿por qué no poner el método de iteración con su tipo de entrada correspondiente? Sería más fácil de implementar y entender (supongo que sí ...).

Al igual que Toolhelp32con el sufijo help , creo que una clase auxiliar estática es adecuada, de modo que podamos tener los nombres calificados claros, como Toolhelp32.Snapshoto Toolhelp32.IEntrysi sería irrelevante aquí ...

Una vez que se obtiene el proceso principal, si desea obtener más información detallada, puede ampliar con esto fácilmente, por ejemplo, iterar en sus módulos y luego agregar:

  • Código - 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;
    }
    

    y alguna prueba ..

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

En caso de que desee un ejemplo de código ...

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.