Como obter o processo pai em .NET de maneira gerenciada

85

Eu estava procurando muito por um método para obter o processo pai no .NET, mas encontrei apenas a forma P / Invoke.

Abatishchev
fonte
5
O que acontece quando várias instâncias do seu processo estão em execução, já que todas terão o mesmo ProcessName?
Michael Burr
1
Caso isso ajude outra pessoa: eu pessoalmente precisava apenas do ID do processo pai. As soluções abaixo de Michael Hale e Simon Mourier não funcionam se o processo pai foi encerrado porque eles estão chamando Process.GetProcessById()com um ID de um (agora) ID de processo inexistente. Mas nesse ponto você tem o ID do processo do pai, para que possa usá-lo se precisar como eu fiz.
Tyler Collier
1
Consulte também stackoverflow.com/questions/2531837/…
hortman
Que tal enviar o id do processo pai como argumento de linha de comando? :)
John Demetriou

Respostas:

62

Este código fornece uma interface agradável para encontrar o objeto do processo pai e leva em consideração a possibilidade de vários processos com o mesmo nome:

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));
    }
}
Michael Hale
fonte
2
Onde o método é float.Asdefinido?
Mark Byers em
22
Esses são alguns métodos surpreendentemente mal nomeados.
Marcos
4
Em meus testes, isso é muito mais lento do que a solução de Simon Mourier. Além disso, infelizmente faz algum tipo de mecanismo de 'trazer o processo para a frente'. Não sei por quê. Alguém mais experimentou isso? O teste que estou executando para isso é um bootstrapper EXE de instalação criado pelo Visual Studio que inicia o instalador do Windows MSIEXEC.exe.
Tyler Collier
6
Infelizmente, ele não funciona quando o nome da categoria do contador de desempenho está localizado (por exemplo, no Windows que não seja em inglês).
LukeSw
5
Eu sugeriria a versão de Simon, a menos que haja uma razão urgente para não fazê-lo, porque a diferença de desempenho é significativa.
David Burton
151

Aqui está uma solução. Ele usa p / invoke, mas parece funcionar bem, 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;
            }
        }
    }
Simon Mourier
fonte
13
Na verdade, ele é gerenciado, mas não é portátil em outro sistema operacional diferente do Windows, você está certo. No entanto, a noção de um processo pai também não é portátil, já que não está no .NET Framework em si, então não acho que seja um grande problema.
Simon Mourier
11
Ótimo! Sem contadores de desempenho lentos. Eu realmente odeio os comentários "não gerenciados". Como consultar um contador de desempenho é mais gerenciado do que usar P / Invoke.
Jabe
5
Infelizmente, esta função é apenas interna. MSDN diz que "[NtQueryInformationProcess pode ser alterado ou indisponível em versões futuras do Windows. Os aplicativos devem usar as funções alternativas listadas neste tópico.]" Msdn.microsoft.com/en-us/library/windows/desktop/…
justin. m.chase
21
@ justin.m.chase - Está lá há quase 20 anos, então duvido que seja removido amanhã e não há funções NT alternativas que forneçam o processo pai ao meu conhecimento, mas sim, claro, use por sua própria conta e risco .
Simon Mourier
4
Este método é pelo menos 10 vezes mais rápido quando comparei o desempenho deste método com outros métodos. A resposta aceita carrapatos: 2600657. Esta resposta carrapatos: 8454.
Mojtaba Rezaeian
9

Deste jeito:

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;
  }
}
Péter Major
fonte
2
Funciona, mas o WMI pode ser muito lento (segundos) .pinvoke é o caminho a percorrer.
Alastair Maw
4

Aqui está minha tentativa de uma solução gerenciada.

Ele consulta os contadores de desempenho para todos os processos e retorna um dicionário de PID filho para o PID pai. Em seguida, você pode verificar o dicionário com seu PID atual para ver seus pais, avós, etc.

É um exagero na quantidade de informações que recebe, com certeza. Sinta-se à vontade para otimizar.

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

Em outras notícias, descobri quantos contadores de desempenho havia na minha máquina: 13401. Caramba.

Jeremy Murray
fonte
2
Este método funciona, mas parece ser extremamente lento. Demorou mais de 10 segundos na minha máquina.
Karsten
3

Se aceitar P / Invoke, existe uma maneira melhor, que é mais documentada do que NtQueryInformationProcess: PROCESSENTRY32 (CreateToolhelp32Snapshot, Process32First, Process32Next). É mostrado neste post .

Preste atenção aos detalhes sutis e observe que o PID pai não é necessariamente o PID do criador; na verdade, eles podem não estar relacionados, conforme apontado pelos comentários da comunidade em PROCESSENTRY32 .

robert4
fonte
2

Se você já cavou o BCL, você descobrirá que as maneiras de encontrar o processo pai são deliberadamente evitadas, veja isto por exemplo:

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

Como você pode ver no código-fonte, ele contém estruturas abrangentes e métodos nativos importados que são absolutamente suficientes para realizar o trabalho. No entanto, mesmo se você acessá-los por meio de reflexão (isso é possível), você não encontrará um método para fazer isso diretamente. Não posso responder por quê, mas esse fenômeno faz com que perguntas como as suas sejam feitas com certa frequência; por exemplo:

Como posso obter o PID do processo pai da minha inscrição

Pois não há resposta junto com algum código usando CreateToolhelp32Snapshot neste tópico, eu adicionaria - parte das definições de estrutura e nomes que roubo da fonte de referência da 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);
        }
    }
    

E podemos usá-lo como:

  • Teste

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

Para finalização alternativa ..

De acordo com a documentação, há um par de métodos de iteração por tipo de entradas como Process32Firste Process32Nextsão para a iteração de processos; mas descobri que os métodos `xxxxFirst 'são desnecessários, e então pensei por que não colocar o método de iteração com seu tipo de entrada correspondente? Seria mais fácil de implementar e ser compreendido (acho que sim ..).

Assim como Toolhelp32com o sufixo help , acho que uma classe auxiliar estática é apropriada, para que possamos ter os nomes qualificados claros, como Toolhelp32.Snapshotou Toolhelp32.IEntryse fosse irrelevante aqui.

Uma vez que o processo pai é obtido, se você ainda deseja obter algumas informações detalhadas, você pode estender isso facilmente, por exemplo, iterar em seus módulos e, em seguida, adicionar:

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

    e algum teste ..

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

Caso você queira um exemplo de código ..

Ken Kin
fonte