Win32进程如何获取其父进程的pid?

41

我目前是通过命令行将pid传递给子进程,但是在Win32 API中有没有一种方法可以做到这一点?另外,是否有人可以消除我担心的疑虑,即如果父进程死亡后,我传递的pid可能属于另一个进程?


当父进程退出时,Windows XP不会重新分配父PID为-1,因此进程树可能是错误的(我曾经见过这种情况发生)。因此,在Process Explorer显示的树中,您可以看到NOTEPAD.EXE作为IEXPLORE.EXE的父进程,这显然是错误的。 - Kevin Panko
5个回答

60

提供一份代码示例,以便其他人在查找此问题时可以参考。最近我正在开发一个Python库项目,不久前也需要做到这一点。以下是我编写的测试/样本代码:

#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>

int main(int argc, char *argv[]) 
{
    int pid = -1;
    HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 pe = { 0 };
    pe.dwSize = sizeof(PROCESSENTRY32);

    //assume first arg is the PID to get the PPID for, or use own PID
    if (argc > 1) {
        pid = atoi(argv[1]);
    } else {
        pid = GetCurrentProcessId();
    }

    if( Process32First(h, &pe)) {
        do {
            if (pe.th32ProcessID == pid) {
                printf("PID: %i; PPID: %i\n", pid, pe.th32ParentProcessID);
            }
        } while( Process32Next(h, &pe));
    }

    CloseHandle(h);
}

你有 Python 版本吗? - Alex Okrushko
1
这种方法可能会提供如其他地方所述的过时PID,如果您尝试遍历父ID,则甚至可能存在循环。 - jws
5
最近stackoverflow怎么了,我已经厌倦了在一个编程网站上浏览数十页而没有看到任何代码。如果有代码示例,我可以支付1000000美元。 - user562566
1
你可能想要添加一个检查INVALID_HANDLE_VALUE的语句。if ( h == INVALID_HANDLE_VALUE ) { // got an invalid handle - hmatt1
@JamesSchmidt,您所说的循环是指进程的父进程也可能是其子孙进程吗?在什么情况下会发生这种情况? - GetFree
一个进程的父进程可能在子进程运行时退出,而子进程仍然保持着旧父进程的PID。然后稍后该PID可以被重新分配,然后子进程的“父进程”仍然指向这个PID,现在甚至可能是它自己的子进程! - jws

23

更好的方法是调用DuplicateHandle()函数来创建一个可继承的进程句柄副本,然后创建子进程并将句柄值传递给命令行。在父进程中关闭复制的句柄。
当子进程完成时,它也需要Close其副本。


8
这种方法的优点在于,即使父进程在您访问句柄之前死亡,该句柄仍然确实指向您的父进程。使用传递PID的方法,可能存在竞争条件(尽管非常不太可能),即传递PID、子进程访问它、父进程关闭并且PID被重新使用之间存在竞争条件。 - Len Holgate
谢谢Peter... DuplicateHandle正是我一直在寻找的。你说得对,HANDLE比PID更好(我只想使用WaitForSingleObject,以便子进程可以在父进程意外退出时终止)。 - Matt Gallagher
DuplicateHandle需要一个参数,即目标进程句柄。你写道首先创建重复句柄,然后创建子进程,这是无意义的。你必须先创建子进程,然后再创建进程句柄的副本。因此,你不能在命令行上传递句柄值。 - truthseeker
1
@truthseeker:不...你必须先复制句柄。否则,子进程将无法继承它。您可以通过命令行传递重复的句柄,因为子进程中继承的句柄与父进程中的数值相同。想一想:如果您无法在继承它们的进程中访问这些句柄,那么句柄继承将是相当无用的! - Peter Ruderman
3
澄清一下:通常你会选择复制或继承一个句柄。但在这种情况下,你只有从GetCurrentProcess()返回的伪句柄,它不能被继承。所以你必须复制它以生成一个真正的可继承句柄。使用继承的原因是如果你直接将句柄复制到子进程,你没有轻松的方法告诉子进程句柄值是什么。 - Harry Johnston
显示剩余2条评论

23
ULONG_PTR GetParentProcessId() // By Napalm @ NetCore2K
{
 ULONG_PTR pbi[6];
 ULONG ulSize = 0;
 LONG (WINAPI *NtQueryInformationProcess)(HANDLE ProcessHandle, ULONG ProcessInformationClass,
  PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength); 
 *(FARPROC *)&NtQueryInformationProcess = 
  GetProcAddress(LoadLibraryA("NTDLL.DLL"), "NtQueryInformationProcess");
 if(NtQueryInformationProcess){
  if(NtQueryInformationProcess(GetCurrentProcess(), 0,
    &pbi, sizeof(pbi), &ulSize) >= 0 && ulSize == sizeof(pbi))
     return pbi[5];
 }
 return (ULONG_PTR)-1;
}

3
在使用 NtQueryInformationProcess 之前,请注意它是一个不受支持的 API,我引用链接中的话:"NtQueryInformationProcess 可能在未来的 Windows 版本中被更改或不可用。" 此外,即使在文档中,父进程 ID 字段也是一个保留字段。 - Marc Durdin
对于最新的MSVC 2017,Right功能表现不太好。 - Stef
我发现这种技术“大多数情况下”有效,但偶尔会出现NtQueryInformationProcess返回0xC0000024或0xC0000008的情况。 - Oliver Bock

13

注意,如果父进程终止,很可能甚至有可能会重新使用PID分配给另一个进程。这是标准的Windows操作。

因此为了确保,一旦您接收到父进程的ID并确认它确实是您的父进程,应该打开一个句柄并使用它。


这可以使用OpenProcess()函数完成。请参见下面artur02答案中的链接。 - Andreas Haferburg
27
通过额外检查启动时间,可以检测到重复使用的进程标识符(PIDs)。真正的父进程启动时间会早于子进程,而被重复使用的PID则会晚于子进程。请注意保持内容原意不变,并使翻译通俗易懂。 - ivan_pozdeev

3

请问有没有其他方法可以缓解我的担忧?如果父进程已经死亡,我传递的pid是否可能在某些时间属于另一个进程?

是的,PID可以被重用。与UNIX不同,Windows没有维护强大的父子关系树。


7
在Linux系统中,进程ID也会被重用,这是因为pid_t并不是无限大小的,MAXPID仅为2^15。我每天都会看到这种情况发生多次。而在Windows系统中,通过句柄有着强大的父子进程关系 -- 只要保持句柄打开,就可以继续引用父进程。 - user4590120

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接